Files
sodalive-backend-spring-boot/docs/20261028_일반이벤트상세조회/reviews/event-detail-review.md
T

8.9 KiB

일반 이벤트 상세 조회 최종 코드 리뷰

판정: APPROVE. 확정 발견 사항 없음.

1. 리뷰 정보와 범위

항목 내용
리뷰 대상 P1-T1 구현 및 P1-GATE의 코드·검증 증거
기준 상태 현재 working tree의 신규 파일 직접 열람 및 SecurityConfig의 GET matcher
리뷰 일자 / 리뷰어 2026-10-28 / Sisyphus-Junior
기준 문서 prd.md EVT-001~005, 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의 단일 GET mapping, nullable principal, ApiResponse.ok 확인. 관리자 endpoint는 추가하지 않음. EventDetailService.kt:25는 read-only이며 ID로 조회하고 전체 목록을 검색하지 않음.
EVT-002: 활성·기간·언어 EventDetailService.kt:31의 startDate > now / endDate < now 거부는 EventRepository.kt:21의 loe / goe와 동일하게 양끝을 포함함. EventService.kt:92와 같은 country resolver로 JP→JA, 그 외→KO. 활성·기간·언어 검사는 관리자 분기보다 먼저 수행됨.
EVT-002: 성인 3상태 및 관리자 우회 EventDetailService.kt:38는 실제 auth 기록으로 판단함. 익명·미인증은 null/false, 인증 일반 회원은 null/true만 허용하므로 인증 성인에게 false를 노출하지 않음. 관리자는 성인 조건만 생략함. EventService.kt:56와 EventRepository.kt:26에 일치. 신규 테스트의 KO/JA × 3상태 × 4종 viewer 비교 및 관리자 비노출 검증 확인.
EVT-003: 정확히 5개 필드·이미지·link EventDetailService.kt:45는 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의 HTTP 200 실패 envelope 유지. 비숫자·Long 초과는 기존 바인딩·공통 오류 처리 유지. SecurityConfig.kt:191는 GET과 단일 segment *만 허용하며 쓰기·collection·추가 하위 경로·팝업·관리자 경로는 기존 인증 규칙 적용. JWT 필터를 생략하는 설정이 아니므로 기존 잘못된 JWT 처리 경로도 유지됨.

핵심 테스트: 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 직접 열람 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. 공유 캐시 삭제 없음. 저장소 변경은 이 리뷰 파일뿐이다.