333 lines
29 KiB
Markdown
333 lines
29 KiB
Markdown
# v2 콘텐츠 목록 요청 언어별 번역 구현 계획
|
|
|
|
- 작성일: 2026-09-09
|
|
- 상태: Phase 1·2 구현 및 자동 검증 완료, test 서버 HTTP Gate 대기
|
|
- 요구사항·API 기준: [prd.md](prd.md)
|
|
- 현재 활성 Goal: P1-GATE test 서버 HTTP 확인 대기
|
|
- 다음 Goal: P2-GATE test 서버 HTTP 확인
|
|
- 실행 여부와 결과는 각 Task/Gate 실행 기록을 기준으로 한다.
|
|
|
|
## 목표와 범위
|
|
|
|
PRD의 7개 GET API에서 오디오 제목·시리즈 제목/시리즈명·테마명을 요청 언어로 표시하고,
|
|
번역이 없거나 공백이면 기존 원문을 반환한다. 제외 항목은 PRD §2와 동일하다.
|
|
|
|
| Phase | 결과 | 상태 | 완료 Task |
|
|
|---|---|---|---|
|
|
| 1 | 채널·메인 전체 목록 번역 통일 | 자동 검증 완료, test 서버 HTTP Gate 대기 | 2/2 |
|
|
| 2 | 추천·더보기·랭킹 번역 통일 | 자동 검증 완료, test 서버 HTTP Gate 대기 | 2/2 |
|
|
|
|
## 구현 원칙
|
|
|
|
- 조사는 v2 패키지에서 시작하고 연결된 기존 번역 저장·조회 코드를 확인한다.
|
|
- 기존 LangInterceptor/LangContext와 ko/en/ja 파싱을 재사용하며 공통 언어 파서를 변경하지 않는다.
|
|
- Kotlin/Spring 기존 계층과 스타일을 따른다. 공개 응답 스키마와 인증·필터·정렬·페이지 조건은 유지한다.
|
|
- 번역 테이블 locale 조인 또는 기존 일괄 조회를 재사용한다. 항목 수만큼 추가 조회하는 N+1은 금지한다.
|
|
- 랭킹·추천 스냅샷의 집계/저장과 요청별 번역 표시를 분리한다.
|
|
- 저장 데이터·DDL·번역 생성 파이프라인·배너 국가별 선택 정책은 변경하지 않는다.
|
|
- 파일 목록은 수정 후보 및 확인 책임 범위다. 기존 동작이 맞는 파일은 테스트로만 확인한다.
|
|
- 기존 함수의 모든 호출자를 확인하고 공용 호출에 변경이 전파되면 관련 테스트와 이 문서에 기록한다.
|
|
- 새 추상화·의존성 없이 기존 Port/Repository를 최소 확장한다.
|
|
|
|
## 공통 참조 파일
|
|
|
|
- src/main/kotlin/kr/co/vividnext/sodalive/i18n/LangInterceptor.kt
|
|
- src/main/kotlin/kr/co/vividnext/sodalive/i18n/LangContext.kt
|
|
- src/main/kotlin/kr/co/vividnext/sodalive/i18n/Lang.kt
|
|
- src/main/kotlin/kr/co/vividnext/sodalive/content/translation/ContentTranslationRepository.kt
|
|
- src/main/kotlin/kr/co/vividnext/sodalive/content/series/translation/SeriesTranslationRepository.kt
|
|
|
|
## 공통 테스트 데이터
|
|
|
|
같은 원문에 서로 다른 ko/en/ja 번역을 저장한다. 번역 행 없음·빈 문자열·공백·다른 locale만 존재·
|
|
오디오와 시리즈 중 한쪽만 번역된 경우를 포함한다.
|
|
조회 요청에는 ko/en/ja, en-US, ja-JP, 누락, 빈 값, 미지원 fr를 사용한다.
|
|
en→ja→en 연속 요청에서 언어가 섞이지 않아야 한다.
|
|
언어별 응답은 대상 문자열만 달라지고 ID·개수·순서·필터·페이지·가격·제외 필드는 같아야 한다.
|
|
|
|
### Phase 1: 채널·메인 전체 목록
|
|
|
|
시작 조건: 사용자 구현 요청. 결과: LIST-01~04의 번역 표시와 기존 동작 유지.
|
|
|
|
- [x] **Task 1.1: 채널 오디오·시리즈·홈의 번역 표시 통일**
|
|
|
|
**Goal P1-T1**
|
|
- Objective: 오디오 title·seriesName·테마명, 홈 오디오/시리즈/AUDIO 일정 제목의 번역과 필드별 fallback을 검증한다. 이미 구현된 시리즈·테마 번역은 회귀를 먼저 확인하며 불필요하게 수정하지 않는다.
|
|
- 요구사항: LIST-01, LIST-02, LIST-04, LANG-01/02, TEXT-01/02, COMPAT-01, READ-01, ISOLATE-01
|
|
- 시작 조건: 사용자의 구현 착수 요청과 PRD 확인.
|
|
- 완료 증거: 아래 TDD 체크박스, focused test 성공, 실제 결과 기록.
|
|
- 범위 밖: 메인 목록·추천·랭킹 변경.
|
|
- Consumes: 기존 원문·저장된 번역·LangContext.lang.code.
|
|
- Produces: 기존 응답 계약을 유지한 요청 locale별 표시 문자열.
|
|
|
|
**확인/수정 후보 파일**
|
|
|
|
- src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/audio/application/CreatorChannelAudioQueryService.kt
|
|
- src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/audio/port/out/CreatorChannelAudioQueryPort.kt
|
|
- src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/audio/adapter/out/persistence/DefaultCreatorChannelAudioQueryRepository.kt
|
|
- src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/home/application/CreatorChannelHomeQueryService.kt
|
|
- src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/home/port/out/CreatorChannelHomeQueryPort.kt
|
|
- src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/home/adapter/out/persistence/DefaultCreatorChannelHomeQueryRepository.kt
|
|
- src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/series/application/CreatorChannelSeriesQueryService.kt
|
|
- src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/series/adapter/out/persistence/DefaultCreatorChannelSeriesQueryRepository.kt
|
|
|
|
**테스트 파일**
|
|
|
|
- src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/audio/adapter/out/persistence/DefaultCreatorChannelAudioQueryRepositoryTest.kt
|
|
- src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/home/adapter/out/persistence/DefaultCreatorChannelHomeQueryRepositoryTest.kt
|
|
- src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/series/adapter/out/persistence/DefaultCreatorChannelSeriesQueryRepositoryTest.kt
|
|
- src/test/kotlin/kr/co/vividnext/sodalive/v2/api/creator/channel/audio/adapter/in/web/CreatorChannelAudioEndToEndTest.kt
|
|
- src/test/kotlin/kr/co/vividnext/sodalive/v2/api/creator/channel/series/adapter/in/web/CreatorChannelSeriesEndToEndTest.kt
|
|
- src/test/kotlin/kr/co/vividnext/sodalive/v2/api/creator/channel/home/CreatorChannelHomeEndToEndTest.kt
|
|
|
|
- [x] **RED:** 위 테스트에서 대상 경로의 언어 선택·원문 fallback·기존 값 불변을 검증하는 가장 작은 실패 사례를 작성한다.
|
|
- [x] **RED 확인:** 아래 명령으로 미구현 번역 때문에 발생한 제목 assertion 실패를 확인한다. 환경 실패는 RED 증거가 아니다.
|
|
- [x] **GREEN:** 기존 계층에서 locale 전달과 번역 조회/매핑만 최소 구현한다.
|
|
- [x] **GREEN 확인:** 같은 명령으로 의도한 번역·fallback 성공을 확인한다.
|
|
- [x] **REFACTOR:** 이번 변경이 만든 중복만 정리하고 아래 focused test 및 직접 호출자 회귀를 확인한다. 실행 결과를 Task 기록에 누적한다.
|
|
|
|
```bash
|
|
./gradlew test --tests '*DefaultCreatorChannelAudioQueryRepositoryTest' --tests '*DefaultCreatorChannelHomeQueryRepositoryTest' --tests '*DefaultCreatorChannelSeriesQueryRepositoryTest' --tests '*CreatorChannelAudioEndToEndTest' --tests '*CreatorChannelSeriesEndToEndTest' --tests '*CreatorChannelHomeEndToEndTest'
|
|
```
|
|
|
|
기대 결과: 종료 코드 0. 번역이 없어서 콘텐츠가 누락되거나 순서·개수·페이지가 달라지는 사례 0건.
|
|
|
|
**Task 실행 기록:**
|
|
|
|
- 2026-09-09: 사용자가 현재 브랜치 구현을 지시해 worktree 없이 `test` 브랜치에서 진행했다.
|
|
- RED: `./gradlew test --tests '*DefaultCreatorChannelAudioQueryRepositoryTest.shouldFindAudioContentsWithTranslatedTitleFallback' --tests '*DefaultCreatorChannelHomeQueryRepositoryTest.shouldFindHomeAudioContentsWithTranslatedTitleFallback' --tests '*DefaultCreatorChannelHomeQueryRepositoryTest.shouldFindHomeSchedulesAndSeriesWithTranslatedTitleFallback'` 실행. 종료 코드 1, 3개 테스트가 제목 assertion 실패로 실패했다.
|
|
- GREEN: 같은 RED 명령 재실행. 종료 코드 0, 오디오 title·홈 오디오 title/seriesName·AUDIO 스케줄 title·홈 시리즈 title의 번역 및 공백 fallback을 확인했다.
|
|
- Focused: `./gradlew test --tests '*DefaultCreatorChannelAudioQueryRepositoryTest' --tests '*DefaultCreatorChannelHomeQueryRepositoryTest' --tests '*DefaultCreatorChannelSeriesQueryRepositoryTest' --tests '*CreatorChannelAudioEndToEndTest' --tests '*CreatorChannelSeriesEndToEndTest' --tests '*CreatorChannelHomeEndToEndTest'` 실행. 종료 코드 0.
|
|
- 리뷰 보완: 같은 콘텐츠에 en/ja 번역을 함께 저장해 locale별 제목 선택과 ID 순서 불변을 추가 검증했다. 홈 service는 `LangContext.lang.code`가 최신 오디오·오디오 목록·스케줄·시리즈 port 호출에 전달되는지 확인했다.
|
|
- 보완 후 Focused 재실행: 위 P1-T1 focused command 실행. 종료 코드 0.
|
|
|
|
- [x] **Task 1.2: 메인 전체 목록 오디오 번역과 시리즈 회귀 확인**
|
|
|
|
**Goal P1-T2**
|
|
- Objective: 각 기존 type에서 오디오 제목 번역을 확인하고 기존 시리즈 locale 전달·fallback을 보존한다. 기존 요일·정렬·유료/무료 필터와 페이지 결과가 동일해야 한다.
|
|
- 요구사항: LIST-03, LANG-01/02, TEXT-01/02, COMPAT-01, READ-01, ISOLATE-01
|
|
- 시작 조건: P1-T1 완료.
|
|
- 완료 증거: 아래 TDD 체크박스, focused test 성공, 실제 결과 기록.
|
|
- 범위 밖: 추천·랭킹 변경.
|
|
- Consumes: 기존 원문·저장된 번역·LangContext.lang.code.
|
|
- Produces: 기존 응답 계약을 유지한 요청 locale별 표시 문자열.
|
|
|
|
**확인/수정 후보 파일**
|
|
|
|
- src/main/kotlin/kr/co/vividnext/sodalive/v2/content/all/application/MainContentAllQueryService.kt
|
|
- src/main/kotlin/kr/co/vividnext/sodalive/v2/content/all/port/out/MainContentAllQueryPort.kt
|
|
- src/main/kotlin/kr/co/vividnext/sodalive/v2/content/all/adapter/out/persistence/DefaultMainContentAllQueryRepository.kt
|
|
|
|
**테스트 파일**
|
|
|
|
- src/test/kotlin/kr/co/vividnext/sodalive/v2/content/all/adapter/out/persistence/DefaultMainContentAllQueryRepositoryTest.kt
|
|
- src/test/kotlin/kr/co/vividnext/sodalive/v2/content/all/application/MainContentAllQueryServiceTest.kt
|
|
- src/test/kotlin/kr/co/vividnext/sodalive/v2/api/content/all/adapter/in/web/MainContentAllEndToEndTest.kt
|
|
|
|
- [x] **RED:** 위 테스트에서 대상 경로의 언어 선택·원문 fallback·기존 값 불변을 검증하는 가장 작은 실패 사례를 작성한다.
|
|
- [x] **RED 확인:** 아래 명령으로 미구현 번역 때문에 발생한 제목 assertion 실패를 확인한다. 환경 실패는 RED 증거가 아니다.
|
|
- [x] **GREEN:** 기존 계층에서 locale 전달과 번역 조회/매핑만 최소 구현한다.
|
|
- [x] **GREEN 확인:** 같은 명령으로 의도한 번역·fallback 성공을 확인한다.
|
|
- [x] **REFACTOR:** 이번 변경이 만든 중복만 정리하고 아래 focused test 및 직접 호출자 회귀를 확인한다. 실행 결과를 Task 기록에 누적한다.
|
|
|
|
```bash
|
|
./gradlew test --tests '*DefaultMainContentAllQueryRepositoryTest' --tests '*MainContentAllQueryServiceTest' --tests '*MainContentAllEndToEndTest'
|
|
```
|
|
|
|
기대 결과: 종료 코드 0. 번역이 없어서 콘텐츠가 누락되거나 순서·개수·페이지가 달라지는 사례 0건.
|
|
|
|
**Task 실행 기록:**
|
|
|
|
- RED: `./gradlew test --tests '*MainContentAllQueryServiceTest.shouldQueryAudiosForAudioType' --tests '*DefaultMainContentAllQueryRepositoryTest.shouldFindAudiosWithTranslatedTitleFallback'` 실행. 종료 코드 1, service locale 전달 assertion과 repository 오디오 title assertion이 실패했다.
|
|
- GREEN/Focused: `./gradlew test --tests '*DefaultMainContentAllQueryRepositoryTest' --tests '*MainContentAllQueryServiceTest' --tests '*MainContentAllEndToEndTest'` 실행. 종료 코드 0, 오디오 title 번역·공백 fallback과 기존 시리즈 번역 회귀를 확인했다.
|
|
- 리뷰 보완: 같은 오디오에 en/ja 번역을 함께 저장해 locale별 title 선택과 ID 순서 불변을 추가 검증했다.
|
|
- 보완 후 Focused 재실행: 위 P1-T2 focused command 실행. 종료 코드 0.
|
|
|
|
#### Phase 1 Gate — P1-GATE
|
|
|
|
- Objective: Phase 1의 요청 헤더부터 DB 번역 선택·HTTP 응답까지 확인한다.
|
|
- 시작 조건: P1-T1, P1-T2 완료.
|
|
- 완료 증거: 해당 Task 명령 통과, 아래 수동 확인과 실제 명령·결과·환경 기록.
|
|
- 범위 밖: 운영 데이터 변경, Papago 실행, 테스트 삭제·완화.
|
|
- [x] 해당 Phase의 모든 focused test 명령을 실행하여 종료 코드 0을 확인한다.
|
|
- [ ] test 서버에서 같은 API·query를 유지하고 Accept-Language만 바꿔 GET 요청한다.
|
|
- [ ] ko/en/ja 번역·없는 번역·공백 번역·기본 언어 동작을 실제 JSON에서 확인한다.
|
|
- [ ] 대상 외 필드, 순서·개수·페이지·인증·성인/차단/구매 정책이 보존되는지 확인한다.
|
|
- [ ] 요청별 번역 조회가 항목 수에 비례해 늘지 않고 GET에서 번역 작업/외부 API/쓰기 호출이 없는지 확인한다.
|
|
- [ ] 검증 로그는 인증 토큰·개인정보를 제거해 기록한다.
|
|
|
|
수동 HTTP는 test 서버와 합성 테스트 계정을 사용한다. 인증이 필요한 API는 test 서버용 토큰을 사용하고 문서에 값을 기록하지 않는다.
|
|
test 서버 배포·DB 준비가 안 되면 수동 Gate를 완료 처리하지 않고 원인과 재개 조건을 남긴다.
|
|
|
|
**Phase 1 Gate 실행 기록:**
|
|
|
|
- 2026-09-09: P1-T1 focused command 재실행. 종료 코드 0.
|
|
- 2026-09-09: P1-T2 focused command 실행. 종료 코드 0.
|
|
- 2026-09-09: `./gradlew ktlintCheck`는 신규 테스트 호출 줄바꿈과 import 정렬 지적 후 수정해 재실행했다. 최종 종료 코드 0.
|
|
- 2026-09-09: `./gradlew assemble` 실행. 종료 코드 0.
|
|
- 2026-09-09: Oracle 리뷰에서 자동 검증 공백을 지적받아 다중 locale 선택, 홈 locale 전달, 공백 fallback 보강 테스트를 추가했다. 보강 focused test 종료 코드 0.
|
|
- 2026-09-09: 보강 후 P1-T1 focused command, P1-T2 focused command, `./gradlew ktlintCheck`, `./gradlew assemble`을 다시 실행했다. 각 종료 코드 0.
|
|
- 로컬 수동 테스트가 불가능하다는 사용자 확인에 따라 HTTP Gate를 test 서버로 이관했다. 재개 조건: test 서버 배포와 합성 인증 수단 준비 후 동일 query에서 `Accept-Language`만 바꿔 LIST-01~04 JSON을 확인한다.
|
|
|
|
### Phase 2: 추천·더보기·랭킹
|
|
|
|
시작 조건: P1-GATE 통과. 결과: LIST-05~07의 번역 표시와 요청 간 격리.
|
|
|
|
- [x] **Task 2.1: 추천·더보기의 모든 오디오 제목 번역**
|
|
|
|
**Goal P2-T1**
|
|
- Objective: 일반 카드와 별도 SQL을 쓰는 mostCommentedAudios를 모두 검증한다. 더보기 두 type과 FIRST_AUDIO_CONTENT 공유 호출자를 확인한다. 추천 스냅샷·백그라운드 작업에 요청 범위 의존을 추가하지 않으며, 공유 홈 추천 호출의 기존 응답을 회귀 검증한다.
|
|
- 요구사항: LIST-05, LIST-07, LANG-01/02, TEXT-01/02, COMPAT-01, READ-01, ISOLATE-01
|
|
- 시작 조건: P1-GATE 완료.
|
|
- 완료 증거: 아래 TDD 체크박스, focused test 성공, 실제 결과 기록.
|
|
- 범위 밖: 배너·댓글·닉네임 및 다른 API의 신규 번역 기능.
|
|
- Consumes: 기존 원문·저장된 번역·LangContext.lang.code.
|
|
- Produces: 기존 응답 계약을 유지한 요청 locale별 표시 문자열.
|
|
|
|
**확인/수정 후보 파일**
|
|
|
|
- src/main/kotlin/kr/co/vividnext/sodalive/v2/content/recommendation/application/AudioRecommendationQueryService.kt
|
|
- src/main/kotlin/kr/co/vividnext/sodalive/v2/content/recommendation/port/out/AudioRecommendationQueryPort.kt
|
|
- src/main/kotlin/kr/co/vividnext/sodalive/v2/content/recommendation/adapter/out/persistence/DefaultAudioRecommendationQueryRepository.kt
|
|
- src/main/kotlin/kr/co/vividnext/sodalive/v2/api/content/overview/application/ContentOverviewFacade.kt
|
|
- src/main/kotlin/kr/co/vividnext/sodalive/v2/recommendation/application/HomeRecommendationQueryService.kt
|
|
- src/main/kotlin/kr/co/vividnext/sodalive/v2/recommendation/port/out/HomeRecommendationQueryPort.kt
|
|
- src/main/kotlin/kr/co/vividnext/sodalive/v2/recommendation/adapter/out/persistence/DefaultHomeRecommendationQueryRepository.kt
|
|
|
|
**테스트 파일**
|
|
|
|
- src/test/kotlin/kr/co/vividnext/sodalive/v2/content/recommendation/adapter/out/persistence/DefaultAudioRecommendationQueryRepositoryTest.kt
|
|
- src/test/kotlin/kr/co/vividnext/sodalive/v2/recommendation/adapter/out/persistence/DefaultHomeRecommendationQueryRepositoryTest.kt
|
|
- src/test/kotlin/kr/co/vividnext/sodalive/v2/api/content/overview/application/ContentOverviewFacadeTest.kt
|
|
- src/test/kotlin/kr/co/vividnext/sodalive/v2/api/content/recommendation/adapter/in/web/AudioRecommendationEndToEndTest.kt
|
|
- src/test/kotlin/kr/co/vividnext/sodalive/v2/api/content/overview/adapter/in/web/ContentOverviewEndToEndTest.kt
|
|
- src/test/kotlin/kr/co/vividnext/sodalive/v2/api/home/application/HomeRecommendationFacadeTest.kt
|
|
|
|
- [x] **RED:** 위 테스트에서 대상 경로의 언어 선택·원문 fallback·기존 값 불변을 검증하는 가장 작은 실패 사례를 작성한다.
|
|
- [x] **RED 확인:** 아래 명령으로 미구현 번역 때문에 발생한 제목 assertion 실패를 확인한다. 환경 실패는 RED 증거가 아니다.
|
|
- [x] **GREEN:** 기존 계층에서 locale 전달과 번역 조회/매핑만 최소 구현한다.
|
|
- [x] **GREEN 확인:** 같은 명령으로 의도한 번역·fallback 성공을 확인한다.
|
|
- [x] **REFACTOR:** 이번 변경이 만든 중복만 정리하고 아래 focused test 및 직접 호출자 회귀를 확인한다. 실행 결과를 Task 기록에 누적한다.
|
|
|
|
```bash
|
|
./gradlew test --tests '*DefaultAudioRecommendationQueryRepositoryTest' --tests '*DefaultHomeRecommendationQueryRepositoryTest' --tests '*ContentOverviewFacadeTest' --tests '*AudioRecommendationEndToEndTest' --tests '*ContentOverviewEndToEndTest' --tests '*HomeRecommendationFacadeTest'
|
|
```
|
|
|
|
기대 결과: 종료 코드 0. 번역이 없어서 콘텐츠가 누락되거나 순서·개수·페이지가 달라지는 사례 0건.
|
|
|
|
**Task 실행 기록:**
|
|
|
|
- 2026-09-09: RED는 이전 구현 담당자가 아래 focused command로 포착했다. 원시 Gradle 콘솔 로그는 현재 세션에 보관되어 있지 않아 출력은 재구성하지 않는다. LIST-05 전체 카드와 LIST-07 NEW_AND_HOT_AUDIO/FIRST_AUDIO_CONTENT가 요청 locale 번역 대신 원문 제목을 반환한 assertion 실패였다.
|
|
- 2026-09-09: GREEN 구현 후 저장소에서 locale별 배치 조회와 nonblank title fallback을 적용했다. Oracle 보완으로 같은 fixture의 en→ja→en MockMvc 격리와 빈 문자열·공백·누락·다른 locale fallback, most-commented의 ID 순서·댓글·작성자 프로필 필드 보존을 추가 검증했다.
|
|
- 2026-09-09: Focused: 위 command 실행, `BUILD SUCCESSFUL in 1m 43s`. production 코드는 Oracle 보완에서 변경하지 않았고, `pointAudios`의 기존 개수·인덱스 검증을 누적 fixture와 `rand()` 정렬에 독립적인 fixture ID·가격 조건으로 교체했다.
|
|
- 수동 HTTP는 로컬 실행이 불가능하다는 사용자 확인에 따라 test 서버 P2-GATE에서 대기한다.
|
|
|
|
- [x] **Task 2.2: 랭킹 응답 제목을 요청 언어로 표시**
|
|
|
|
**Goal P2-T2**
|
|
- Objective: 같은 랭킹 스냅샷의 contentId로 요청 locale 번역을 일괄 조회하여 표시 제목에만 반영한다. 번역 없음·공백이면 기존 snapshot.title을 사용한다. 집계 재실행 없이 번역 반영과 rank·rankChange·점수 불변을 검증한다.
|
|
- 요구사항: LIST-06, RANK-01, LANG-01/02, TEXT-01/02, COMPAT-01, READ-01, ISOLATE-01
|
|
- 시작 조건: P2-T1 완료.
|
|
- 완료 증거: 아래 TDD 체크박스, focused test 성공, 실제 결과 기록.
|
|
- 범위 밖: 집계·점수·스냅샷 저장 형식 변경.
|
|
- Consumes: 기존 원문·저장된 번역·LangContext.lang.code.
|
|
- Produces: 기존 응답 계약을 유지한 요청 locale별 표시 문자열.
|
|
|
|
**확인/수정 후보 파일**
|
|
|
|
- src/main/kotlin/kr/co/vividnext/sodalive/v2/content/ranking/application/AudioRankingQueryService.kt
|
|
- src/main/kotlin/kr/co/vividnext/sodalive/v2/content/ranking/port/out/AudioRankingSnapshotPort.kt
|
|
- src/main/kotlin/kr/co/vividnext/sodalive/v2/content/ranking/adapter/out/persistence/DefaultAudioRankingSnapshotPersistenceAdapter.kt
|
|
|
|
**테스트 파일**
|
|
|
|
- src/test/kotlin/kr/co/vividnext/sodalive/v2/content/ranking/application/AudioRankingQueryServiceTest.kt
|
|
- src/test/kotlin/kr/co/vividnext/sodalive/v2/content/ranking/adapter/out/persistence/DefaultAudioRankingSnapshotPersistenceAdapterTest.kt
|
|
- src/test/kotlin/kr/co/vividnext/sodalive/v2/api/content/ranking/adapter/in/web/AudioRankingControllerTest.kt
|
|
|
|
- [x] **RED:** 위 테스트에서 대상 경로의 언어 선택·원문 fallback·기존 값 불변을 검증하는 가장 작은 실패 사례를 작성한다.
|
|
- [x] **RED 확인:** 아래 명령으로 미구현 번역 때문에 발생한 제목 assertion 실패를 확인한다. 환경 실패는 RED 증거가 아니다.
|
|
- [x] **GREEN:** 기존 계층에서 locale 전달과 번역 조회/매핑만 최소 구현한다.
|
|
- [x] **GREEN 확인:** 같은 명령으로 의도한 번역·fallback 성공을 확인한다.
|
|
- [x] **REFACTOR:** 이번 변경이 만든 중복만 정리하고 아래 focused test 및 직접 호출자 회귀를 확인한다. 실행 결과를 Task 기록에 누적한다.
|
|
|
|
```bash
|
|
./gradlew test --tests '*AudioRankingQueryServiceTest' --tests '*DefaultAudioRankingSnapshotPersistenceAdapterTest' --tests '*AudioRankingControllerTest'
|
|
```
|
|
|
|
기대 결과: 종료 코드 0. 번역이 없어서 콘텐츠가 누락되거나 순서·개수·페이지가 달라지는 사례 0건.
|
|
|
|
**Task 실행 기록:**
|
|
|
|
- 2026-09-09: RED: `./gradlew test --tests '*AudioRankingQueryServiceTest' --tests '*DefaultAudioRankingSnapshotPersistenceAdapterTest' --tests '*AudioRankingControllerTest'` 종료 코드 1. `AudioRankingQueryServiceTest.shouldUseRequestedLocaleTitlesWithoutChangingRankingValues`의 `expected: <[en-title-2, en-title-1]> but was: <[audio-2, audio-1]>` 및 `AudioRankingControllerTest.shouldIsolateRequestedLocaleTitlesWithoutChangingRankingResponse`의 `JSON path "$.data.items[0].title" expected:<en-title-2> but was:<audio-2>`를 확인했다. `DefaultAudioRankingSnapshotPersistenceAdapterTest.shouldTranslateSnapshotTitlesInBatchesWithSnapshotFallback`도 snapshot title 반환 assertion으로 실패했다.
|
|
- 2026-09-09: GREEN: `LangContext.lang.code`을 조회 port에만 전달하고, 최신·이전 각 스냅샷 결과 집합에서 `findByContentIdInAndLocale` 한 번으로 유효한 title만 적용했다. 빈 문자열·공백·누락·다른 locale 전용 번역은 각 snapshot.title로 fallback하며, replace/집계/refresh 경로는 변경하지 않았다.
|
|
- 2026-09-09: Focused: 위 명령 종료 코드 0, `BUILD SUCCESSFUL in 40s`. query service의 en→ja→en locale 전달과 title 외 `showRankChange`·type·순서·rank·rankChange·isNew·닉네임·cover URL 불변, 실제 adapter의 최신·이전 조회 배치 8회 statement, controller의 en→ja→en JSON을 검증했다.
|
|
- 2026-09-09: 직접 호출자 회귀: `./gradlew test --tests '*AudioRankingSnapshotRefreshServiceTest'` 종료 코드 0, `BUILD SUCCESSFUL in 6s`.
|
|
- 수동 HTTP는 test 서버 배포 후 P2-GATE에서 수행한다.
|
|
- 2026-09-09: P2-T2 spec-review 보강: `./gradlew test --tests '*DefaultAudioRankingSnapshotPersistenceAdapterTest'` 종료 코드 0, `BUILD SUCCESSFUL in 34s`. `rendered_payload`를 SQL NULL로 저장한 실제 translation row는 converter의 빈 payload로 읽혀 snapshot.title으로 fallback했다. `{ "title": null }` JSON은 non-null `ContentTranslationPayload.title` 계약 밖이라 생성하지 않았다.
|
|
- 2026-09-09: 빈 latest 조회 후 Hibernate `prepareStatementCount=1`, 이어진 빈 previous 조회 후 누적 `=2`를 확인했다. 각 snapshot native SELECT만 발생했고 ContentTranslation SELECT는 0회다. non-empty latest·previous en→ja→en 여섯 조회는 snapshot+batch translation 각 1회씩 총 12 statements였으며, title만 빈 문자열로 정규화한 전체 `AudioRankingSnapshotRecord`가 locale 간 동일했다. latest score base 10~14와 previous 20~24의 final/normalized/raw·count·growth·boost 필드를 포함했고, 재조회한 저장 snapshot title과 모든 score 값도 변경되지 않았다.
|
|
|
|
#### Phase 2 Gate — P2-GATE
|
|
|
|
- Objective: Phase 2의 요청 헤더부터 DB 번역 선택·HTTP 응답까지 확인한다.
|
|
- 시작 조건: P2-T1, P2-T2 완료.
|
|
- 완료 증거: 해당 Task 명령 통과, 아래 수동 확인과 실제 명령·결과·환경 기록.
|
|
- 범위 밖: 운영 데이터 변경, Papago 실행, 테스트 삭제·완화.
|
|
- [x] 해당 Phase의 모든 focused test 명령을 실행하여 종료 코드 0을 확인한다.
|
|
- [ ] test 서버에서 같은 API·query를 유지하고 Accept-Language만 바꿔 GET 요청한다.
|
|
- [ ] ko/en/ja 번역·없는 번역·공백 번역·기본 언어 동작을 실제 JSON에서 확인한다.
|
|
- [ ] 대상 외 필드, 순서·개수·페이지·인증·성인/차단/구매 정책이 보존되는지 확인한다.
|
|
- [ ] 요청별 번역 조회가 항목 수에 비례해 늘지 않고 GET에서 번역 작업/외부 API/쓰기 호출이 없는지 확인한다.
|
|
- [ ] 검증 로그는 인증 토큰·개인정보를 제거해 기록한다.
|
|
|
|
수동 HTTP는 test 서버와 합성 테스트 계정을 사용한다. 인증이 필요한 API는 test 서버용 토큰을 사용하고 문서에 값을 기록하지 않는다.
|
|
test 서버 배포·DB 준비가 안 되면 수동 Gate를 완료 처리하지 않고 원인과 재개 조건을 남긴다.
|
|
|
|
**Phase 2 Gate 실행 기록:**
|
|
|
|
- 2026-09-09: P2-T1 focused command를 `--rerun-tasks`로 강제 재실행했다. 종료 코드 0, `BUILD SUCCESSFUL in 5m 12s`, 10 tasks executed.
|
|
- 2026-09-09: P2-T2 focused command를 `--rerun-tasks`로 강제 재실행했다. 종료 코드 0, `BUILD SUCCESSFUL in 5m 14s`, 10 tasks executed.
|
|
- 2026-09-09: `AudioRankingSnapshotRefreshServiceTest` 직접 호출자 회귀를 실행했다. 종료 코드 0, `BUILD SUCCESSFUL in 6s`.
|
|
- 로컬 수동 테스트는 불가능하다는 사용자 확인에 따라 LIST-01~07 HTTP 검증을 test 서버 배포 후 수행한다.
|
|
|
|
## 실행 순서와 완료 기준
|
|
|
|
P1-T1 → P1-T2 → P1-GATE → P2-T1 → P2-T2 → P2-GATE. 2026-09-09 사용자 지시로 로컬 P1-GATE를 test 서버로 이관하고 P2 자동 구현·검증을 먼저 완료했다.
|
|
한 번에 하나의 Task 또는 Gate만 활성화하며, 완료된 기록은 되돌리거나 삭제하지 않는다.
|
|
|
|
최종 품질 명령:
|
|
|
|
```bash
|
|
./gradlew test --tests 'kr.co.vividnext.sodalive.v2.creator.channel.*' --tests 'kr.co.vividnext.sodalive.v2.content.*' --tests 'kr.co.vividnext.sodalive.v2.api.content.*' --tests 'kr.co.vividnext.sodalive.v2.api.creator.channel.*' --tests '*HomeRecommendation*'
|
|
./gradlew ktlintCheck
|
|
./gradlew assemble
|
|
```
|
|
|
|
기대 결과: 각 자동 검증 명령 종료 코드 0. 대상 7개 API의 실제 HTTP 확인은 test 서버 Gate에서 별도 완료한다.
|
|
전체 test는 공통 인증·언어 파서·serialization 변경 또는 targeted 결과로 영향 판단이 불가능할 때 추가한다.
|
|
생략 시 국소적인 표시값 변경이라는 근거와 대신 실행한 위 영향 범위 회귀를 기록한다.
|
|
초기 문서 작성 단계에서는 위 구현 검증 명령을 실행하지 않았으며, 구현 후 결과는 각 Task/Gate 기록에 누적한다.
|
|
|
|
## 결정 기록
|
|
|
|
PRD의 DEC-LIST-01~07을 따른다.
|
|
기존 파싱·API 계약·원문 fallback을 유지하며, 요청 외 영역으로 확대해야 할 경우 먼저 문서와 사용자 결정을 갱신한다.
|
|
|
|
## 문서 검증 기록
|
|
|
|
- 2026-09-09: 사용자 인터뷰의 원문 fallback과 댓글·닉네임·배너 유지 결정을 PRD와 Task에 반영.
|
|
- 초기 문서 작성 당시 코드·테스트·운영 데이터 변경 없이 구현 Task와 Gate는 전부 미착수였다.
|
|
- 경로 존재 확인 및 필수 Gradle 명령 확인 결과는 아래에 누적한다.
|
|
- 2026-09-09: Task의 기존 소스·테스트 경로 존재를 확인했다. 채널 홈 EndToEndTest는 현재 없으므로 신규 작성 예정으로 구분했다. 구현 완료 체크박스와 템플릿 placeholder가 남아 있지 않음을 확인했다.
|
|
- 2026-09-09: 문서 유지보수 규칙에 따라 `./gradlew tasks --all` 실행. 종료 코드 0, `BUILD SUCCESSFUL in 26s`; test·ktlintCheck·assemble 태스크를 확인했다. 기능 테스트·빌드·HTTP 검증은 구현하지 않은 문서 작업이므로 실행하지 않았다.
|
|
- 2026-09-09: Phase 1·2 전체 영향 범위 test 명령 실행. 종료 코드 0, `BUILD SUCCESSFUL in 3m 23s`.
|
|
- 2026-09-09: 최초 `./gradlew ktlintCheck`에서 신규 랭킹·추천 테스트의 들여쓰기와 긴 줄 위반을 확인해 포맷만 수정했다. 관련 두 repository test 종료 코드 0, 최종 `./gradlew ktlintCheck` 종료 코드 0.
|
|
- 2026-09-09: `./gradlew assemble` 실행. 종료 코드 0, `BUILD SUCCESSFUL in 6s`.
|
|
- 2026-09-09: 전체 `./gradlew test` 실행. 종료 코드 0, `BUILD SUCCESSFUL in 8m 19s`.
|
|
- 2026-09-09: 문서 상태를 Phase 1·2 자동 검증 완료와 test 서버 HTTP Gate 대기로 갱신하고 `./gradlew tasks --all`로 명령 유효성을 재확인했다. 종료 코드 0.
|
|
- 2026-09-09: 전체 영향 범위 회귀 명령을 실행했다. 종료 코드 0, `BUILD SUCCESSFUL in 3m 23s`.
|
|
- 2026-09-09: `./gradlew ktlintCheck` 최초 실행에서 신규 테스트 두 파일의 들여쓰기·긴 줄 위반을 확인해 포맷만 수정했다. 관련 ranking adapter·recommendation repository 테스트 재실행 종료 코드 0, 최종 `ktlintCheck` 종료 코드 0.
|
|
- 2026-09-09: `./gradlew assemble` 실행. 종료 코드 0, `BUILD SUCCESSFUL in 6s`.
|
|
- 2026-09-09: 문서 상태와 test 서버 Gate 갱신 후 `./gradlew tasks --all` 재실행. 종료 코드 0, `BUILD SUCCESSFUL in 928ms`.
|
|
- 로컬 수동 테스트는 불가능하므로 자동 검증 완료 상태로 test 서버 배포를 준비한다. 배포 후 P1-GATE부터 LIST-01~07의 실제 HTTP 응답을 순서대로 확인한다.
|