From 8b24e8946533089999cf5de99004c48b66910c50 Mon Sep 17 00:00:00 2001 From: Klaus Date: Sat, 27 Jun 2026 05:10:27 +0900 Subject: [PATCH] =?UTF-8?q?docs(content):=20=EC=BD=98=ED=85=90=EC=B8=A0=20?= =?UTF-8?q?=EC=A0=84=EC=B2=B4=EB=B3=B4=EA=B8=B0=20Phase=201=20=EA=B8=B0?= =?UTF-8?q?=EB=A1=9D=EC=9D=84=20=EA=B0=B1=EC=8B=A0=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../20260627_콘텐츠_전체보기_API/plan-task.md | 31 +++++++++++++------ 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/docs/20260627_콘텐츠_전체보기_API/plan-task.md b/docs/20260627_콘텐츠_전체보기_API/plan-task.md index 883b0b34..ee2bd90a 100644 --- a/docs/20260627_콘텐츠_전체보기_API/plan-task.md +++ b/docs/20260627_콘텐츠_전체보기_API/plan-task.md @@ -157,7 +157,7 @@ data class ContentOverviewPage( val page: Int, val size: Int ) { - val offset: Int = page * size + val offset: Long = page.toLong() * size } class ContentOverviewQueryPolicy { @@ -270,7 +270,7 @@ private fun emptyResponse(type: ContentOverviewType): ContentOverviewPageRespons ### Phase 1: 콘텐츠 전체보기 응답/요청 정책 작성 -- [ ] **Task 1.1: ContentOverview DTO 직렬화 테스트 작성** +- [x] **Task 1.1: ContentOverview DTO 직렬화 테스트 작성** - Files: - Create: `src/test/kotlin/kr/co/vividnext/sodalive/v2/api/content/overview/dto/ContentOverviewPageResponseTest.kt` - Create: `src/main/kotlin/kr/co/vividnext/sodalive/v2/api/content/overview/dto/ContentOverviewPageResponse.kt` @@ -329,8 +329,12 @@ private fun emptyResponse(type: ContentOverviewType): ContentOverviewPageRespons - GREEN: 위 DTO 초안을 추가하고 테스트를 통과시킨다. - 통과 확인 명령: `./gradlew test --tests kr.co.vividnext.sodalive.v2.api.content.overview.dto.ContentOverviewPageResponseTest` - REFACTOR: import 정리 후 같은 테스트를 재실행한다. + - 검증 기록: + - RED: `./gradlew test --tests kr.co.vividnext.sodalive.v2.api.content.overview.dto.ContentOverviewPageResponseTest` 실행 시 `ContentOverviewPageResponse`, `ContentOverviewType`, `ContentOverviewItemResponse` 미구현으로 `compileTestKotlin` 실패. + - GREEN: DTO 구현 후 같은 명령 재실행, `BUILD SUCCESSFUL`. + - REVIEW 보완: `fromFirstAudioContent(...)`가 성인/오리지널 플래그를 전달하는 테스트를 추가했다. 보완 RED는 `isAdult`, `isOriginalSeries` 파라미터 미존재로 `compileTestKotlin` 실패했고, 시그니처 보강 후 같은 DTO 테스트가 `BUILD SUCCESSFUL`. -- [ ] **Task 1.2: ContentOverviewQueryPolicy 테스트와 구현 작성** +- [x] **Task 1.2: ContentOverviewQueryPolicy 테스트와 구현 작성** - Files: - Create: `src/test/kotlin/kr/co/vividnext/sodalive/v2/api/content/overview/application/ContentOverviewQueryPolicyTest.kt` - Create: `src/main/kotlin/kr/co/vividnext/sodalive/v2/api/content/overview/application/ContentOverviewQueryPolicy.kt` @@ -369,6 +373,15 @@ private fun emptyResponse(type: ContentOverviewType): ContentOverviewPageRespons - GREEN: 위 policy 초안을 추가하고 테스트를 통과시킨다. - 통과 확인 명령: `./gradlew test --tests kr.co.vividnext.sodalive.v2.api.content.overview.application.ContentOverviewQueryPolicyTest` - REFACTOR: `ContentOverviewType.from(...)`와 page 보정 로직이 DTO/Facade에 중복되지 않게 유지하고 같은 테스트를 재실행한다. + - 검증 기록: + - RED: `./gradlew test --tests kr.co.vividnext.sodalive.v2.api.content.overview.application.ContentOverviewQueryPolicyTest` 실행 시 `ContentOverviewQueryPolicy`, `ContentOverviewPage` 미구현으로 `compileTestKotlin` 실패. + - GREEN: policy 구현 후 같은 명령 재실행, `BUILD SUCCESSFUL`. + - REVIEW 보완: `size = 19`가 기본 size `20`으로 보정되는 테스트를 추가하고, `MIN_SIZE = 20` 정책을 반영했다. 보완 후 같은 policy 테스트가 `BUILD SUCCESSFUL`. + - REVIEW 보완: 큰 `page` 입력에서 `offset`이 Int overflow 되지 않도록 `offset: Long = page.toLong() * size`로 변경했다. 보완 RED는 `Int.MAX_VALUE, size = 50` offset assertion 실패였고, 수정 후 같은 policy 테스트가 `BUILD SUCCESSFUL`. + - REVIEW 보완: 후속 Phase에서 `ContentOverviewPage.offset`을 그대로 넘길 수 있도록 `RecommendationSnapshotPort`, `HomeRecommendationQueryPort`, 관련 service/adapter/repository offset 계약과 문서 예시를 `Long`으로 정렬했다. + - Phase 1 묶음: `./gradlew test --tests 'kr.co.vividnext.sodalive.v2.api.content.overview.*'` 실행, `BUILD SUCCESSFUL`. + - Lint: `./gradlew ktlintCheck` 실행, `BUILD SUCCESSFUL`. + - 참고: `./gradlew test` 전체 실행은 다수 테스트의 XML 결과 파일 write 실패로 중단되어 Phase 1 로직 실패로 보지 않는다. --- @@ -463,14 +476,14 @@ private fun emptyResponse(type: ContentOverviewType): ContentOverviewPageRespons ) Mockito.doReturn(true).`when`(memberContentPreferenceService).canViewAdultContent(member) Mockito.doReturn(nowSnapshots).`when`(snapshotPort) - .findLatestSnapshots(RecommendedSectionType.NEW_AND_HOT_AUDIO_ALL, offset = 20, limit = 21) + .findLatestSnapshots(RecommendedSectionType.NEW_AND_HOT_AUDIO_ALL, offset = 20L, limit = 21) Mockito.doReturn(listOf(audioCard(3L), audioCard(4L), audioCard(5L))).`when`(queryPort) .findAudioCardsByIds(listOf(3L, 4L, 5L), member.id, true, anyLocalDateTime()) - val result = queryService.findNewAndHotAudios(member, offset = 20, limit = 21) + val result = queryService.findNewAndHotAudios(member, offset = 20L, limit = 21) assertEquals(listOf(3L, 4L, 5L), result.map { it.audioContentId }) - Mockito.verify(snapshotPort).findLatestSnapshots(RecommendedSectionType.NEW_AND_HOT_AUDIO_ALL, offset = 20, limit = 21) + Mockito.verify(snapshotPort).findLatestSnapshots(RecommendedSectionType.NEW_AND_HOT_AUDIO_ALL, offset = 20L, limit = 21) } ``` - 실패 확인 명령: `./gradlew test --tests kr.co.vividnext.sodalive.v2.content.recommendation.application.AudioRecommendationQueryServiceTest` @@ -478,7 +491,7 @@ private fun emptyResponse(type: ContentOverviewType): ContentOverviewPageRespons - GREEN: `AudioRecommendationQueryService.findNewAndHotAudios(member, offset, limit)`를 추가한다. - 구현 기준: ```kotlin - fun findNewAndHotAudios(member: Member, offset: Int, limit: Int): List { + fun findNewAndHotAudios(member: Member, offset: Long, limit: Int): List { val now = LocalDateTime.now() val canViewAdultContent = canViewAdultContent(member) val visibility = if (canViewAdultContent) AudioRecommendationVisibility.ALL else AudioRecommendationVisibility.SAFE @@ -525,7 +538,7 @@ private fun emptyResponse(type: ContentOverviewType): ContentOverviewPageRespons fun shouldReturnNewAndHotPage() { val member = member(id = 10L) Mockito.doReturn(listOf(audioCard(1L), audioCard(2L), audioCard(3L))).`when`(audioRecommendationQueryService) - .findNewAndHotAudios(member, offset = 0, limit = 3) + .findNewAndHotAudios(member, offset = 0L, limit = 3) val response = facade.getContents("NEW_AND_HOT_AUDIO", page = 0, size = 2, member = member) @@ -540,7 +553,7 @@ private fun emptyResponse(type: ContentOverviewType): ContentOverviewPageRespons val member = member(id = 10L) Mockito.doReturn(true).`when`(memberContentPreferenceService).canViewAdultContent(member) Mockito.doReturn(listOf(firstAudio(1L), firstAudio(2L))).`when`(homeRecommendationQueryService) - .findFirstAudioContents(anyLocalDateTime(), offset = 20, limit = 21, memberId = member.id, includeAdultContents = true) + .findFirstAudioContents(anyLocalDateTime(), offset = 20L, limit = 21, memberId = member.id, includeAdultContents = true) val response = facade.getContents("FIRST_AUDIO_CONTENT", page = 1, size = 20, member = member)