Server: 일괄 제출 API + upsert_all + N+1 제거
ID: 7f1b04ff-a40a-4697-bbf4-a70bf37065f5
## 목표
Big5 일괄 제출을 위한 서버 API 구현 + 기존 코드 N+1 제거.
## ⚠️ 리뷰 반려 사유 (2026-03-09)
### [CRITICAL] r[:selected] 타입 안전성 (controller:110)
- `r[:selected] ? 1 : 0`에서 ActionController::Parameters를 통해 문자열 `"false"`가 올 경우 Ruby에서 truthy → 모든 응답이 1로 저장되는 치명적 버그
- **수정**: `ActiveModel::Type::Boolean.new.cast(r[:selected]) ? 1 : 0` 사용
### [HIGH] question_id 타입 불일치 (controller:87-88)
- `submitted_ids`가 문자열 Set, `valid_question_ids`가 UUID Set → 검증 무력화 가능
- **수정**: `r[:question_id].to_s`로 타입 통일
### [HIGH] valid_questions_index 키 타입 불일치 (controller:107)
- `index_by(&:id)` 키가 UUID인데 `r[:question_id]`가 문자열 → dna_type이 nil로 저장
- **수정**: `valid_questions_index[r[:question_id].to_s]` 또는 키 타입 통일
---
## 작업 내용
(이하 기존 내용 동일)
### 1. 새 컨트롤러 액션: submit_all_big5_responses
- POST /diagnosis_sessions/:id/submit_all_big5_responses
- params: { responses: [{ question_id:, selected: true/false, response_time: }] }
- 90개 응답(30라운드 × 3문항)을 한 번에 수신
- 서버 검증: 응답 수 확인, question_id 유효성, 세션 상태 확인
### 2. session_manager: save_responses_bulk
- upsert_all 활용
### 3. N+1 제거 (기존 코드)
### 4. 라우트 추가
## 완료 기준
- 반려 사유 3건 모두 수정
- 타입 안전성 테스트 추가
- 기존 테스트 통과
첨부 이미지
이미지 추가 (Ctrl+V로 붙여넣기 또는 클릭)
JPEG, PNG, GIF, WebP / 최대 10MB
활동 로그
-
팀팀리드 재리뷰 완료 - 승인 (Server: 일괄 제출 API)
2026년 03월 09일 07:16:26
상세 보기
## 리뷰 결과
- 상태: 승인
- 코드 품질: 반려사유 3건 모두 수정 확인 (Boolean cast, question_id 타입 통일, index 키 타입 통일)
- 보안: PASS — ActiveModel::Type::Boolean.new.cast() 안전, .to_s 변환 SQL injection 무관
- 테스트: 타입 안전성 테스트 2건 추가, 전체 12 runs 0 failures -
팀팀리드 상태 변경: 리뷰 → 완료
2026년 03월 09일 07:16:19
-
Bbackend-dev 반려 사유 3건 수정 완료
2026년 03월 09일 07:13:08
상세 보기
## 변경 사항
### diagnosis_sessions_controller.rb
- `ActiveModel::Type::Boolean.new.cast(r[:selected])` 적용 — 문자열 "false" 안전 처리
- `submitted_ids`를 `.to_s` 변환하여 타입 통일
- `valid_question_id_strings` Set 생성으로 검증 로직 타입 일치
- `string_keyed_index` 변환으로 dna_type nil 방지### diagnosis_sessions_controller_test.rb
- 문자열 "true"/"false" selected 필드 테스트 추가
- 문자열 question_id 검증 및 dna_type 조회 테스트 추가## 테스트 결과
- 12 runs, 63 assertions, 0 failures, 0 errors -
Bbackend-dev 상태 변경: 진행 중 → 리뷰
2026년 03월 09일 07:13:00
-
팀팀리드 리뷰 완료 - 반려 (Server: 일괄 제출 API)
2026년 03월 09일 07:05:35
상세 보기
## 리뷰 결과
- 상태: 반려
- bin/ci: 통과 (payments 관련 기존 실패 제외)## 코드 품질
- [CRITICAL] r[:selected] 타입 안전성 — 문자열 "false"가 truthy로 처리되어 모든 응답이 1로 저장될 위험
- [HIGH] question_id 타입 불일치 — String vs UUID Set 비교로 검증 무력화 가능
- [HIGH] valid_questions_index 키 타입 불일치 — dna_type이 nil로 저장될 위험## 보안
- Brakeman/bundler-audit 통과
- upsert_all SQL Injection 위험 없음
- value 필드 Boolean→0/1 변환으로 임의값 주입 불가## UX/UI
- 해당 없음 (서버 API)## 수정 필요 사항
- ActiveModel::Type::Boolean.new.cast() 사용
- r[:question_id].to_s로 타입 통일
- 타입 안전성 테스트 추가 -
팀팀리드 상태 변경: 리뷰 → 진행 중
2026년 03월 09일 07:04:48
-
Rrails-dev fixture 부작용 테스트 2건 수정 완료
2026년 03월 09일 02:27:37
상세 보기
## 수정 1: DiagnosisFlowTest#test_index_page_renders_diagnosis_types
- **원인**: `big5_adult` fixture의 `name_key: "diagnosis.types.big5_adult"`에 대한 i18n 키가 locale 파일에 없어서 `feature_tags`가 String 반환 → `.each` 에러
- **수정**: `config/locales/diagnosis/ko.yml`에 `big5_adult` 키 추가 (title, description, features)## 수정 2: DiagnosisFlowTest#test_start_shows_confirm_resume_when_duplicate_within_1_hour
- **원인**: `confirm_resume`는 `status: :unprocessable_entity` (422)로 렌더링하지만 테스트가 `assert_response :success` (200) 기대
- **수정**: `assert_response :success` → `assert_response :unprocessable_entity`
- **추가**: big5 fixture의 `created_at`을 2~3시간 전으로 설정 (duplicate check 영향 방지)## 변경 파일
- `config/locales/diagnosis/ko.yml` — big5_adult i18n 키 추가
- `test/integration/diagnosis_flow_test.rb` — assert_response 수정
- `test/fixtures/diagnosis_sessions.yml` — big5 fixture created_at 조정## 테스트 결과
57 runs, 146 assertions, 0 failures, 0 errors -
Qqa-verifier 테스트 및 품질 검증 완료
2026년 03월 09일 02:22:45
상세 보기
## 검증 결과
### 1. 새로운 테스트 (18개 모두 통과 ✅)
- `submit_all_big5_responses` 액션 테스트 5개: 모두 통과
- `save_responses_bulk` 서비스 테스트 2개: 모두 통과
- `submit_big5_round`, `submit_part2_round` 테스트 포함### 2. N+1 쿼리 제거 확인 ✅
- `submit_big5_round`, `submit_part2_round`: `SurveyQuestion.where(id:).index_by` 패턴 사용
- `submit_all_big5_responses`: `valid_questions_index` memoization 적용### 3. 보안 점검
- **CSRF**: Stimulus 컨트롤러에서 `X-CSRF-Token` 헤더 사용 ✅
- **Strong Parameters**: `params.require(:responses)` 사용 ✅
- **SQL injection**: upsert_all에서 records 값이 서버 검증 후 빌드, unique_by/update_only 하드코딩 ✅
- **sessionStorage XSS**: `_escapeHTML()` 처리, 서버 재검증 ✅
- **subset 검증**: invalid_ids 체크로 외부 question_id 주입 차단 ✅
- **Brakeman**: 신규 코드에서 추가 이슈 없음 (기존 이슈만 존재)### 4. 기존 실패 분류
- `CustomPaymentsTest` (7개 오류): 기존 라우팅 오류, 새 코드와 무관 (stash 후 테스트로 확인)
- `DiagnosisFlowTest#test_index_page_renders_diagnosis_types`: **새 fixture(big5_adult) 추가 부작용** - i18n 번역 키 `diagnosis.types.big5_adult.features` 누락
- `DiagnosisFlowTest#test_start_shows_confirm_resume_when_duplicate_within_1_hour`: **새 fixture 부작용** - big5_in_progress 세션 추가로 인해 duplicate 체크 로직에 영향 가능성### 5. Minor 코드 이슈
- `submit_all_big5_responses`에서 `valid_question_ids`(Set)와 `valid_questions_index`(Hash)를 별도 쿼리 (통합 가능)
- question_id 타입 비교: JSON 파싱 시 Integer vs String 불일치 가능성### 종합 판정
✅ 핵심 기능 테스트 통과 | ⚠️ fixture 추가로 인한 2개 기존 테스트 실패 수정 필요 -
Rrails-dev question_id 검증 로직 subset 방식으로 수정 완료
2026년 03월 09일 02:18:37
상세 보기
## 문제
- 기존: `submitted_ids.sort == valid_question_ids.sort` (정확 일치 요구)
- 클라이언트는 90개(60긍정 + 30선택부정) 전송, 서버에는 105개(60긍정 + 45부정)
- → 항상 422 실패## 수정 내용
- subset 검증으로 변경: `submitted_ids`가 `valid_question_ids`의 부분집합인지 확인
- 최소 응답 수 검증: `valid_question_ids.size / 2` 이상 필요
- invalid ID 검증: 비-big5 문항 ID 거부## 테스트 변경
- fixture: quality당 부정 문항 1개 → 3개 (production과 동일 구조)
- 새 테스트: subset 수락, 비-big5 ID 거부, 너무 적은 응답 거부
- 48 runs, 129 assertions, 0 failures## 변경 파일
- `app/controllers/diagnosis_sessions_controller.rb`
- `test/controllers/diagnosis_sessions_controller_test.rb`
- `test/fixtures/survey_questions.yml` -
Rrails-dev Server API 구현 완료
2026년 03월 09일 02:07:55
상세 보기
## 변경 파일
### 구현 파일
- `app/controllers/diagnosis_sessions_controller.rb` — submit_all_big5_responses 액션 추가, N+1 제거 (submit_big5_round, submit_part2_round), RecordNotUnique rescue 추가
- `app/services/diagnosis/session_manager.rb` — save_responses_bulk 메서드 추가 (upsert_all 사용), save_response에 RecordNotUnique retry 추가
- `config/routes.rb` — post :submit_all_big5_responses 라우트 추가### 테스트 파일
- `test/controllers/diagnosis_sessions_controller_test.rb` — 신규 (6개 테스트: submit_all_big5_responses 4개 + N+1 제거 검증 2개)
- `test/services/diagnosis/session_manager_test.rb` — save_responses_bulk 테스트 2개 추가
- `test/fixtures/diagnosis_categories.yml` — big5 category 추가
- `test/fixtures/diagnosis_types.yml` — big5_adult type 추가
- `test/fixtures/diagnosis_sessions.yml` — big5 세션 2개 추가
- `test/fixtures/survey_questions.yml` — big5 문항 12개 추가
- `test/controllers/admin/users_controller_test.rb` — fixture 수 변경 반영
- `test/integration/admin/users_test.rb` — fixture 수 변경 반영## 주요 변경 사항
1. **submit_all_big5_responses**: 90개 응답 일괄 제출 API. 세션 상태, question_id 유효성, 응답 수 검증 후 upsert_all로 저장 → complete 처리
2. **save_responses_bulk**: DiagnosisResponse.upsert_all로 1회 쿼리에 전체 응답 저장. unique index(idx_responses_session_question) 기반 upsert
3. **N+1 제거**: submit_big5_round와 submit_part2_round에서 SurveyQuestion.find(qid)를 SurveyQuestion.where(id:).index_by(&:id)로 변경
4. **RecordNotUnique rescue**: save_response의 find_or_initialize_by 레이스 컨디션 방어## 테스트 결과
- 46 runs, 124 assertions, 0 failures, 0 errors, 0 skips -
Rrails-dev 상태 변경: 할 일 → 리뷰
2026년 03월 09일 02:07:38
-
Rrails-dev Server API 구현 시작
2026년 03월 09일 02:02:16
상세 보기
## 작업 범위
- submit_all_big5_responses 컨트롤러 액션
- save_responses_bulk 서비스 메서드
- N+1 제거 (submit_big5_round, submit_part2_round)
- 라우트 추가
- TDD 기반 개발