# 이벤트 접속 국가별 언어 필터 구현 리뷰 ## 1. 리뷰 정보 | 항목 | 내용 | |---|---| | 리뷰 대상 | Phase 1~2 전체 구현과 `P1-GATE`, `P2-GATE` | | 기준 commit 또는 working tree | 기준 `2ad30f90699a03162000d3b87be39f45b92740af` + 미커밋 구현 변경 | | 리뷰 일자 | 2026-08-20 | | 리뷰어 | Codex, 독립 리뷰 agent `event_language_review` | | 기준 문서 | `prd.md`, `plan-task.md` | | 리뷰 상태 | 수정 검증 완료 | ## 2. 리뷰 목적과 범위 ### 목적 - 구현이 `EVENT-LANG-001~009`와 기존 API 경계를 충족하는지 확인한다. - 완료 기록과 실제 코드·테스트 결과가 일치하는지 확인한다. ### 포함 범위 - 코드: 관리자 이벤트 등록·목록, 앱 이벤트 controller/service/repository, `Event`, `SecurityConfig` - 테스트: 관리자·앱 controller 통합, service, repository, 기존 국가 판정 통합 테스트 - 문서: `prd.md`, `plan-task.md`, 운영 DDL ### 제외 범위 - 운영 DB DDL 실제 실행과 배포 - 이벤트 언어와 관계없는 기능 ## 3. 판정 기준 | 심각도 | 기준 | |---|---| | Blocker | 보안·데이터 손실 위험 또는 핵심 흐름 불능 | | High | 확정 요구사항·기존 API 경계 위반 또는 주요 회귀 | | Medium | 제한된 조건의 기능·복구 문제 | | Low | 비핵심 유지보수성·문서 정합성 문제 | ## 4. 검토한 근거 ### 문서와 코드 - 요구사항: `EVENT-LANG-001~009` - 계획: `P1-T1~P2-GATE` - 코드: `AdminEventBannerController`, `AdminEventBannerService`, `AdminEventBannerRepository`, `EventController`, `EventService`, `EventRepository`, `Event`, `SecurityConfig` - 테스트: `AdminEventBannerControllerIntegrationTest`, `EventControllerIntegrationTest`, `EventServiceTest`, `EventRepositoryTest`, `MemberContentPreferenceIntegrationTest` ### 실행 환경 ```text OS: Darwin 25.0.0 x86_64 Java: OpenJDK 17.0.15 Build: Gradle Wrapper 8.1.1 DB: 테스트용 H2 MySQL mode ``` ### 실행한 검증 | 명령 또는 수동 검증 | 결과 | 핵심 증거 | |---|---|---| | 직접 영향 5개 클래스 `cleanTest test` | 성공 | 겹침 제거 후 exit code 0, 27개 테스트 통과, `BUILD SUCCESSFUL in 46s` | | 독립 리뷰의 동일 5개 클래스 실행 | 성공 | exit code 0, `BUILD SUCCESSFUL in 56s` | | 전체 호출자·기준 HEAD 보안 설정 diff 대조 | 발견 | 기준 HEAD의 `/event/popup`은 `anyRequest().authenticated()` 적용 | | 수정 후 독립 정적 재리뷰 | 성공 | Critical·Important·Minor 추가 발견 없음, Ready to merge 판정 | | `git diff --check HEAD` | 성공 | exit code 0, whitespace 오류 없음 | 동시에 실행된 두 Gradle `cleanTest`가 같은 XML 결과 경로를 사용해 한 차례 writer 경합이 발생했다. 테스트 assertion은 모두 통과했고, 병행 실행을 중단한 뒤 동일 명령을 재실행해 성공 종료를 확인했다. ## 5. 발견 사항 요약 | ID | 심각도 | 상태 | 제목 | 소유 Task | 후속 goal | |---|---|---|---|---|---| | `REV-EVENT-LANG-001` | High | 수정 완료 | `GET /event/popup`의 기존 인증 경계가 익명 공개로 확장됨 | `P2-T1` | `P2-R1` | 언어 저장·관리자 전체 언어 조회·앱 KO/JA QueryDSL 필터·콘텐츠 메인 null 언어 경로에서는 추가 확정 문제를 찾지 못했다. ## 6. 발견 사항 상세 ### REV-EVENT-LANG-001 — `GET /event/popup`의 기존 인증 경계가 익명 공개로 확장됨 - **심각도:** High - **상태:** 수정 완료 - **관련 요구사항:** `EVENT-LANG-005`, 기존 접근 정책 유지 - **소유 Task:** `P2-R1` **관찰 내용** 기준 HEAD의 `SecurityConfig`는 `GET /event`만 `permitAll()`이고, `/event/popup`은 마지막 `anyRequest().authenticated()`를 적용받는다. 수정 전 구현은 `/event/popup` exact matcher를 추가해 익명 요청도 허용했다. 국가별 언어 필터에는 인증 정책 변경이 필요하지 않으므로 요청 범위를 넘어선 보안 경계 확장이었다. **근거** - 기준 코드: `SecurityConfig.kt`의 `/event` matcher 다음에 `/event/popup` matcher가 없고 마지막 규칙은 `anyRequest().authenticated()`다. - 수정 전 코드: `SecurityConfig.kt`에 `GET /event/popup` `permitAll()` 한 줄이 추가됐다. - 수정 전 테스트: `EventControllerIntegrationTest`의 팝업 2개가 익명 200을 기대해 확장된 동작을 고정했다. - 문서: `prd.md`가 두 API 모두 기존 로그인·비로그인 접근이라고 잘못 기술했다. **재현 또는 검증 절차** 1. 수정 전 security filter를 켠 상태로 인증 없이 `GET /event/popup`을 호출한다. 2. 수정 전 결과는 HTTP 200이다. 3. 기준 HEAD의 matcher 순서에서는 HTTP 401이다. 4. 요구되는 결과는 기존 인증 경계를 유지하는 HTTP 401이며, 인증 사용자의 국가별 팝업 필터는 계속 동작해야 한다. **영향** 명시 승인 없이 endpoint 접근 범위가 넓어지고, PRD가 실제 기준 동작과 불일치한다. **권장 조치** `SecurityConfig`의 신규 matcher 한 줄을 제거한다. 통합 테스트에는 익명 팝업 401 회귀를 추가하고, JP·국가 누락 팝업 언어 테스트는 `MemberAdapter` 인증 사용자로 실행한다. `/event`의 기존 익명 접근과 언어 조회 구현은 변경하지 않는다. **판정 기록** - 2026-08-20 — 기준 HEAD·현재 diff·security-on 통합 테스트를 대조해 확정했다. - 2026-08-20 — `P2-R1`에서 exact `permitAll()`을 제거하고 익명 401·인증 사용자 KO/JA 팝업과 직접 영향 28개 테스트를 통과해 수정 완료로 판정했다. ## 7. 확정 항목의 plan·goal 전환 `REV-EVENT-LANG-001`을 `plan-task.md`의 `P2-R1` 회귀 수정 Goal로 전환했다. ## 8. 리뷰 종료 판정 | 판정 항목 | 결과 | 근거 | |---|---|---| | 리뷰 범위 전체 확인 | 충족 | 문서·production diff·전체 호출자·DDL·직접 영향 테스트 확인 | | 후보 항목 판정 완료 | 충족 | 1건 확정, 나머지 substantive issue 없음 | | 확정 항목 plan 반영 | 충족 | `P2-R1` 추가 | | 검증 명령과 결과 기록 | 충족 | 4절 기록 | **최종 결론:** 수정 검증 완료 **남은 항목:** 없음 ## 9. 수정 후 검증 기록 ### 1차 수정 검증 — 2026-08-20 - 무엇을: `REV-EVENT-LANG-001`의 익명 팝업 공개를 제거하고 기존 인증 경계를 복원했다. - 왜: 국가별 언어 필터와 무관한 보안 접근 범위 확장을 제거하기 위해서다. - 어떻게: - RED: `EventControllerIntegrationTest` 5개 중 익명 팝업 401만 실제 200으로 실패했고 인증 팝업·익명 목록 4개는 통과했다. - GREEN: `SecurityConfig`의 exact matcher 한 줄 제거 후 controller 통합 테스트 5개가 `BUILD SUCCESSFUL in 38s`로 통과했다. - 회귀: 직접 영향 5개 클래스 28개가 failures `0`, errors `0`, skipped `0`, `BUILD SUCCESSFUL in 42s`로 통과했다. - 정적 검증: `ktlintCheck`, `tasks --all`, `git diff --check HEAD`가 모두 성공했고 기준 HEAD 대비 `SecurityConfig` diff가 없다. - 독립 재리뷰: 수정 후 최종 diff와 문서를 다시 검토해 Critical·Important·Minor 모두 추가 발견 없음으로 판정했다. - 남은 항목: 운영 DB DDL 실제 반영은 기존 범위대로 미실행이며, 리뷰 확정 항목은 남아 있지 않다.