docs(gift): Phase 16 푸시와 가격 스냅샷 검증 결과를 기록한다
This commit is contained in:
@@ -4,12 +4,12 @@
|
||||
|
||||
| 문서 항목 | 내용 |
|
||||
|---|---|
|
||||
| 상태 | Phase 15 서버 구현·검증·리뷰 완료, 운영 반영 전 |
|
||||
| 상태 | Phase 16 서버 구현·검증·리뷰 완료, 운영 반영 전 |
|
||||
| 작성일 | 2026-09-29 |
|
||||
| 요구사항 기준 | `docs/20260929_크리에이터_선물하기/prd.md` |
|
||||
| API 기준 | `docs/20260929_크리에이터_선물하기/prd.md`의 `8. API 계약` |
|
||||
| 현재 Phase | Phase 15 전달 완료 배송 개인정보 정리 |
|
||||
| 현재 활성 Goal | 없음. `P15-GATE` 완료, 운영 반영은 별도 승인 |
|
||||
| 현재 Phase | Phase 16 누락 푸시와 가격 스냅샷 검증 |
|
||||
| 현재 활성 Goal | 없음. `P16-GATE` 완료, 운영 반영은 별도 |
|
||||
|
||||
## 목표
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
| 10 | 완료 | `3/3` | 완료 | 관리자 선물함 목록/상세 API 추가 |
|
||||
| 14 | 완료 | `4/4` | 완료 | 전체 테스트 제외, 운영 MySQL/배포/별도 프론트엔드 검증 범위 밖 |
|
||||
| 15 | 서버 구현·Gate 완료 | `4/4` Task, `P15-R1` 완료 | 없음 | 운영 schema/reader 배포 확인은 별도 |
|
||||
| 16 | 완료 | `2/2` Task, Gate 완료 | 없음 | 현재 test 브랜치, Phase16 커밋 승인·미배포 |
|
||||
|
||||
- 동시에 하나의 미완료 goal만 운용한다.
|
||||
- 구현 중 요구사항이 바뀌면 이 문서의 체크박스와 범위를 먼저 갱신한 뒤 코드를 수정한다.
|
||||
@@ -1967,3 +1968,81 @@ P15-T1 문서 검증 → 별도 구현 승인 → P15-T2 → P15-T3 → P15-T4
|
||||
- 검증 명령: `./gradlew --no-daemon test --rerun --tests '*GiftEntityMappingTest' --tests '*GiftSchedulerTest' --tests '*GiftQueryServiceTest' --tests '*GiftAdminQueryServiceTest' --tests '*GiftCommandServiceTest' --tests '*GiftAdminServiceTest' --tests '*GiftControllerTest' --tests '*AdminGiftControllerTest' ktlintCheck`.
|
||||
- 결과: exit 0, `BUILD SUCCESSFUL in 1m 18s`. 8개 클래스 229개 테스트 실패·오류·skip 0. 테스트는 재실행됐으며 컴파일과 lint는 동일 입력의 UP-TO-DATE 결과다. 실제 JWT HTTP 삭제 전후 계약, 개인정보 12개 null 저장·재실행 0건, 경합 및 자동취소 batch lock 회귀를 포함한다.
|
||||
- 범위: 기존 구현의 커밋 검증이므로 전체 테스트와 패키징 빌드는 반복하지 않았다. 2026-10-23 전체 2,396개 결과와 이번 focused 결과를 구분한다. 운영 MySQL DDL·배포·실제 Redis 다중 인스턴스 검증 및 push는 수행하지 않는다.
|
||||
|
||||
### Phase 16: 누락 발신자 푸시와 가격 스냅샷 검증
|
||||
|
||||
**Goal:** 수령확인·배송지 기한 초과 시 발신자 알림을 PRD대로 발행하고, 설정 변경이 기존 선물 신청과 결제를 바꾸지 않음을 실제 흐름으로 증명한다.
|
||||
|
||||
- 승인: 2026-10-26 사용자 후속 작업 진행 요청. 기존 확정 정책을 보완하며 재인터뷰하지 않는다. `test` 브랜치에서 worktree 없이 수행하고 커밋·푸시·운영 DB·실제 FCM 발송은 하지 않는다.
|
||||
- 기준: PRD `9.3`, `DEC-022`; 기존 완료 Task/검증 이력은 보존한다. 두 호출자의 누락이 원인이며 GiftPushService/FCM 기반을 새로 만들지 않는다.
|
||||
- 의존성: `P16-T1` → `P16-T2` → `P16-GATE`. 공유 controller test 때문에 한 구현 담당이 순서대로 실행하고, 변경 전체의 독립 리뷰는 Gate에서 수행한다.
|
||||
|
||||
#### Task 16.1 누락 발신자 푸시 보완
|
||||
|
||||
**Goal ID: `P16-T1`**
|
||||
|
||||
- 시작 조건: 승인된 기존 푸시 문구·대상·딥링크와 현재 누락 호출자 확인.
|
||||
- Files: `src/main/kotlin/kr/co/vividnext/sodalive/v2/gift/application/GiftCommandService.kt`, `src/main/kotlin/kr/co/vividnext/sodalive/v2/gift/adapter/out/scheduler/GiftScheduler.kt`.
|
||||
- Test: `src/test/kotlin/kr/co/vividnext/sodalive/v2/gift/GiftCommandServiceTest.kt`, `src/test/kotlin/kr/co/vividnext/sodalive/v2/gift/adapter/out/scheduler/GiftSchedulerTest.kt`, `src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/gift/adapter/in/web/AdminGiftControllerTest.kt`; 기존 `src/test/kotlin/kr/co/vividnext/sodalive/v2/gift/application/GiftPushServiceTest.kt` 재사용.
|
||||
- Interfaces: 기존 `confirmDelivery(...)` 및 `markExpiredRecipientAddressGiftsUndeliverable(now)` 성공 분기에 기존 send 메서드 호출만 추가한다. API·cron·잠금·상태/환불 정책 불변.
|
||||
- 범위 밖: 새 푸시 문구/알림종류, 직접 취소/관리자 취소 알림, 기한 계산이나 완료 허용 조건 변경.
|
||||
- 완료 증거: 발신자에게 정확한 applicationNo로 1회 호출, 수취인 추가 알림0, 거부/재실행의 추가 호출0, 기존 경합 및 rollback 회귀와 이벤트 계약 통과.
|
||||
- [x] **RED:** 수령확인의 stale 무푸시 기대를 정확한 발신자1회·추가0회로 교체하고 반복/거절·만료3상태/비대상/재실행 검증을 보강했다. 승인된 새 발행에 관련된 경합 기대값만 변경했다.
|
||||
- [x] **RED 확인:** `./gradlew --no-daemon test --tests '*GiftCommandServiceTest' --tests '*GiftSchedulerTest'` baseline102개 통과 후 production 수정 전103개 중5개가 실제 누락 호출 `WantedButNotInvoked`로 실패했다.
|
||||
- [x] **GREEN:** 상태 전이 성공 분기에 기존 `sendDeliveredToSender`, `sendUndeliverableToSender` 호출 두 줄만 추가했다. 공통 푸시/금융/잠금/cron/DTO는 불변이다.
|
||||
- [x] **GREEN 확인:** 관련4클래스168개 통과, 실제 JWT 수령확인 성공·권한 거부·반복 및 actual transaction scheduler 만료의 발신자1회·수취인0회·추가0을 확인했다. 기존 payload 테스트로 문구/딥링크를 대조했다.
|
||||
- [x] **REFACTOR:** 기존 취소·rollback·erasure no-push 검증과18개 경합을 유지했다. 최종 관련216개와 lint 통과, 독립 리뷰 승인. 아래 Progress에 실제 한계를 기록했다.
|
||||
|
||||
#### Task 16.2 기존 신청 스냅샷 보존 통합 테스트
|
||||
|
||||
**Goal ID: `P16-T2`**
|
||||
|
||||
- 시작 조건: `P16-T1` 통과. 가격/카테고리 production 동작은 이미 구현되어 있으므로 테스트를 위해 임의로 깨뜨리지 않는다.
|
||||
- Test: 기존 실제 command/admin/query/결제를 주입하는 `src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/gift/adapter/in/web/AdminGiftControllerTest.kt`의 fixture/TransactionTemplate/JWT HTTP 헬퍼를 재사용한다.
|
||||
- Interfaces: `registerGift`, `updateSizePrice`, `deleteCategory`, 기존 목록/상세 GET. 신규 endpoint/helper production 코드 없음.
|
||||
- 범위 밖: 가격/카테고리 정책 재설계, 새 dependency, 전체 fixture framework, 새 snapshot 필드.
|
||||
- 검증: 최초 판매가80으로 신청A→판매가120으로 변경→신청B. A의 저장·조회·결제80과 B의120을 flush/clear/reload 및 HTTP로 비교한다. 카테고리 비활성화 뒤 기존A/B와 결제는 유지, 옵션에서 제외, 신규 신청은 거부되며 추가 차감/행 생성이 없다.
|
||||
- [x] **검증 추가:** 실제 registerGift로 만든 A80/B120, 실제 가격 변경/카테고리 비활성화, DB reload와 JWT HTTP 목록/상세/옵션을 연결했다. 최초 기존가격 fixture 격리를 위해 클래스 초기화의 sizePrice 삭제 한 줄만 보강했다.
|
||||
- [x] **실행:** 단독 테스트와 관련 클래스 통과. 기존 가격 동작의 검증 보강이므로 임의 production RED는 만들지 않았다. 최초 테스트 끝의 Mockito matcher NPE는 fixture 검증 코드 오류로 구분하고 수정 후 같은 검증을 재실행했다. 가격 production은 변경하지 않았다.
|
||||
- [x] **정리:** 실제 UseCan80/120·원 결제 연결·정산·잔액100을 보존하고 비활성화 후 추가 Gift/차감0을 확인했다. 운영 데이터·실제 FCM·공유 설정은 건드리지 않았다.
|
||||
|
||||
#### Phase 16 Gate
|
||||
|
||||
**Goal ID: `P16-GATE`**
|
||||
|
||||
- 시작 조건: 두 Task의 실제 증거 확보. 완료 기준: 누락 알림과 가격/카테고리 시나리오 PASS, 관련/전체 회귀·lint/build·독립 리뷰 및 임시자원 정리.
|
||||
- [x] 관련6클래스216개 테스트/lint를 통과했고 최종 전체 실행에서도 해당 테스트를 포함해 통과했다. 아래 실제 명령을 기록했으며 별도 미실행 명령을 실행했다고 표시하지 않는다.
|
||||
- [x] 최초 전체 실행15분 timeout을 성공으로 계산하지 않았다. 상세 로그를 남긴20분 제한 재실행은18분22초에 exit0, 전체2400개·lint·build 통과했다.
|
||||
- [x] 실제 localhost HTTP로 가격/기존 신청 보존, 수령확인 푸시 경계와 만료 transaction을 검증했다. 실제 transport/기기 실수신은 미검증이다.
|
||||
- [x] 독립 reviewer APPROVE, 차단 사항 없음. PRD 가격/푸시 성공 기준2개만 새 증거로 완료 표시하고 과거 나머지 항목은 유지했다.
|
||||
- [x] `tasks --all`·diff 검사를 통과했고 전용 임시자원 삭제/부재를 확인했다. 기존 사용자 GradleDaemon과 공유캐시를 보존했으며 커밋·푸시는 하지 않았다.
|
||||
|
||||
#### Phase 16 Progress
|
||||
|
||||
- 2026-10-26: 기존 PRD에 따른 후속 작업을 승인받아 위 계획을 기록했다. P16-T1/T2/Gate는 아직 구현 검증 전이며 Phase15 기록은 보존했다.
|
||||
|
||||
#### P16-T1/T2 실행 결과 — 2026-10-26
|
||||
|
||||
- Baseline: `./gradlew --no-daemon test --tests '*GiftCommandServiceTest' --tests '*GiftSchedulerTest'` exit0(31초),102개 통과. 동일 명령 RED는103개 중5개 누락 호출 실패(52초), production 두 줄 수정 후 관련4클래스168개 통과(1분16초).
|
||||
- Snapshot: 실제 creator/member/Charge300 및 활성카테고리를 준비해 A80결제(잔액220)→가격120→B120결제(잔액100)→카테고리 비활성화를 검증했다. 실제 등록·결제·DB flush/clear/reload 및 TestRestTemplate JWT GET이다. 기존 신청 이름/가격/UseCan ID·정산 보존, 옵션 제외, 이후 신청 거부/추가차감0을 확인했다.
|
||||
- 단독 명령: `./gradlew --no-daemon test --tests '*AdminGiftControllerTest.shouldPreserveExistingGiftSnapshotsAfterPriceChangeAndCategoryDeactivation'` exit0(51초). 최초 테스트 matcher NPE는 업무결함 RED가 아니며 정확한80/120 호출 검증과 총2회 확인으로 수정했다.
|
||||
- 관련 최종 명령: `./gradlew --no-daemon test --tests '*GiftCommandServiceTest' --tests '*GiftSchedulerTest' --tests '*GiftPushServiceTest' --tests '*AdminGiftControllerTest' --tests '*GiftAdminServiceTest' --tests '*GiftQueryServiceTest' ktlintCheck` exit0(1분26초),51+52+11+55+28+19=216개,실패·오류·skip0. 최초 들여쓰기 lint2건은 수정 후 재실행했다.
|
||||
- 실표면: `P16_CONFIRM_HTTP status=200 success=true rejected=true repeated=false senderPush=1 recipientPush=0`, `P16_EXPIRY_DB transitioned=1 repeated=0 senderPush=1 recipientPush=0`. JWT 미인증401·발신자 수령확인 거부·재요청 거부, 원 결제/시각 보존을 함께 검증했다.
|
||||
- 이벤트 범위: 실제 HTTP/transaction 호출자에서 mock GiftPushService 경계의 정확한 호출을 확인하고 기존 GiftPushServiceTest에서 실제 FcmEvent payload를 검증했다. 실제 FCM 전송/기기 수신이나 외부 provider 성공을 증명한 것은 아니다.
|
||||
|
||||
#### P16-GATE 최종 검증 — 2026-10-26
|
||||
|
||||
- 전체1차: `./gradlew --no-daemon test --rerun ktlintCheck build`는900초 도구 제한으로 종료됐다. 로그는 `:test`에서 끝났고 실패 메시지는 없었으나 성공으로 계산하지 않았다. 해당 실행의 JVM 잔류 없음 확인 후 재실행했다.
|
||||
- 전체2차: `./gradlew --no-daemon test --rerun ktlintCheck build --info`를1200초 제한으로 실행해 exit0, `BUILD SUCCESSFUL in 18m 22s`. 최신 JUnit XML322개에서 테스트2400개,실패0·오류0·skip0 확인. lint/build도 성공했다.
|
||||
- 최신 실제 HTTP 증거: AdminGiftControllerTest55개 성공 XML에서 수령확인·만료 senderPush1/recipientPush0 및 `P16_SNAPSHOT_DB deactivated=false/true A=80 B=120 usageIdsPreserved=true actualUseCan=80,120 memberBalance=100 chargeBalance=100 gifts=2 usesDelta=2 chargesDelta=0 listCount=2`를 확인했다.
|
||||
- 리뷰: 최초 reviewer 결과 수집이 세션 소실로 실패해 승인 근거로 쓰지 않았다. 별도 read-only reviewer가 현재 diff/소스/테스트/보존로그를 대조해 범위 한정 APPROVE,차단 사항 없음으로 판정했고 보고서를 파일로 보존했다. reviewer가 통과를 확인한216개와 lead의 이후 전체2400개를 구분한다.
|
||||
- 검증 한계: LSP 도구가 없어 Kotlin 컴파일·ktlint로 대체했다. 기존 H2/비선물 SKIP LOCKED·duplicate JSON·Netty·Gradle 경고는 수정 범위 밖이며 새 테스트 실패는 없다. 운영 MySQL/실제 FCM/배포는 미실행이다. 기존 DataGrip·작업 전부터 있던 GradleDaemon은 사용자 자원으로 보존한다.
|
||||
- 최종 문서/자원: `./gradlew --no-daemon tasks --all` exit0(6초), `git diff --check` exit0, index 변경 없음, HEAD는 시작 시점의 `d052dd50` 유지. `build/libs/sodalive-0.0.1-SNAPSHOT.jar`와 최신 JUnit/HTML 산출물을 확인했다. 작업 root `gift-p16.xKuBMZ`의 열린 파일과 이번 JVM/Redis 잔류가 없음을 확인한 뒤 내부 캐시·임시 Tomcat·Redis·로그·메모를 제거하고 경로 부재를 확인했다. 작업 전08:44 시작한 기존 GradleDaemon PID25133과 공유 Gradle cache/build 산출물은 보존했다.
|
||||
- 최종 판정: `P16-T1`, `P16-T2`, `P16-GATE` 완료. 이번 production 수정은 호출 두 줄이며 가격/카테고리 코드는 불변이다. 변경은 현재 test 브랜치의 미커밋 상태로 남긴다.
|
||||
|
||||
#### Phase 16 커밋 전 재검증 — 2026-10-26
|
||||
|
||||
- 사용자 `/commit-with-validation` 요청으로 기존 Phase 16 변경의 커밋을 승인받았다. 위 미커밋 기록은 당시 실행 결과로 보존한다.
|
||||
- 검증 명령: `./gradlew --no-daemon test --rerun --tests '*GiftCommandServiceTest' --tests '*GiftSchedulerTest' --tests '*GiftPushServiceTest' --tests '*AdminGiftControllerTest' --tests '*GiftAdminServiceTest' --tests '*GiftQueryServiceTest' ktlintCheck`.
|
||||
- 결과: exit 0, `BUILD SUCCESSFUL in 1m 19s`. 최신 JUnit XML 6개에서 테스트 216개, 실패·오류·skip 0을 확인했다. 테스트는 재실행됐으며 컴파일과 lint는 동일 입력의 UP-TO-DATE 결과다.
|
||||
- 범위: 기존 변경의 커밋 검증이므로 관련 회귀를 재실행하고 전체 테스트·패키징 빌드는 반복하지 않았다. 위 전체 2,400개 결과는 이전 Gate 기록이며 이번 재실행 결과와 구분한다. 운영 MySQL·실제 FCM·배포는 미검증이다.
|
||||
|
||||
Reference in New Issue
Block a user