diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/v2/api/content/overview/application/ContentOverviewQueryPolicy.kt b/src/main/kotlin/kr/co/vividnext/sodalive/v2/api/content/overview/application/ContentOverviewQueryPolicy.kt new file mode 100644 index 00000000..183396f7 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/v2/api/content/overview/application/ContentOverviewQueryPolicy.kt @@ -0,0 +1,38 @@ +package kr.co.vividnext.sodalive.v2.api.content.overview.application + +import kr.co.vividnext.sodalive.v2.api.content.overview.dto.ContentOverviewType + +data class ContentOverviewPage( + val page: Int, + val size: Int +) { + val offset: Long = page.toLong() * size +} + +class ContentOverviewQueryPolicy { + fun resolveType(type: String?): ContentOverviewType { + return ContentOverviewType.from(type) + } + + fun createPage(page: Int?, size: Int?): ContentOverviewPage { + val resolvedPage = (page ?: DEFAULT_PAGE).coerceAtLeast(DEFAULT_PAGE) + val requestedSize = size ?: DEFAULT_SIZE + val resolvedSize = if (requestedSize < MIN_SIZE) DEFAULT_SIZE else minOf(requestedSize, MAX_SIZE) + return ContentOverviewPage(page = resolvedPage, size = resolvedSize) + } + + fun pageItems(items: List, page: ContentOverviewPage): List { + return items.take(page.size) + } + + fun hasNext(items: List, page: ContentOverviewPage): Boolean { + return items.size > page.size + } + + companion object { + const val DEFAULT_PAGE = 0 + const val DEFAULT_SIZE = 20 + const val MIN_SIZE = 20 + const val MAX_SIZE = 50 + } +} diff --git a/src/test/kotlin/kr/co/vividnext/sodalive/v2/api/content/overview/application/ContentOverviewQueryPolicyTest.kt b/src/test/kotlin/kr/co/vividnext/sodalive/v2/api/content/overview/application/ContentOverviewQueryPolicyTest.kt new file mode 100644 index 00000000..01679540 --- /dev/null +++ b/src/test/kotlin/kr/co/vividnext/sodalive/v2/api/content/overview/application/ContentOverviewQueryPolicyTest.kt @@ -0,0 +1,45 @@ +package kr.co.vividnext.sodalive.v2.api.content.overview.application + +import kr.co.vividnext.sodalive.v2.api.content.overview.dto.ContentOverviewType +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Test + +class ContentOverviewQueryPolicyTest { + private val policy = ContentOverviewQueryPolicy() + + @Test + @DisplayName("콘텐츠 전체보기 type은 null 또는 invalid 값을 기본 타입으로 보정한다") + fun shouldResolveTypeWithDefaultFallback() { + assertEquals(ContentOverviewType.NEW_AND_HOT_AUDIO, policy.resolveType(null)) + assertEquals(ContentOverviewType.NEW_AND_HOT_AUDIO, policy.resolveType("UNKNOWN")) + assertEquals(ContentOverviewType.FIRST_AUDIO_CONTENT, policy.resolveType("FIRST_AUDIO_CONTENT")) + } + + @Test + @DisplayName("콘텐츠 전체보기 page와 size를 기본값과 최대값으로 보정한다") + fun shouldNormalizePageAndSize() { + assertEquals(ContentOverviewPage(page = 0, size = 20), policy.createPage(null, null)) + assertEquals(ContentOverviewPage(page = 0, size = 20), policy.createPage(-1, 0)) + assertEquals(ContentOverviewPage(page = 0, size = 20), policy.createPage(0, 19)) + assertEquals(ContentOverviewPage(page = 2, size = 50), policy.createPage(2, 100)) + } + + @Test + @DisplayName("콘텐츠 전체보기 offset은 큰 page 입력에서도 Int overflow 없이 계산한다") + fun shouldCalculateOffsetWithoutIntOverflow() { + val page = policy.createPage(Int.MAX_VALUE, 50) + + assertEquals(Int.MAX_VALUE.toLong() * 50, page.offset) + } + + @Test + @DisplayName("콘텐츠 전체보기 응답 목록과 hasNext는 size + 1 조회 결과로 계산한다") + fun shouldCalculatePageItemsAndHasNext() { + val page = ContentOverviewPage(page = 0, size = 2) + val items = listOf(1, 2, 3) + + assertEquals(listOf(1, 2), policy.pageItems(items, page)) + assertEquals(true, policy.hasNext(items, page)) + } +}