|
|
|
@@ -0,0 +1,88 @@
|
|
|
|
|
# 이벤트 수정 누락 필드 보존 구현 계획
|
|
|
|
|
|
|
|
|
|
## 목표와 상태
|
|
|
|
|
- 작성일: 2026-11-06
|
|
|
|
|
- 요구사항: [prd.md](prd.md)
|
|
|
|
|
- 상태: 기능 범위 구현 및 검증 완료
|
|
|
|
|
- 현재 활성 Goal: 없음
|
|
|
|
|
- 기술: Kotlin 1.6.21, Java 17, Spring Boot 2.7.14, JUnit 5, MockMvc, H2.
|
|
|
|
|
- 구조: 기존 두 서비스의 대입 조건에 null 가드만 추가한다. API 시그니처와 명시값 처리 방식은 바꾸지 않는다.
|
|
|
|
|
- 실행: subagent-driven-development. 기존 작업 디렉터리를 사용하고 worktree는 생성하지 않는다.
|
|
|
|
|
|
|
|
|
|
### Phase 1: 두 API의 누락 값 보존
|
|
|
|
|
|
|
|
|
|
- [x] **Task 1.1: 요청 누락 회귀 테스트와 최소 수정 (P1-T1)**
|
|
|
|
|
|
|
|
|
|
**Objective:** 두 이벤트 수정 API가 누락된 link/isAdult를 보존하면서 명시값은 기존대로 저장한다.
|
|
|
|
|
- 시작 조건: EVENT-001~005 확정, 기존 코드·테스트 확인 완료.
|
|
|
|
|
- 완료 증거: RED assertion 실패, GREEN 통합 테스트 통과, DB 재조회 검증.
|
|
|
|
|
- 범위 밖: 다른 필드 처리 변경, 공개 스키마 변경, 운영 데이터 접근.
|
|
|
|
|
|
|
|
|
|
**Files:**
|
|
|
|
|
- `src/main/kotlin/kr/co/vividnext/sodalive/event/EventService.kt`
|
|
|
|
|
- `src/main/kotlin/kr/co/vividnext/sodalive/admin/event/banner/AdminEventBannerService.kt`
|
|
|
|
|
- `src/test/kotlin/kr/co/vividnext/sodalive/event/EventControllerIntegrationTest.kt`
|
|
|
|
|
- `src/test/kotlin/kr/co/vividnext/sodalive/admin/event/banner/AdminEventBannerControllerIntegrationTest.kt`
|
|
|
|
|
|
|
|
|
|
- [x] **RED:** 기존 통합 테스트에 관리자 인증 PUT 요청과 DB 재조회 assertion을 추가한다.
|
|
|
|
|
- [x] **RED 확인:** 아래 focused 명령으로 기존 link/isAdult가 null로 바뀌어 실패하는지 확인한다.
|
|
|
|
|
- [x] **GREEN:** 두 서비스의 link/isAdult 대입 조건에 각 인자 `!= null` 가드를 추가한다.
|
|
|
|
|
- [x] **GREEN 확인:** 동일 focused 테스트에서 모든 시나리오가 통과한다.
|
|
|
|
|
- [x] **REFACTOR:** 불필요한 리팩터링 없이 기존 테스트를 포함한 직접 영향 회귀를 확인한다.
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
export TMPDIR=/var/folders/yh/8xsbvpsj5wg2qnxzxdp11_gm0000gn/T/opencode/event-omission.qeHSia/implementer.ZukrpN
|
|
|
|
|
export NODE_COMPILE_CACHE=$TMPDIR/node-compile-cache BUN_RUNTIME_TRANSPILER_CACHE_PATH=$TMPDIR/bun-transpiler
|
|
|
|
|
./gradlew test --no-daemon --tests 'kr.co.vividnext.sodalive.event.EventControllerIntegrationTest' --tests 'kr.co.vividnext.sodalive.admin.event.banner.AdminEventBannerControllerIntegrationTest'
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**P1-T1 검증 결과 (2026-11-06):**
|
|
|
|
|
- RED: production 변경 전 위 명령 exit 1, 2분 23초. 23개 중 10개 assertion 실패, error/skip 0. 일반 클래스 12개 중 5개, 관리자 클래스 11개 중 5개 실패했다. 각 API의 제목만 수정한 true/false/null 3개 사례에서 기존 링크 대신 null이 저장됐으며 true/false도 null이 됐다. 빈 문자열/공백 링크 2개 사례에서는 링크의 기존 처리 방식은 맞았지만 생략한 isAdult가 true 대신 null이 됐다. 기존 테스트 9개와 명시값 변경 4개는 통과했다.
|
|
|
|
|
- GREEN: 네 비교문에만 `link != null &&`, `isAdult != null &&`를 추가한 뒤 동일 명령 exit 0, 3분 35초. 일반 12개와 관리자 11개, 총 23개 모두 통과하고 failure/error/skip은 0이었다. 기존 테스트를 삭제·변경하지 않았고 리팩터링하지 않았다.
|
|
|
|
|
- S1~S3는 실제 컨트롤러·보안·서비스와 기존 S3 mock을 사용했다. 모든 요청의 HTTP 200 및 `success=true`를 확인한 뒤 별도 repository 호출로 H2를 재조회했다. 제목만 수정하는 사례는 이미지 3종·isPopup·기간·lang·isActive까지 Event 스냅샷으로 완전 비교했다. 명시값은 Boolean을 반대 값에서 true/false로 각각 변경했고, 빈 문자열/공백 링크는 일반 API의 원문 저장과 관리자 API의 null 저장을 확인했다. 시각은 `2099-01-01T00:00:00`, `2099-01-02T23:59:59`로 고정했다.
|
|
|
|
|
- 임시 RED/GREEN 보고서: `/var/folders/yh/8xsbvpsj5wg2qnxzxdp11_gm0000gn/T/opencode/event-omission.qeHSia/implementer.ZukrpN/p1-t1-red-green.md`. GREEN XML에서도 각 클래스의 test/failure/error/skip 수를 확인했다. LSP 진단 도구가 제공되지 않아 실행하지 못했으며 Gradle의 production/test Kotlin 컴파일이 성공했다. 변경 파일의 컴파일 경고는 없고, 기존 다른 파일의 불필요한 `!!`·unchecked cast·미사용 인자 및 Gradle deprecation 경고는 남아 있다.
|
|
|
|
|
- 한계: MockMvc/H2 통합 검증이며 실제 네트워크·운영 MySQL 검증은 아니다. 전체 테스트는 미실행이며 사용자 실행 명령은 `./gradlew test`이다. QA·변경 범위 lint·빌드·리뷰·임시 리소스 정리는 상위 에이전트의 P1-GATE에 남겼다. 커밋·스테이징·푸시와 운영 앱/서비스 실행은 하지 않았다.
|
|
|
|
|
|
|
|
|
|
## 시나리오 계약
|
|
|
|
|
| 시나리오 | PASS 조건 | 검증 표면 |
|
|
|
|
|
|---|---|---|
|
|
|
|
|
| S1 누락 보존 | 제목만 수정해도 link와 isAdult=true/false/null 및 다른 생략 필드가 DB 재조회에서 유지된다. | 두 실제 컨트롤러의 MockMvc PUT + H2 |
|
|
|
|
|
| S2 명시값 반영 | 새 링크, isAdult=true/false가 저장되고 응답 success=true이다. | 두 컨트롤러 + H2 |
|
|
|
|
|
| S3 빈값 호환 | 빈 문자열·공백 link는 일반 API에서 원문, 관리자 API에서 null로 저장된다. isAdult 생략값은 유지된다. | 두 컨트롤러 + H2 |
|
|
|
|
|
| S4 인접 회귀 | 기존 이벤트 조회·언어·생성 테스트가 통과한다. | 기존 focused 테스트 |
|
|
|
|
|
|
|
|
|
|
- [x] **Phase Gate (P1-GATE): 검증 및 리뷰**
|
|
|
|
|
- 시작 조건: P1-T1 완료.
|
|
|
|
|
- 완료 증거: focused 회귀, `./gradlew bootJar`, 변경 diff 리뷰, 검증 한계 기록, 임시 리소스 정리.
|
|
|
|
|
- 범위 밖: 전체 테스트 자동 실행, 관련 없는 lint 수정, 커밋/푸시.
|
|
|
|
|
- TDD 예외 사유: Gate는 구현이 아닌 검증 단계이다.
|
|
|
|
|
- 대체 검증: 실제 HTTP QA가 가능한 로컬 테스트 환경이면 endpoint 응답과 DB 결과를 확인한다. 불가하면 MockMvc와 실제 네트워크 검증을 구분해 기록한다.
|
|
|
|
|
- LSP 도구가 제공되지 않으면 그 사실을 명시하고 Kotlin 컴파일과 변경 범위 lint를 대체 증거로 남긴다.
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
./gradlew test --tests 'kr.co.vividnext.sodalive.event.*' --tests 'kr.co.vividnext.sodalive.admin.event.banner.*'
|
|
|
|
|
./gradlew bootJar
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## 종료 조건
|
|
|
|
|
S1~S4의 관찰 가능한 동작과 focused 테스트가 통과하고, 빌드·변경 리뷰·임시 리소스 정리가 완료되면 즉시 종료한다. 전체 테스트는 저장소 규칙에 따라 사용자가 `./gradlew test`로 실행하며, 미검증 범위를 명시한다.
|
|
|
|
|
|
|
|
|
|
## 검증 기록
|
|
|
|
|
- 초기 조사: 두 서비스에서 link/isAdult만 누락 null을 대입하고, 기존 세 테스트 클래스에 update 검증이 없음을 확인했다.
|
|
|
|
|
- 계획 에이전트 후속 호출은 timeout으로 종료됐다. 기존 조사와 승인된 요구사항으로 위 최소 변경을 확정했다.
|
|
|
|
|
|
|
|
|
|
### P1-GATE 검증 결과 (2026-11-06)
|
|
|
|
|
- 실제 HTTP QA: 임시 `EventOmissionHttpQaTest`를 별도 Gradle init script로 테스트 소스에 연결했다. `./gradlew --no-daemon --console=plain -I "$TMPDIR/http-qa.init.gradle" test --tests 'kr.co.vividnext.sodalive.event.EventOmissionHttpQaTest'` exit 0. RANDOM_PORT 서버와 TestRestTemplate, 실제 TokenProvider 관리자 JWT, H2·embedded Redis를 사용했다. MockMvc 요청으로 대체하지 않았다.
|
|
|
|
|
- 실제 네트워크: 두 경로 각각 기존 성인 여부 true/false/null에서 제목만 수정, 링크만 수정, isAdult=true, isAdult=false, 빈 링크, 공백 링크를 차례로 전송했다. 총 36개 PUT 모두 HTTP 200, `{"success":true,"message":"수정되었습니다.","data":{},"errorProperty":null}` 응답이며, 6개 조합 모두 DB 재조회 assertion을 통과했다. 제목 외 필드 보존·독립 필드 수정·빈값 호환을 확인했다.
|
|
|
|
|
- 1차 Gate: `./gradlew --no-daemon --console=plain test --tests 'kr.co.vividnext.sodalive.event.*' --tests 'kr.co.vividnext.sodalive.admin.event.banner.*' ktlintCheck bootJar --continue` exit 0, 1분 30초. 저장소 39개 및 앞선 임시 QA 컴파일 클래스 2개가 실행됐다. `ktlintCheck` 실제 실행 통과, `bootJar` 생성 및 `build/libs/sodalive-0.0.1-SNAPSHOT.jar` 확인.
|
|
|
|
|
- 임시 QA 컴파일 클래스가 일반 실행에도 남은 것을 확인해 해당 소유 class 파일만 제거했다. 이후 임시 init script 없이 아래 명령으로 저장소 테스트만 재실행했다.
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
./gradlew --no-daemon --console=plain test --rerun --tests 'kr.co.vividnext.sodalive.event.*' --tests 'kr.co.vividnext.sodalive.admin.event.banner.*' ktlintCheck bootJar --continue
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
- 최종 결과: exit 0, 2분 57초. XML 5개에서 일반 컨트롤러 12 + 관리자 컨트롤러 11 + 언어 통합 7 + 저장소 5 + 서비스 4 = **39개**, 실패·오류·skip 모두 0. XML 시각은 `2026-11-06T04:14:52`~`04:15:19`. 임시 QA class는 최종 결과에 없다. Kotlin test 컴파일 및 테스트는 실제 실행됐고, 동일 입력의 lint·bootJar는 직전 통과 결과를 재사용한 UP-TO-DATE였다.
|
|
|
|
|
- 독립 리뷰: `bg_8220e32c`가 PRD EVENT-001~005, 실제 diff, 연결 코드와 테스트, HTTP/DB 원본을 대조해 **APPROVE**. 차단 사항 없음. [리뷰 기록](reviews/event-omission-review.md).
|
|
|
|
|
- 진단: LSP 도구가 제공되지 않아 미실행이며 Kotlin 컴파일·ktlintCheck로 대체했다. `git diff --check` 통과. 기존 무관 코드 경고와 테스트 context 종료 경고는 이번 수정 범위 밖이다.
|
|
|
|
|
- 검증 한계: 전체 테스트와 운영 MySQL·실제 관리자 UI는 미검증이다. 전체 검증은 사용자가 `./gradlew test`로 실행한다. 운영 데이터 복원·접속 및 커밋·푸시는 하지 않았다.
|
|
|
|
|
- 정리: Gradle wrapper/daemon/test worker·embedded Redis 프로세스가 남아 있지 않음을 확인했다. 이번 작업 소유 `event-omission.qeHSia` 루트와 두 자식 루트, 임시 HTTP QA 소스·init script·로그를 삭제했고, 루트·임시 class·임시 XML 부재 검사 `CLEANUP_PASS`를 확인했다. 앞서 기록된 임시 보고서 경로는 실행 당시 경로이며 현재는 위 문서와 리뷰에 요약 근거를 보존한다. 공유 캐시·기존 worktree·운영 리소스는 건드리지 않았다.
|