# 무료 콘텐츠 포인트 결제 불가 구현 계획 | 문서 항목 | 내용 | |---|---| | 상태 | 리뷰 후속 작업 완료 | | 작성일 | 2026-07-31 | | 요구사항 기준 | `docs/20260731_무료_콘텐츠_포인트_결제_불가/prd.md` | | API 기준 | PRD `8. API 계약` | | 현재 Phase | Phase 1 회귀 수정 완료 | | 현재 활성 Goal | 없음 | ## 목표 무료 오디오 콘텐츠가 소비자 조회에서 포인트 결제 가능으로 노출되거나 POINT 전용 목록에 포함되지 않게 한다. ## 구현 방식 - 응답 DTO의 공개 필드와 내부 조회 record는 변경하지 않는다. - 각 소비자 응답 조립 지점에서 `isPointAvailable && price > 0`을 적용한다. - 추천과 전체 탭 POINT repository 조건에 `price > 0`을 추가한다. - 관리자 mapper와 저장 로직은 수정하지 않는다. - 단일 식 적용을 위한 새 공통 abstraction이나 dependency는 만들지 않는다. ## 현재 상태 | Phase | 상태 | 완료 Task | 활성/다음 Goal | 차단 또는 남은 조건 | |---:|---|---:|---|---| | 1. 소비자 응답 정규화 | 완료 | `3/3` | 없음 | 없음 | | 2. POINT 조회 조건 보정 | 완료 | `1/1` | 없음 | 없음 | - 동시에 하나의 미완료 goal만 운용한다. - 완료된 Task와 검증 기록은 되돌리거나 삭제하지 않는다. - goal에는 token budget을 설정하지 않는다. ## 범위 ### 포함 - legacy 콘텐츠 상세 `isAvailableUsePoint` 보정 - v2 콘텐츠 overview·전체 탭·추천 탭 응답 보정 - v2 홈 추천 첫 오디오 응답 보정 - v2 크리에이터 채널 홈·오디오·라이브의 공통 오디오 응답 보정 - 추천 `pointAudios`와 전체 탭 `type=POINT`의 유료 조건 보강 - 전체 탭 POINT count·pagination 회귀 검증 - AI 캐릭터 관리자 조회 원본값 유지 회귀 검증 ### 제외 - DB 저장값과 기존 데이터 변경 - 콘텐츠 생성·수정 validation 변경 - 관리자 콘텐츠 mapper·service 변경 - legacy 콘텐츠 상세 이외의 legacy 조회 API 변경 - 결제·주문·포인트 차감 로직 변경 - API schema와 dependency 변경 ## 기술적 제약 - 기술 스택: Kotlin, Java 17, Spring Boot 2.7.14, JUnit 5, QueryDSL/JPA. - 기존 package와 mapper/repository 책임을 유지한다. - 공개 API 필드명 `isAvailableUsePoint`, `isPointAvailable`을 유지한다. - `price == 0`은 무료, 실질 포인트 가능 여부는 `configured && price > 0`으로 고정한다. - POINT 조회의 목록과 count는 같은 repository 조건을 사용한다. - 모든 구현 Task는 `RED → RED 확인 → GREEN → GREEN 확인 → REFACTOR` 순서로 실행한다. - focused test부터 실행하고 최종 Gate에서 영향 범위 회귀와 전체 `test`를 실행한다. ## Phase 1: 소비자 응답 정규화 **Phase 결과:** 무료·저장값 true인 콘텐츠가 대상 소비자 응답에서는 false로 보이지만 관리자 조회에서는 true를 유지한다. **선행조건:** PRD `POINT-001~004`, `POINT-007~008` 확정. **Phase 완료 조건:** `P1-T1`과 `P1-GATE` 완료, 검증 기록 누적. **후속 리뷰 완료 조건:** `P1-R1` 완료, `P1-GATE` 재검증과 수정 후 검증 기록 누적. ### Task 1.1 소비자 응답의 실질 포인트 가능 여부 적용 **Goal 실행 `P1-T1`:** legacy 상세와 모든 대상 v2 응답 조립 경계에서 가격을 반영한 포인트 가능 여부를 반환한다. - **시작 조건:** PRD `DEC-001`, `DEC-003` 확인. - **완료 증거:** RED/GREEN 체크박스, 대상 mapper 테스트, 관리자 원본값 회귀 테스트와 Progress 기록. - **범위 밖:** POINT 전용 repository 필터와 콘텐츠 저장값 변경. **Files:** - Modify: `src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentService.kt` - Modify: `src/main/kotlin/kr/co/vividnext/sodalive/v2/api/content/overview/dto/ContentOverviewPageResponse.kt` - Modify: `src/main/kotlin/kr/co/vividnext/sodalive/v2/api/content/all/dto/MainContentAllTabResponse.kt` - Modify: `src/main/kotlin/kr/co/vividnext/sodalive/v2/api/content/recommendation/dto/AudioRecommendationsResponse.kt` - Modify: `src/main/kotlin/kr/co/vividnext/sodalive/v2/api/creator/channel/common/dto/CreatorChannelAudioContentResponse.kt` - Modify: `src/main/kotlin/kr/co/vividnext/sodalive/v2/api/home/application/HomeRecommendationFacade.kt` - Modify: `src/test/kotlin/kr/co/vividnext/sodalive/content/AudioContentServiceTest.kt` - Modify: `src/test/kotlin/kr/co/vividnext/sodalive/v2/api/content/overview/dto/ContentOverviewPageResponseTest.kt` - Modify: `src/test/kotlin/kr/co/vividnext/sodalive/v2/api/content/all/dto/MainContentAllTabResponseTest.kt` - Modify: `src/test/kotlin/kr/co/vividnext/sodalive/v2/api/content/recommendation/application/AudioRecommendationFacadeTest.kt` - Create: `src/test/kotlin/kr/co/vividnext/sodalive/v2/api/creator/channel/common/dto/CreatorChannelAudioContentResponseTest.kt` - Create: `src/test/kotlin/kr/co/vividnext/sodalive/v2/api/home/application/HomeRecommendationFacadeTest.kt` - Modify: `src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/content/AiCharacterAdminAudioContentControllerTest.kt` **Interfaces:** - Consumes: 각 mapper가 이미 받는 `price: Int`와 저장된 Boolean 필드. - Produces: schema를 바꾸지 않고 `configured && price > 0`으로 보정된 소비자 응답 Boolean. - Preserves: AI 캐릭터 관리자 목록·상세의 원본 Boolean. - [x] **RED:** 각 응답 경계에 무료·저장값 true fixture를 추가하고 소비자 응답은 false, 유료·저장값 true 응답은 true로 기대한다. 관리자 상세에는 무료·저장값 true가 true로 유지되는 회귀 assertion을 추가한다. - [x] **RED 확인:** 아래 focused test를 실행해 소비자 응답이 현재 true를 전달하여 발생하는 assertion 실패를 확인하고, 관리자 회귀 assertion은 기존 동작으로 통과하는지 구분해 기록한다. ```bash ./gradlew test \ --tests 'kr.co.vividnext.sodalive.content.AudioContentServiceTest' \ --tests 'kr.co.vividnext.sodalive.v2.api.content.overview.dto.ContentOverviewPageResponseTest' \ --tests 'kr.co.vividnext.sodalive.v2.api.content.all.dto.MainContentAllTabResponseTest' \ --tests 'kr.co.vividnext.sodalive.v2.api.content.recommendation.application.AudioRecommendationFacadeTest' \ --tests 'kr.co.vividnext.sodalive.v2.api.creator.channel.common.dto.CreatorChannelAudioContentResponseTest' \ --tests 'kr.co.vividnext.sodalive.v2.api.home.application.HomeRecommendationFacadeTest' \ --tests 'kr.co.vividnext.sodalive.v2.api.admin.aicharacter.content.AiCharacterAdminAudioContentControllerTest' ``` - [x] **GREEN:** 소비자 응답 조립 지점의 Boolean 대입을 `storedValue && price > 0`으로 바꾼다. `AiCharacterAdminAudioContentMapper`와 관리자 service는 수정하지 않는다. - [x] **GREEN 확인:** 같은 focused test 명령을 다시 실행해 무료 true → false, 유료 true → true, 관리자 무료 true → true가 모두 통과하는지 확인한다. - [x] **REFACTOR:** 동일한 한 줄 식을 유지하고 새 helper나 구조 변경을 추가하지 않는다. 변경 파일에 `./gradlew ktlintCheck`를 실행하고 결과를 Progress에 기록한다. ### Phase 1 Gate **Goal 실행 `P1-GATE`:** 대상 소비자 응답 계약과 관리자 제외 계약을 최종 판정한다. - **시작 조건:** `P1-T1` 완료. - **완료 증거:** 아래 명령 성공, 공개 필드명 유지 확인과 Progress 기록. - **범위 밖:** POINT repository 필터 구현과 관련 없는 응답 리팩터링. ```bash ./gradlew test \ --tests 'kr.co.vividnext.sodalive.content.AudioContentServiceTest' \ --tests 'kr.co.vividnext.sodalive.v2.api.content.*' \ --tests 'kr.co.vividnext.sodalive.v2.api.creator.channel.*' \ --tests 'kr.co.vividnext.sodalive.v2.api.home.*' ./gradlew ktlintCheck ``` **Expected:** 대상 소비자 응답에서 무료 콘텐츠가 포인트 가능으로 노출되지 않고 기존 JSON 필드 집합이 유지된다. ### Task 1.2 소비자·관리자 포인트 가능 계약의 회귀 증거 보강 **Goal 실행 `P1-R1`:** 소비자 응답의 저장값 false 조건과 무료 관리자 응답의 원본값 유지 조건을 자동 회귀 테스트로 증명한다. - **시작 조건:** `REV-P1-001`, `REV-P1-002` 확정과 기존 `P1-GATE` 완료. - **완료 증거:** 대상 소비자 경계의 `price > 0, storedIsPointAvailable == false` assertion, 관리자 목록·상세의 `price == 0, storedIsPointAvailable == true` assertion, focused test와 `P1-GATE` 재검증 기록. - **범위 밖:** production 코드 변경, 관리자 응답 보정, 공개 API schema 변경. - **TDD 예외 사유:** production 구현은 정적 검토상 계약을 이미 충족하며, 누락된 것은 완료 근거인 회귀 assertion이다. 의도적인 production 결함을 만들어 RED를 재현하지 않는다. - **대체 검증 방법:** 기존 fixture가 누락한 진리표 조건을 추가하고 focused test와 `P1-GATE`를 통과시킨 뒤 mapper 식과 관리자 원본 전달 식을 다시 대조한다. **Files:** - Modify: `src/test/kotlin/kr/co/vividnext/sodalive/content/AudioContentServiceTest.kt` - Modify: `src/test/kotlin/kr/co/vividnext/sodalive/v2/api/content/overview/dto/ContentOverviewPageResponseTest.kt` - Modify: `src/test/kotlin/kr/co/vividnext/sodalive/v2/api/content/all/dto/MainContentAllTabResponseTest.kt` - Modify: `src/test/kotlin/kr/co/vividnext/sodalive/v2/api/content/recommendation/application/AudioRecommendationFacadeTest.kt` - Modify: `src/test/kotlin/kr/co/vividnext/sodalive/v2/api/creator/channel/common/dto/CreatorChannelAudioContentResponseTest.kt` - Modify: `src/test/kotlin/kr/co/vividnext/sodalive/v2/api/home/application/HomeRecommendationFacadeTest.kt` - Modify: `src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/content/AiCharacterAdminAudioContentControllerTest.kt` - Modify: `docs/20260731_무료_콘텐츠_포인트_결제_불가/plan-task.md` - [x] 대상 소비자 응답 경계마다 `price > 0, storedIsPointAvailable == false`가 false로 유지되는 assertion을 추가한다. - [x] AI 캐릭터 관리자 목록·상세 fixture를 `price == 0, storedIsPointAvailable == true`로 구성하고 저장값 true가 그대로 반환되는지 확인한다. - [x] Task 1.1 focused test와 `P1-GATE`를 재실행하고 결과를 Progress에 누적한다. - [x] `REV-P1-001`, `REV-P1-002`의 수정 후 검증 기록과 이 문서의 현재 상태를 갱신한다. ### Task 1.3 legacy 상세의 유료 포인트 가능 positive 회귀 증거 보강 **Goal 실행 `P1-R2`:** legacy 상세에서 `price > 0, storedIsPointAvailable == true`가 true를 반환하는 계약을 자동 회귀 테스트로 증명한다. - **시작 조건:** `REV-P1-003` 확정과 `P1-R1` 완료. - **완료 증거:** legacy 상세의 무료·저장값 true → false, 유료·저장값 false → false, 유료·저장값 true → true assertion, focused test와 `P1-GATE` 재검증 기록. - **범위 밖:** production 코드, v2 응답 test, 관리자 mapper/service 변경. - **TDD 예외 사유:** production 구현은 정적 검토상 positive 계약을 이미 충족하며, 누락된 것은 회귀 assertion이다. 의도적인 production 결함을 만들어 RED를 재현하지 않는다. - **대체 검증 방법:** 기존 legacy 상세 test의 진리표를 완성하고 focused test와 `P1-GATE`를 통과시킨다. **Files:** - Modify: `src/test/kotlin/kr/co/vividnext/sodalive/content/AudioContentServiceTest.kt` - Modify: `docs/20260731_무료_콘텐츠_포인트_결제_불가/plan-task.md` - [x] legacy 상세 test에 `price > 0, storedIsPointAvailable == true` 응답이 true인 assertion을 추가한다. - [x] 하나의 test가 세 계약 조건을 드러내도록 `DisplayName`과 test 함수명을 맞춘다. - [x] `AudioContentServiceTest`, `P1-GATE`, `ktlintCheck`를 실행하고 결과를 Progress에 누적한다. - [x] `REV-P1-003`의 수정 후 검증 기록과 이 문서의 현재 상태를 갱신한다. ## Phase 2: POINT 조회 조건 보정 **Phase 결과:** 추천과 전체 탭 POINT 목록, count와 pagination 후보에서 무료 콘텐츠가 제외된다. **선행조건:** `P1-GATE` 완료와 PRD `POINT-005~006` 확정. **Phase 완료 조건:** `P2-T1`과 `P2-GATE` 완료, 검증 기록 누적. ### Task 2.1 추천·전체 탭 POINT 조회에 유료 조건 적용 **Goal 실행 `P2-T1`:** 두 POINT 조회가 `isPointAvailable == true && price > 0` 조건을 공통으로 사용한다. - **시작 조건:** `P1-GATE` 완료, PRD `DEC-002` 확인. - **완료 증거:** repository RED/GREEN, endpoint E2E와 Progress 기록. - **범위 밖:** FREE/AUDIO/추천 점수·랜덤 정렬·공개/성인/차단 조건 변경. **Files:** - Modify: `src/main/kotlin/kr/co/vividnext/sodalive/v2/content/recommendation/adapter/out/persistence/DefaultAudioRecommendationQueryRepository.kt` - Modify: `src/main/kotlin/kr/co/vividnext/sodalive/v2/content/all/adapter/out/persistence/DefaultMainContentAllQueryRepository.kt` - Modify: `src/test/kotlin/kr/co/vividnext/sodalive/v2/content/recommendation/adapter/out/persistence/DefaultAudioRecommendationQueryRepositoryTest.kt` - Modify: `src/test/kotlin/kr/co/vividnext/sodalive/v2/content/all/adapter/out/persistence/DefaultMainContentAllQueryRepositoryTest.kt` - Modify: `src/test/kotlin/kr/co/vividnext/sodalive/v2/api/content/recommendation/adapter/in/web/AudioRecommendationEndToEndTest.kt` - Modify: `src/test/kotlin/kr/co/vividnext/sodalive/v2/api/content/all/adapter/in/web/MainContentAllEndToEndTest.kt` **Interfaces:** - Consumes: 기존 `audioContent.isPointAvailable`과 `audioContent.price` QueryDSL 필드. - Produces: 추천 `pointAudios`, 전체 탭 POINT 목록과 count에 공통 적용되는 `isPointAvailable.isTrue.and(price.gt(0))` 조건. - Preserves: FREE 목록은 `price.eq(0)`, AUDIO 목록은 `price.gt(0)`인 기존 조건. - [x] **RED:** 두 repository fixture에 `price = 0, isPointAvailable = true`와 `price > 0, isPointAvailable = true`를 함께 두고 POINT 결과에는 유료 항목만 포함되도록 기대한다. 전체 탭 E2E는 `totalCount`, `audios`, `hasNext`가 같은 후보 집합을 반영하도록 기대한다. - [x] **RED 확인:** 아래 focused test를 실행해 무료 true 콘텐츠가 POINT 결과에 포함되어 발생하는 목록 또는 count assertion 실패를 확인한다. ```bash ./gradlew test \ --tests 'kr.co.vividnext.sodalive.v2.content.recommendation.adapter.out.persistence.DefaultAudioRecommendationQueryRepositoryTest' \ --tests 'kr.co.vividnext.sodalive.v2.content.all.adapter.out.persistence.DefaultMainContentAllQueryRepositoryTest' \ --tests 'kr.co.vividnext.sodalive.v2.api.content.recommendation.adapter.in.web.AudioRecommendationEndToEndTest' \ --tests 'kr.co.vividnext.sodalive.v2.api.content.all.adapter.in.web.MainContentAllEndToEndTest' ``` - [x] **GREEN:** `findPointAudios` 조건과 `optionalAudioPointCondition`에 `price.gt(0)`을 결합한다. 전체 탭 count와 목록은 기존 `audioCondition`을 계속 공유한다. - [x] **GREEN 확인:** 같은 focused test를 다시 실행해 POINT 결과에서 무료가 제외되고 유료 true는 유지되며 `totalCount`와 `hasNext`가 일치하는지 확인한다. - [x] **REFACTOR:** FREE/AUDIO 조건, 랜덤/가격/인기 정렬과 공통 visibility 조건이 바뀌지 않았는지 직접 영향 회귀와 `ktlintCheck`로 확인한다. ### Phase 2 Gate **Goal 실행 `P2-GATE`:** 소비자 응답과 POINT 조회의 전체 요구사항을 최종 판정한다. - **시작 조건:** `P2-T1` 완료. - **완료 증거:** focused·전체 회귀·lint 성공, 문서 Progress와 최종 검증 기록. - **범위 밖:** 실패를 숨기기 위한 test 삭제·skip·완화와 관련 없는 코드 수정. ```bash ./gradlew test \ --tests 'kr.co.vividnext.sodalive.v2.content.recommendation.*' \ --tests 'kr.co.vividnext.sodalive.v2.content.all.*' \ --tests 'kr.co.vividnext.sodalive.v2.api.content.*' ./gradlew test ./gradlew ktlintCheck ``` **Expected:** 모든 테스트와 lint가 성공하고 무료 콘텐츠가 어떤 대상 소비자 응답이나 POINT 전용 결과에서도 포인트 결제 가능으로 취급되지 않는다. ## 실행 순서와 의존성 | 순서 | Goal | 선행조건 | 병행 가능 | 차단 시 다음 행동 | |---:|---|---|---|---| | 1 | `P1-T1` | 없음 | 아니요 | 실패 응답 경계와 fixture를 다시 대조 | | 2 | `P1-GATE` | `P1-T1` | 아니요 | 실패 소유 mapper의 회귀 수정 goal 추가 | | 3 | `P2-T1` | `P1-GATE` | 아니요 | 목록·count 조건 공유 여부 확인 | | 4 | `P2-GATE` | `P2-T1` | 아니요 | 실패 소유 repository 또는 mapper로 되돌림 | | 5 | `P1-R1` | Phase별 review 판정 완료 | 아니요 | 누락된 계약 fixture와 assertion 범위를 다시 대조 | | 6 | `P1-R2` | `P1-R1` | 아니요 | legacy 상세의 세 계약 조건을 다시 대조 | ```text P1-T1 → P1-GATE → P2-T1 → P2-GATE ``` 리뷰 후속 실행 순서: ```text P1-R1 → P1-R2 ``` ## 변경 금지 항목 - 관리자 조회와 DB 저장값을 보정하지 않는다. - 공개 DTO 필드를 추가·삭제·이름 변경하지 않는다. - POINT 조건 보강을 FREE/AUDIO/추천 점수·정렬 변경으로 확장하지 않는다. - 새 dependency, schema migration과 공통 abstraction을 추가하지 않는다. - 기존 완료 문서의 체크박스와 검증 기록을 삭제하거나 덮어쓰지 않는다. - test를 삭제·skip·완화해 Gate를 통과시키지 않는다. ## Progress 실제 구현 시 기존 기록을 삭제하거나 덮어쓰지 않고 Goal 실행 결과를 차수별로 누적한다. ### 문서 작성 검증 — 2026-07-31 - 상태: 완료 - 무엇을: 확정 요구사항을 새 PRD와 goal 실행형 계획에 기록하고 기존 추천·전체 탭 문서의 충돌 계약을 정정했다. - 왜: 구현 전에 단일 기준 문서, 범위, 제외 조건과 완료 증거를 확정하기 위해서다. - 어떻게: - `rg` placeholder·요구사항/Goal 추적 검색 — placeholder 없음, `POINT-001~008`과 `P1-T1`·`P2-T1` 연결 확인. - 계획에 기록된 production 파일 존재 확인 — 누락 없음. - `git diff --check` — 출력 없음. - `./gradlew --no-daemon tasks --all` — `BUILD SUCCESSFUL`, exit code 0. - 구현 test: 문서만 변경했으므로 실행하지 않았다. - 남은 항목: `P1-T1`부터 구현 실행. - 다음 행동: 사용자가 구현을 요청하면 `P1-T1`의 RED부터 시작한다. ### 구현 검증 — 2026-07-31 - 상태: 완료 - 무엇을: 무료 콘텐츠 포인트 결제 불가 정책을 대상 소비자 응답과 POINT 전용 조회 조건에 적용했다. - 왜: `price == 0` 콘텐츠가 포인트 결제 가능 상태와 동시에 노출되거나 POINT 목록·개수·페이징 후보에 포함되지 않도록 하기 위해서다. - 어떻게: - P1 RED focused test — 소비자 응답 경계에서 무료·저장값 true가 기존 true로 전달되어 assertion 실패 확인. - P1 GREEN focused test — `BUILD SUCCESSFUL`, 무료 true → false, 유료 true → true, 관리자 상세 원본 true 유지 확인. - P1-GATE — 원 wildcard 명령은 600초 제한 초과로 분할 실행했고, content 범위 `BUILD SUCCESSFUL`(9m31s), creator/home 범위 `BUILD SUCCESSFUL`(4m27s), `./gradlew ktlintCheck` `BUILD SUCCESSFUL`(1m02s). - P2 RED focused test — recommendation/main-all repository와 E2E에서 무료·저장값 true가 POINT 후보에 포함되어 4개 assertion 실패 확인. - P2 GREEN focused test — `BUILD SUCCESSFUL`, 추천 `pointAudios`와 전체 탭 POINT 목록·`totalCount`·`hasNext`가 유료 포인트 후보만 반영함을 확인. - P2-GATE 영향 범위 — `./gradlew test --tests 'kr.co.vividnext.sodalive.v2.content.recommendation.*' --tests 'kr.co.vividnext.sodalive.v2.content.all.*' --tests 'kr.co.vividnext.sodalive.v2.api.content.*'` `BUILD SUCCESSFUL`(1m48s). - 최종 Gate — `./gradlew test` `BUILD SUCCESSFUL`(13m27s), `./gradlew ktlintCheck` `BUILD SUCCESSFUL`(40s). - 결정: 관리자 mapper/service, DB 저장값, 공개 DTO 필드명과 구조는 변경하지 않았다. - 남은 항목: 없음. ### Phase별 리뷰 — 2026-07-31 - 상태: Phase 1 수정 goal 필요, Phase 2 확정 발견 사항 없음. - 무엇을: PRD, 구현 계획, staged production/test diff와 관련 호출 경계를 Phase별로 대조했다. - 왜: 완료 체크와 실제 계약 증거가 일치하는지 판정하기 위해서다. - 어떻게: - `git diff --cached --check` — 출력 없음. - `rg`로 대상 응답 mapper, POINT repository 조건과 관련 test assertion을 대조했다. - `./gradlew --no-daemon tasks --all` — `BUILD SUCCESSFUL`, exit code 0. - 사용자 지시에 따라 compile과 test는 다시 실행하지 않았으며 기존 구현 검증 기록을 근거로만 확인했다. - 후속: `REV-P1-001`, `REV-P1-002`를 `P1-R1`로 전환했다. - 리뷰 문서: - `docs/20260731_무료_콘텐츠_포인트_결제_불가/reviews/phase-1-review.md` - `docs/20260731_무료_콘텐츠_포인트_결제_불가/reviews/phase-2-review.md` ### 리뷰 후속 검증 — 2026-07-31 - 상태: 완료 - 무엇을: `REV-P1-001`, `REV-P1-002`의 누락된 회귀 증거를 `P1-R1`로 보강했다. - 왜: 소비자 응답의 저장값 false 조건과 AI 캐릭터 관리자 무료 원본값 유지 조건을 자동 테스트로 고정하기 위해서다. - 어떻게: - P1-R1 focused test — `./gradlew test --tests 'kr.co.vividnext.sodalive.content.AudioContentServiceTest' --tests 'kr.co.vividnext.sodalive.v2.api.content.overview.dto.ContentOverviewPageResponseTest' --tests 'kr.co.vividnext.sodalive.v2.api.content.all.dto.MainContentAllTabResponseTest' --tests 'kr.co.vividnext.sodalive.v2.api.content.recommendation.application.AudioRecommendationFacadeTest' --tests 'kr.co.vividnext.sodalive.v2.api.creator.channel.common.dto.CreatorChannelAudioContentResponseTest' --tests 'kr.co.vividnext.sodalive.v2.api.home.application.HomeRecommendationFacadeTest' --tests 'kr.co.vividnext.sodalive.v2.api.admin.aicharacter.content.AiCharacterAdminAudioContentControllerTest'` `BUILD SUCCESSFUL`(3m11s). - P1-GATE — `./gradlew test --tests 'kr.co.vividnext.sodalive.content.AudioContentServiceTest' --tests 'kr.co.vividnext.sodalive.v2.api.content.*' --tests 'kr.co.vividnext.sodalive.v2.api.creator.channel.*' --tests 'kr.co.vividnext.sodalive.v2.api.home.*'` `BUILD SUCCESSFUL`(12m15s). - `./gradlew ktlintCheck` — `BUILD SUCCESSFUL`(47s). - 결정: production 코드, 관리자 mapper/service, 공개 DTO schema는 변경하지 않았다. - 남은 항목: 없음. ### 2차 Phase 1 리뷰 — 2026-07-31 - 상태: `REV-P1-001`, `REV-P1-002` 수정 확인, 추가 수정 goal 필요. - 무엇을: `P1-R1` test diff, Phase 1 production mapper, 관리자 제외 경계와 완료 기록을 다시 대조했다. - 왜: 기존 확정 발견 사항이 실제로 수정됐는지, 계약 진리표에 다른 누락은 없는지 판정하기 위해서다. - 어떻게: - staged diff와 `rg`로 6개 소비자 조립 경계의 유료·저장값 false assertion을 확인했다. - 관리자 목록·상세의 `price == 0, stored == true` fixture와 true assertion을 확인했다. - `git diff --check`, `git diff --cached --check` — 출력 없음. - `./gradlew --no-daemon tasks --all` — `BUILD SUCCESSFUL`, exit code 0. - 사용자 지시에 따라 compile과 test는 다시 실행하지 않았다. - 후속: legacy 상세의 유료·저장값 true positive assertion 누락을 `REV-P1-003`, `P1-R2`로 전환했다. ### 리뷰 후속 검증 2차 — 2026-07-31 - 상태: 완료 - 무엇을: `REV-P1-003`의 누락된 legacy 상세 positive 회귀 증거를 `P1-R2`로 보강했다. - 왜: legacy 상세에서 무료·저장값 true, 유료·저장값 false, 유료·저장값 true의 포인트 사용 가능 계약을 하나의 test로 고정하기 위해서다. - 어떻게: - P1-R2 focused test — `./gradlew test --tests 'kr.co.vividnext.sodalive.content.AudioContentServiceTest'` `BUILD SUCCESSFUL`(14s). - P1-GATE 단위 범위 — `./gradlew test --tests 'kr.co.vividnext.sodalive.content.AudioContentServiceTest' --tests 'kr.co.vividnext.sodalive.v2.api.content.*' --tests 'kr.co.vividnext.sodalive.v2.api.creator.channel.*' --tests 'kr.co.vividnext.sodalive.v2.api.home.*'` `BUILD SUCCESSFUL`(2m 26s). - `./gradlew ktlintCheck` — `BUILD SUCCESSFUL`(14s). - 결정: production 코드, 관리자 mapper/service, 공개 DTO schema는 변경하지 않았다. - 전체 테스트: 사용자 지시에 따라 실행하지 않았다. - 남은 항목: 없음. ## Decision Log | 날짜 | ID | 상태 | 결정 | 근거 | 영향 Goal/문서 | |---|---|---|---|---|---| | 2026-07-31 | `PLAN-DEC-001` | 확정 | 응답 조립 지점에서 `configured && price > 0`을 직접 적용하고 새 abstraction을 만들지 않는다 | 기존 mapper 책임과 최소 변경 원칙 | `P1-T1` | | 2026-07-31 | `PLAN-DEC-002` | 확정 | POINT repository 조건에 `price > 0`을 결합하고 전체 탭 목록과 count의 기존 조건 공유 구조를 유지한다 | 목록·count 일관성 | `P2-T1` | | 2026-07-31 | `PLAN-DEC-003` | 확정 | 여러 소비자 API 경계를 변경하므로 최종 Gate에서 전체 `test`를 실행한다 | 영향 범위 회귀 증거 필요 | `P2-GATE` | ## 발견된 문제 초기 구현 시 발견된 문제 없음. ### 2026-07-31 Phase별 리뷰 - `REV-P1-001` — 소비자 응답 테스트에 `price > 0, storedIsPointAvailable == false` 계약 증거가 없다. - `REV-P1-002` — 관리자 회귀 테스트가 무료 fixture로 저장값 유지 계약을 검증하지 않는다. - `REV-P1-003` — legacy 상세 test가 유료·저장값 true의 positive 계약을 검증하지 않는다. - 후속 Goal: `P1-R1` - 처리: 2026-07-31 `P1-R1` 완료. - 추가 후속 Goal: `P1-R2` - 처리: 2026-07-31 `P1-R2` 완료. - Phase 2: 확정 발견 사항 없음. ## 최종 보고 형식 ```markdown 구현 결과: 무료 콘텐츠 포인트 결제 불가 정책을 소비자 응답과 POINT 조회에 적용 - 변경: 대상 응답 mapper와 추천·전체 탭 POINT 조회 조건 - 결정: 관리자·DB 원본 유지, 공개 schema 유지 - 검증: focused test, 영향 범위 회귀, 전체 test, ktlintCheck 결과 - 남은 항목: 없음 또는 실패·외부 조건 - 문서: PRD와 이 plan-task.md의 Progress·검증 기록 ```