docs(content): 콘텐츠 전체보기 Phase 1 기록을 갱신한다
This commit is contained in:
@@ -157,7 +157,7 @@ data class ContentOverviewPage(
|
|||||||
val page: Int,
|
val page: Int,
|
||||||
val size: Int
|
val size: Int
|
||||||
) {
|
) {
|
||||||
val offset: Int = page * size
|
val offset: Long = page.toLong() * size
|
||||||
}
|
}
|
||||||
|
|
||||||
class ContentOverviewQueryPolicy {
|
class ContentOverviewQueryPolicy {
|
||||||
@@ -270,7 +270,7 @@ private fun emptyResponse(type: ContentOverviewType): ContentOverviewPageRespons
|
|||||||
|
|
||||||
### Phase 1: 콘텐츠 전체보기 응답/요청 정책 작성
|
### Phase 1: 콘텐츠 전체보기 응답/요청 정책 작성
|
||||||
|
|
||||||
- [ ] **Task 1.1: ContentOverview DTO 직렬화 테스트 작성**
|
- [x] **Task 1.1: ContentOverview DTO 직렬화 테스트 작성**
|
||||||
- Files:
|
- Files:
|
||||||
- Create: `src/test/kotlin/kr/co/vividnext/sodalive/v2/api/content/overview/dto/ContentOverviewPageResponseTest.kt`
|
- 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`
|
- 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 초안을 추가하고 테스트를 통과시킨다.
|
- GREEN: 위 DTO 초안을 추가하고 테스트를 통과시킨다.
|
||||||
- 통과 확인 명령: `./gradlew test --tests kr.co.vividnext.sodalive.v2.api.content.overview.dto.ContentOverviewPageResponseTest`
|
- 통과 확인 명령: `./gradlew test --tests kr.co.vividnext.sodalive.v2.api.content.overview.dto.ContentOverviewPageResponseTest`
|
||||||
- REFACTOR: import 정리 후 같은 테스트를 재실행한다.
|
- 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:
|
- Files:
|
||||||
- Create: `src/test/kotlin/kr/co/vividnext/sodalive/v2/api/content/overview/application/ContentOverviewQueryPolicyTest.kt`
|
- 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`
|
- 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 초안을 추가하고 테스트를 통과시킨다.
|
- GREEN: 위 policy 초안을 추가하고 테스트를 통과시킨다.
|
||||||
- 통과 확인 명령: `./gradlew test --tests kr.co.vividnext.sodalive.v2.api.content.overview.application.ContentOverviewQueryPolicyTest`
|
- 통과 확인 명령: `./gradlew test --tests kr.co.vividnext.sodalive.v2.api.content.overview.application.ContentOverviewQueryPolicyTest`
|
||||||
- REFACTOR: `ContentOverviewType.from(...)`와 page 보정 로직이 DTO/Facade에 중복되지 않게 유지하고 같은 테스트를 재실행한다.
|
- 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(true).`when`(memberContentPreferenceService).canViewAdultContent(member)
|
||||||
Mockito.doReturn(nowSnapshots).`when`(snapshotPort)
|
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)
|
Mockito.doReturn(listOf(audioCard(3L), audioCard(4L), audioCard(5L))).`when`(queryPort)
|
||||||
.findAudioCardsByIds(listOf(3L, 4L, 5L), member.id, true, anyLocalDateTime())
|
.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 })
|
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`
|
- 실패 확인 명령: `./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)`를 추가한다.
|
- GREEN: `AudioRecommendationQueryService.findNewAndHotAudios(member, offset, limit)`를 추가한다.
|
||||||
- 구현 기준:
|
- 구현 기준:
|
||||||
```kotlin
|
```kotlin
|
||||||
fun findNewAndHotAudios(member: Member, offset: Int, limit: Int): List<AudioCard> {
|
fun findNewAndHotAudios(member: Member, offset: Long, limit: Int): List<AudioCard> {
|
||||||
val now = LocalDateTime.now()
|
val now = LocalDateTime.now()
|
||||||
val canViewAdultContent = canViewAdultContent(member)
|
val canViewAdultContent = canViewAdultContent(member)
|
||||||
val visibility = if (canViewAdultContent) AudioRecommendationVisibility.ALL else AudioRecommendationVisibility.SAFE
|
val visibility = if (canViewAdultContent) AudioRecommendationVisibility.ALL else AudioRecommendationVisibility.SAFE
|
||||||
@@ -525,7 +538,7 @@ private fun emptyResponse(type: ContentOverviewType): ContentOverviewPageRespons
|
|||||||
fun shouldReturnNewAndHotPage() {
|
fun shouldReturnNewAndHotPage() {
|
||||||
val member = member(id = 10L)
|
val member = member(id = 10L)
|
||||||
Mockito.doReturn(listOf(audioCard(1L), audioCard(2L), audioCard(3L))).`when`(audioRecommendationQueryService)
|
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)
|
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)
|
val member = member(id = 10L)
|
||||||
Mockito.doReturn(true).`when`(memberContentPreferenceService).canViewAdultContent(member)
|
Mockito.doReturn(true).`when`(memberContentPreferenceService).canViewAdultContent(member)
|
||||||
Mockito.doReturn(listOf(firstAudio(1L), firstAudio(2L))).`when`(homeRecommendationQueryService)
|
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)
|
val response = facade.getContents("FIRST_AUDIO_CONTENT", page = 1, size = 20, member = member)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user