Files

63 lines
8.9 KiB
Markdown

# 일반 이벤트 상세 조회 최종 코드 리뷰
**판정: APPROVE. 확정 발견 사항 없음.**
## 1. 리뷰 정보와 범위
| 항목 | 내용 |
|---|---|
| 리뷰 대상 | P1-T1 구현 및 P1-GATE의 코드·검증 증거 |
| 기준 상태 | 현재 working tree의 신규 파일 직접 열람 및 SecurityConfig의 GET matcher |
| 리뷰 일자 / 리뷰어 | 2026-10-28 / Sisyphus-Junior |
| 기준 문서 | [prd.md](../prd.md) EVT-001~005, [plan-task.md](../plan-task.md) P1-T1·P1-GATE |
| 리뷰 상태 | 판정 완료. 호스트 중단 전 리뷰의 판정은 승계하지 않음 |
변경 내용: 익명 접근 가능한 `GET /api/v2/events/{id}`가 기존 목록의 노출 정책을 적용해 단일 `EventItem`을 반환한다. 관리자 상세나 추가 응답 필드는 만들지 않는다.
신규 controller/service/test, 보안 matcher와 공통 인증·예외 경로, 기존 event Service/Repository/DTO 및 인접 회귀 결과를 검토했다. 제품·테스트·계획 문서 수정, 테스트·빌드 재실행, 별도 HTTP 실행, 에이전트·커밋은 제외했다. 다중 사용자 요청을 전제로 단건 조회와 상태 변경 여부를 확인했으며 부하 측정은 하지 않았다.
## 2. 요구사항 대조
| 기준 | 판정 및 코드·테스트 근거 |
|---|---|
| EVT-001: 일반용 단건 GET만 추가 | [EventDetailController.kt:12](/Users/klaus/Develop/sodalive/Server/sodalive_/src/main/kotlin/kr/co/vividnext/sodalive/v2/api/event/adapter/in/web/EventDetailController.kt:12)의 단일 GET mapping, nullable principal, `ApiResponse.ok` 확인. 관리자 endpoint는 추가하지 않음. [EventDetailService.kt:25](/Users/klaus/Develop/sodalive/Server/sodalive_/src/main/kotlin/kr/co/vividnext/sodalive/v2/api/event/application/EventDetailService.kt:25)는 read-only이며 ID로 조회하고 전체 목록을 검색하지 않음. |
| EVT-002: 활성·기간·언어 | [EventDetailService.kt:31](/Users/klaus/Develop/sodalive/Server/sodalive_/src/main/kotlin/kr/co/vividnext/sodalive/v2/api/event/application/EventDetailService.kt:31)의 `startDate > now` / `endDate < now` 거부는 [EventRepository.kt:21](/Users/klaus/Develop/sodalive/Server/sodalive_/src/main/kotlin/kr/co/vividnext/sodalive/event/EventRepository.kt:21)의 `loe` / `goe`와 동일하게 양끝을 포함함. [EventService.kt:92](/Users/klaus/Develop/sodalive/Server/sodalive_/src/main/kotlin/kr/co/vividnext/sodalive/event/EventService.kt:92)와 같은 country resolver로 JP→JA, 그 외→KO. 활성·기간·언어 검사는 관리자 분기보다 먼저 수행됨. |
| EVT-002: 성인 3상태 및 관리자 우회 | [EventDetailService.kt:38](/Users/klaus/Develop/sodalive/Server/sodalive_/src/main/kotlin/kr/co/vividnext/sodalive/v2/api/event/application/EventDetailService.kt:38)는 실제 auth 기록으로 판단함. 익명·미인증은 null/false, 인증 일반 회원은 null/true만 허용하므로 인증 성인에게 false를 노출하지 않음. 관리자는 성인 조건만 생략함. [EventService.kt:56](/Users/klaus/Develop/sodalive/Server/sodalive_/src/main/kotlin/kr/co/vividnext/sodalive/event/EventService.kt:56)와 [EventRepository.kt:26](/Users/klaus/Develop/sodalive/Server/sodalive_/src/main/kotlin/kr/co/vividnext/sodalive/event/EventRepository.kt:26)에 일치. 신규 테스트의 KO/JA × 3상태 × 4종 viewer 비교 및 관리자 비노출 검증 확인. |
| EVT-003: 정확히 5개 필드·이미지·link | [EventDetailService.kt:45](/Users/klaus/Develop/sodalive/Server/sodalive_/src/main/kotlin/kr/co/vividnext/sodalive/v2/api/event/application/EventDetailService.kt:45)는 [GetEventResponse.kt:13](/Users/klaus/Develop/sodalive/Server/sodalive_/src/main/kotlin/kr/co/vividnext/sodalive/event/GetEventResponse.kt:13)의 `id`, `thumbnailImageUrl`, `detailImageUrl`, `popupImageUrl`, `link`만 반환함. HTTPS 보존, 그 외 CDN 접두사는 기존 Service와 같고 nullable 이미지·link를 보존함. 외부 link에 HTTP 요청·리다이렉트하지 않으며 엔티티를 수정하지 않음. |
| EVT-004·005: 실패 envelope·보안 경계 | 양수 아님·미존재·비노출은 같은 `SodaException(common.error.invalid_request)`를 사용하며 [SodaExceptionHandler.kt:26](/Users/klaus/Develop/sodalive/Server/sodalive_/src/main/kotlin/kr/co/vividnext/sodalive/common/SodaExceptionHandler.kt:26)의 HTTP 200 실패 envelope 유지. 비숫자·Long 초과는 기존 바인딩·공통 오류 처리 유지. [SecurityConfig.kt:191](/Users/klaus/Develop/sodalive/Server/sodalive_/src/main/kotlin/kr/co/vividnext/sodalive/configs/SecurityConfig.kt:191)는 GET과 단일 segment `*`만 허용하며 쓰기·collection·추가 하위 경로·팝업·관리자 경로는 기존 인증 규칙 적용. JWT 필터를 생략하는 설정이 아니므로 기존 잘못된 JWT 처리 경로도 유지됨. |
핵심 테스트: [EventDetailControllerIntegrationTest.kt:74](/Users/klaus/Develop/sodalive/Server/sodalive_/src/test/kotlin/kr/co/vividnext/sodalive/v2/api/event/EventDetailControllerIntegrationTest.kt:74)의 응답 완전 비교·DB 재조회, `:165`의 성인 행렬, `:212`의 관리자 비노출, `:244`의 ID 오류, `:271`의 인접 보안 검증을 읽었다. RANDOM_PORT/TestRestTemplate은 실제 HTTP, 인증 MockMvc는 동일 컨텍스트와 실제 H2 member/auth/event fixture를 사용한다. 서비스·repository·인증 기록을 mock으로 대체하지 않으며 S3Uploader만 mock이다.
## 3. 검토한 실행 증거
아래는 기존 실행 결과의 확인이며 이 리뷰에서 실행한 테스트가 아니다.
| 출처 | 결과 및 확인 범위 |
|---|---|
| [신규 suite XML:2](/Users/klaus/Develop/sodalive/Server/sodalive_/build/test-results/test/TEST-kr.co.vividnext.sodalive.v2.api.event.EventDetailControllerIntegrationTest.xml:2) 직접 열람 | 2026-10-28T10:19:56, 28 tests, failures/errors/skipped 모두 0. testcase 목록과 실제 HTTP·MockMvc `system-out` 확인. |
| 같은 XML의 실제 응답 직접 열람 | `:10378` 익명 200·정확한 5필드·nullable 이미지·외부 link 보존. `:6484`, `:7407` 인증 일반 회원의 false 이벤트 거부. `:13745` 이후 관리자 비활성·미래·종료·언어 불일치 모두 동일 실패. `:10026`~`:10326`의 인접 보안 13건 모두 401. `:4568` 양수 아닌 ID도 HTTP 200, `success=false`, `data=null`, 기존 메시지 확인. |
| 기존 회귀 XML 직접 열람 | `EventControllerIntegrationTest` 5, `EventRepositoryTest` 5, `EventServiceTest` 4, `AdminEventBannerControllerIntegrationTest` 4건. 각 `build/test-results/test/TEST-kr.co.vividnext.sodalive.<클래스 전체 이름>.xml`의 testsuite/testcase를 확인했으며 합계 18건 모두 failures/errors/skipped 0. |
| 부모 세션 제공 전체 suite 증거 | `./gradlew test --console=plain --no-daemon -Pkotlin.compiler.execution.strategy=in-process`: BUILD SUCCESSFUL, 19m 29s, 326 suites / 2584 tests / failures/errors/skips 0. 전체 집계는 부모 제공 증거이며 이 리뷰에서 재집계하지 않음. |
| 부모 세션 제공 빌드·린트 증거 | `bootJar` 성공, `ktlintCheck`는 CsvSource 줄바꿈 수정 후 성공. Kotlin production/test compile 성공을 대체 진단 증거로 사용하며 LSP 성공으로 표현하지 않음. |
## 4. 발견 사항과 검증 한계
**확정 발견 사항 없음.** 승인된 EVT-001~005를 위반하는 구체 사례나 차단할 회귀를 발견하지 못했다. 회귀 수정 Task/goal 전환 항목 없음.
- 정확히 같은 시각의 기간 경계는 고정 시계 실행 테스트가 아니라 기존 QueryDSL 포함 비교와 신규 거부 조건의 코드 대조로 확인했다. 계획에서 허용한 검증 방식이며 현재 비교 연산에 결함은 없다.
- 인증 viewer는 JWT 발급·전송을 포함한 별도 실제 HTTP가 아니라 인증 MockMvc와 실제 DB fixture로 검증됐다. 이는 승인된 S2 검증 표면이며 기존 JWT 필터 경로는 코드로 확인했다.
- `lsp_diagnostics`는 부모와 이 리뷰 환경에 도구가 없어 미실행이다. 이는 진단 도구의 제한이지 확인된 코드 결함이 아니다. 테스트·빌드·린트 성공은 위에 출처를 구분해 기록했으며 재실행하지 않았다.
## 5. 리뷰 종료 판정과 정리
| 항목 | 결과 |
|---|---|
| 승인 요구사항·구현·보안·테스트 범위 확인 | 충족 |
| 발견 사항 판정 / plan 전환 | 확정 발견 사항 없음 / 해당 없음 |
| 실행 증거와 미검증 부분 구분 | 충족 |
| 최종 결론 | **APPROVE**. 이 구현의 반영을 차단할 항목 없음 |
이 판정은 구현과 제공된 증거에 대한 승인이다. 부모 소유의 P1-GATE 체크박스·상단 상태나 부모 QA 자원의 최종 정리 상태를 대신 갱신·인증하지 않는다.
**Teardown Receipt:** 이 리뷰에서 shell·테스트·빌드·서버·에이전트를 실행하지 않았고 소유 임시 root/프로세스를 생성하지 않았다. 정리 대상 0, 남은 리뷰 소유 자원 0. 공유 캐시 삭제 없음. 저장소 변경은 이 리뷰 파일뿐이다.