diff --git a/docs/20260929_크리에이터_선물하기/plan-task.md b/docs/20260929_크리에이터_선물하기/plan-task.md index 0305810d..360372d5 100644 --- a/docs/20260929_크리에이터_선물하기/plan-task.md +++ b/docs/20260929_크리에이터_선물하기/plan-task.md @@ -9,7 +9,7 @@ | 요구사항 기준 | `docs/20260929_크리에이터_선물하기/prd.md` | | API 기준 | `docs/20260929_크리에이터_선물하기/prd.md`의 `8. API 계약` | | 현재 Phase | Phase 14 관리자 선물 취소 | -| 현재 활성 Goal | 없음, `P14-GATE` 완료 | +| 현재 활성 Goal | 없음, `P14-T4` 시각 정밀도 회귀 수정 완료 | ## 목표 @@ -41,7 +41,7 @@ | 8 | 완료 | `1/1` | 완료 | 없음 | | 9 | 완료 | `3/3` | 완료 | 없음 | | 10 | 완료 | `3/3` | 완료 | 관리자 선물함 목록/상세 API 추가 | -| 14 | 완료 | `3/3` | 완료 | 전체 테스트 제외, 운영 MySQL/배포/별도 프론트엔드 검증 범위 밖 | +| 14 | 완료 | `4/4` | 완료 | 전체 테스트 제외, 운영 MySQL/배포/별도 프론트엔드 검증 범위 밖 | - 동시에 하나의 미완료 goal만 운용한다. - 구현 중 요구사항이 바뀌면 이 문서의 체크박스와 범위를 먼저 갱신한 뒤 코드를 수정한다. @@ -1587,6 +1587,30 @@ P7은 FCM 확장 후 전체 상태 전이를 연결하고 최종 회귀로 종 - 결과: 관련 5개 클래스 124개 테스트에서 실패·오류·skip 0. lint는 UP-TO-DATE이며 `git diff --check`도 통과했다. - 범위: 관리자 취소와 직접 영향받는 사용자 명령·스케줄러 회귀를 재실행했다. 기존 사용자 요청에 따라 전체 테스트는 제외했고 운영 MySQL 검증은 수행하지 않았다. +### Phase 14 후속 회귀 수정 + +#### Task 14.4 관리자 취소 시각 정밀도 회귀 수정 + +**Goal 실행 `P14-T4`:** 관리자 취소의 DB 재조회 `canceledAt`과 응답 `occurredAt`을 OS/JDK 시계 정밀도와 무관하게 일치시킨다. + +- **시작 조건:** Jenkins의 `AdminGiftControllerTest` 458/526행 실패 보고 및 정밀도 정규화 수정 승인. H2 2.1.214 `timestamp`에 나노초 입력 시 마이크로초 반올림을 별도 JDBC로 재현했다. +- **정책:** 운영 DDL `canceled_at TIMESTAMP`에 맞춰 초 단위 절삭 후 저장·응답에 같은 값을 사용한다. 테스트 비교를 완화하거나 전역 `toUtcIso`, DDL, 사용자/자동취소 동작을 변경하지 않는다. +- **Files:** Modify `src/main/kotlin/kr/co/vividnext/sodalive/v2/gift/application/GiftAdminService.kt`, `src/test/kotlin/kr/co/vividnext/sodalive/v2/gift/application/GiftAdminServiceTest.kt`; Verify `src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/gift/adapter/in/web/AdminGiftControllerTest.kt`; Update 기존 `prd.md`, `plan-task.md`. +- **완료 증거:** 고정 나노초·초 경계 입력의 DB flush/clear 재조회 테스트, 기존 실패 두 HTTP 테스트를 포함한 focused test, lint/테스트 제외 빌드, 변경 리뷰 통과. +- **범위 밖:** 전체 테스트(기존 사용자 제외 요청 유지), 커밋/푸시, 운영 DB/CI 설정 변경. +- [x] **RED:** `0`, `123456789`, `999999999` 나노초 입력을 사용해 초 단위 기대값과 DB 재조회/응답의 완전 일치를 검증하고 기존 코드의 실패를 확인한다. +- [x] **GREEN:** 관리자 취소 메서드에서 취소 시각을 한 번 초 단위로 정규화하고 저장·응답에 사용한다. +- [x] **REFACTOR/검증:** `./gradlew --no-daemon test --tests '*GiftAdminServiceTest' --tests '*AdminGiftControllerTest' ktlintCheck` 및 `./gradlew --no-daemon build -x test`를 실행한다. 기존 458/526행 assertion과 실제 JWT HTTP 검증은 유지한다. + +#### P14-T4 진행 기록 — 2026-10-22 + +- RED: 신규 parameterized DB roundtrip 테스트 3개 중 2개 실패(exit 1, 42초). `23:59:59.123456789`는 DB에서 `.123457`로, `23:59:59.999999999`는 다음 날 `00:00`으로 반올림되어 응답과 달랐다. 나노초 0 입력은 통과했다. +- GREEN: 관리자 취소의 `now`를 `ChronoUnit.SECONDS`로 한 번 절삭해 저장과 결과에 같은 값을 사용했다. `./gradlew --no-daemon test --tests '*GiftAdminServiceTest' --tests '*AdminGiftControllerTest' ktlintCheck` exit 0(1분 26초), service 28 + controller 31 = 59개 실패·오류·skip 0. +- 실제 HTTP: `shouldCancelReceivedGiftThroughRealJwtHttpAndRequeryActions`에서 body 없는 POST가 `success=true`, `status=CANCELED`, `occurredAt=2026-10-22T19:05:41Z`를 반환하고 DB 재조회값과 정확히 일치했다. Jenkins에서 실패한 기존 두 assertion과 환불·경합 검증은 변경하지 않았다. +- 빌드/문서: `./gradlew --no-daemon build -x test tasks --all` exit 0(14초). 전체 테스트는 기존 사용자 제외 요청을 유지해 실행하지 않았다. 커밋/푸시 없음. +- 한계: Jenkins 재실행과 실제 운영 DB 검증은 하지 않았다. CI 원본 expected/actual은 제공되지 않았으며 로컬 고정 나노초 입력으로 해당 실패 메커니즘을 재현·검증했다. LSP 도구가 없어 Kotlin 컴파일·ktlint로 대체했다. +- 최종 판정: 독립 변경 리뷰 APPROVE, 차단 사항 없음. 기존 HTTP 458/526행의 완전 일치 검증과 전역 날짜 변환/DDL/환불 로직이 변경되지 않았음을 확인했다. `git diff --check` 통과, 후속 커밋 없음. + ### Phase 14 실행 순서와 의존성 | 순서 | Goal | 선행조건 | 병행 가능 | 차단 시 다음 행동 | @@ -1595,6 +1619,7 @@ P7은 FCM 확장 후 전체 상태 전이를 연결하고 최종 회귀로 종 | 32 | `P14-T2` | `P14-T1` 검증 및 사용자 구현 승인 | 아니요 | 승인 대기, 코드 변경 금지 | | 33 | `P14-T3` | `P14-T2` 계약/승인 | 아니요 | 선행 구현 승인 대기 | | 34 | `P14-GATE` | `P14-T2`, `P14-T3` | 아니요 | 실패 원인 Task에 반영 | +| 35 | `P14-T4` | Jenkins 정밀도 불일치 후속 수정 승인 | 아니요 | DB roundtrip 실패 원인 확인 | ```text P14-T1 문서 검증 → 사용자 구현 승인 → P14-T2 → P14-T3 → P14-GATE diff --git a/docs/20260929_크리에이터_선물하기/prd.md b/docs/20260929_크리에이터_선물하기/prd.md index 1afd3713..797c90df 100644 --- a/docs/20260929_크리에이터_선물하기/prd.md +++ b/docs/20260929_크리에이터_선물하기/prd.md @@ -903,6 +903,8 @@ Response `data`는 기존 `AdminGiftOperationStatusResponse`를 사용한다. `occurredAt`은 `canceledAt`과 같은 UTC ISO `Z` 문자열이다. 누락되거나 취소할 수 없는 상태의 신청번호는 기존 `SodaException(messageKey = "common.error.invalid_request")`로 거부한다. 정상 `ApiResponse` envelope를 사용하며 신규 오류 스키마나 HTTP status 정책은 추가하지 않는다. +관리자 취소 시각은 `gift-schema.sql`의 `canceled_at TIMESTAMP` 정밀도에 맞춰 초 단위로 절삭한 단일 값을 저장과 응답에 사용한다. OS/JDK 시계가 나노초를 반환해도 DB 재조회 시각과 응답 시각은 일치해야 한다. 전역 날짜 변환 및 다른 상태 변경의 시각 정책은 변경하지 않는다. + 이 관리자 취소는 기존 사용자 직접 취소와 운송장 미등록 자동취소를 변경하지 않는다. 취소 전용 푸시는 추가하지 않으며, 자동취소 푸시 문구나 자동취소 사유를 관리자 취소에 재사용하지 않는다. ### 8.15 자동 스케줄링 작업