test #426
@@ -0,0 +1,128 @@
|
|||||||
|
package kr.co.vividnext.sodalive.v2.creator.channel.series.domain
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.creator.admin.content.series.SeriesPublishedDaysOfWeek
|
||||||
|
import kr.co.vividnext.sodalive.v2.common.domain.ContentSort
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.live.domain.CreatorChannelPage
|
||||||
|
import org.springframework.stereotype.Component
|
||||||
|
|
||||||
|
@Component
|
||||||
|
class CreatorChannelSeriesQueryPolicy {
|
||||||
|
fun resolveSort(sort: String?): ContentSort {
|
||||||
|
return runCatching { ContentSort.valueOf(sort ?: ContentSort.LATEST.name) }
|
||||||
|
.getOrDefault(ContentSort.LATEST)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createPage(page: Int?, size: Int?): CreatorChannelPage {
|
||||||
|
return CreatorChannelPage(
|
||||||
|
page = page?.coerceAtLeast(MIN_PAGE) ?: DEFAULT_PAGE,
|
||||||
|
size = size?.coerceIn(MIN_PAGE_SIZE, MAX_PAGE_SIZE) ?: DEFAULT_PAGE_SIZE
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T> limitItems(fetched: List<T>, page: CreatorChannelPage): List<T> {
|
||||||
|
return fetched.take(page.size)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun hasNext(fetched: List<*>, page: CreatorChannelPage): Boolean {
|
||||||
|
return fetched.size > page.size
|
||||||
|
}
|
||||||
|
|
||||||
|
fun purchaseRate(paidContentCount: Int, purchasedContentCount: Int): Int {
|
||||||
|
if (paidContentCount == 0) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return purchasedContentCount * 100 / paidContentCount
|
||||||
|
}
|
||||||
|
|
||||||
|
fun publishedDaysOfWeekText(days: Set<SeriesPublishedDaysOfWeek>, locale: String): String {
|
||||||
|
if (days.contains(SeriesPublishedDaysOfWeek.RANDOM)) {
|
||||||
|
return randomText(locale)
|
||||||
|
}
|
||||||
|
if (days.containsAll(WEEKDAYS)) {
|
||||||
|
return everyDayText(locale)
|
||||||
|
}
|
||||||
|
|
||||||
|
val dayText = WEEKDAYS
|
||||||
|
.filter(days::contains)
|
||||||
|
.joinToString(", ") { dayText(it, locale) }
|
||||||
|
|
||||||
|
return weeklyText(dayText, locale)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun randomText(locale: String): String {
|
||||||
|
return when (locale) {
|
||||||
|
"en" -> "Random"
|
||||||
|
"ja" -> "ランダム"
|
||||||
|
else -> "랜덤"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun everyDayText(locale: String): String {
|
||||||
|
return when (locale) {
|
||||||
|
"en" -> "Every day"
|
||||||
|
"ja" -> "毎日"
|
||||||
|
else -> "매일"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun weeklyText(dayText: String, locale: String): String {
|
||||||
|
return when (locale) {
|
||||||
|
"en" -> "Every $dayText"
|
||||||
|
"ja" -> "毎週 $dayText"
|
||||||
|
else -> "매주 $dayText"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun dayText(day: SeriesPublishedDaysOfWeek, locale: String): String {
|
||||||
|
return when (locale) {
|
||||||
|
"en" -> EN_DAY_TEXTS.getValue(day)
|
||||||
|
"ja" -> JA_DAY_TEXTS.getValue(day)
|
||||||
|
else -> KO_DAY_TEXTS.getValue(day)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val DEFAULT_PAGE = 0
|
||||||
|
private const val DEFAULT_PAGE_SIZE = 20
|
||||||
|
private const val MIN_PAGE = 0
|
||||||
|
private const val MIN_PAGE_SIZE = 20
|
||||||
|
private const val MAX_PAGE_SIZE = 50
|
||||||
|
|
||||||
|
private val WEEKDAYS = listOf(
|
||||||
|
SeriesPublishedDaysOfWeek.SUN,
|
||||||
|
SeriesPublishedDaysOfWeek.MON,
|
||||||
|
SeriesPublishedDaysOfWeek.TUE,
|
||||||
|
SeriesPublishedDaysOfWeek.WED,
|
||||||
|
SeriesPublishedDaysOfWeek.THU,
|
||||||
|
SeriesPublishedDaysOfWeek.FRI,
|
||||||
|
SeriesPublishedDaysOfWeek.SAT
|
||||||
|
)
|
||||||
|
private val KO_DAY_TEXTS = mapOf(
|
||||||
|
SeriesPublishedDaysOfWeek.SUN to "일",
|
||||||
|
SeriesPublishedDaysOfWeek.MON to "월",
|
||||||
|
SeriesPublishedDaysOfWeek.TUE to "화",
|
||||||
|
SeriesPublishedDaysOfWeek.WED to "수",
|
||||||
|
SeriesPublishedDaysOfWeek.THU to "목",
|
||||||
|
SeriesPublishedDaysOfWeek.FRI to "금",
|
||||||
|
SeriesPublishedDaysOfWeek.SAT to "토"
|
||||||
|
)
|
||||||
|
private val EN_DAY_TEXTS = mapOf(
|
||||||
|
SeriesPublishedDaysOfWeek.SUN to "Sun",
|
||||||
|
SeriesPublishedDaysOfWeek.MON to "Mon",
|
||||||
|
SeriesPublishedDaysOfWeek.TUE to "Tue",
|
||||||
|
SeriesPublishedDaysOfWeek.WED to "Wed",
|
||||||
|
SeriesPublishedDaysOfWeek.THU to "Thu",
|
||||||
|
SeriesPublishedDaysOfWeek.FRI to "Fri",
|
||||||
|
SeriesPublishedDaysOfWeek.SAT to "Sat"
|
||||||
|
)
|
||||||
|
private val JA_DAY_TEXTS = mapOf(
|
||||||
|
SeriesPublishedDaysOfWeek.SUN to "日",
|
||||||
|
SeriesPublishedDaysOfWeek.MON to "月",
|
||||||
|
SeriesPublishedDaysOfWeek.TUE to "火",
|
||||||
|
SeriesPublishedDaysOfWeek.WED to "水",
|
||||||
|
SeriesPublishedDaysOfWeek.THU to "木",
|
||||||
|
SeriesPublishedDaysOfWeek.FRI to "金",
|
||||||
|
SeriesPublishedDaysOfWeek.SAT to "土"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,150 @@
|
|||||||
|
package kr.co.vividnext.sodalive.v2.creator.channel.series.domain
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.creator.admin.content.series.SeriesPublishedDaysOfWeek
|
||||||
|
import kr.co.vividnext.sodalive.creator.admin.content.series.SeriesState
|
||||||
|
import kr.co.vividnext.sodalive.member.MemberRole
|
||||||
|
import kr.co.vividnext.sodalive.v2.common.domain.ContentSort
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.series.port.out.CreatorChannelSeriesCreatorRecord
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.series.port.out.CreatorChannelSeriesRecord
|
||||||
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
import org.junit.jupiter.api.Assertions.assertFalse
|
||||||
|
import org.junit.jupiter.api.Assertions.assertNull
|
||||||
|
import org.junit.jupiter.api.Assertions.assertTrue
|
||||||
|
import org.junit.jupiter.api.DisplayName
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
|
class CreatorChannelSeriesQueryPolicyTest {
|
||||||
|
private val policy = CreatorChannelSeriesQueryPolicy()
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("시리즈 탭 sort 정책은 null과 알 수 없는 값을 LATEST로 fallback한다")
|
||||||
|
fun shouldFallbackInvalidSortToLatest() {
|
||||||
|
assertEquals(ContentSort.LATEST, policy.resolveSort(null))
|
||||||
|
assertEquals(ContentSort.LATEST, policy.resolveSort("UNKNOWN"))
|
||||||
|
assertEquals(ContentSort.POPULAR, policy.resolveSort("POPULAR"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("시리즈 탭 page 정책은 page와 size를 fallback하고 fetch limit을 계산한다")
|
||||||
|
fun shouldFallbackPageAndSizeForSeriesTab() {
|
||||||
|
val minimumPage = policy.createPage(page = -1, size = 10)
|
||||||
|
val maximumPage = policy.createPage(page = 2, size = 100)
|
||||||
|
|
||||||
|
assertEquals(0, minimumPage.page)
|
||||||
|
assertEquals(20, minimumPage.size)
|
||||||
|
assertEquals(0L, minimumPage.offset)
|
||||||
|
assertEquals(21, minimumPage.fetchLimit)
|
||||||
|
assertEquals(2, maximumPage.page)
|
||||||
|
assertEquals(50, maximumPage.size)
|
||||||
|
assertEquals(100L, maximumPage.offset)
|
||||||
|
assertEquals(51, maximumPage.fetchLimit)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("시리즈 탭 목록 정책은 요청 size만 남기고 다음 페이지 여부를 계산한다")
|
||||||
|
fun shouldLimitItemsAndCalculateHasNext() {
|
||||||
|
val page = policy.createPage(page = 0, size = 20)
|
||||||
|
val fetched = (1..21).toList()
|
||||||
|
|
||||||
|
val items = policy.limitItems(fetched, page)
|
||||||
|
|
||||||
|
assertEquals((1..20).toList(), items)
|
||||||
|
assertTrue(policy.hasNext(fetched, page))
|
||||||
|
assertFalse(policy.hasNext((1..20).toList(), page))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("시리즈 탭 구매율은 유료 콘텐츠가 없으면 0이고 있으면 정수 백분율로 계산한다")
|
||||||
|
fun shouldCalculatePurchaseRateAsInteger() {
|
||||||
|
assertEquals(0, policy.purchaseRate(paidContentCount = 0, purchasedContentCount = 3))
|
||||||
|
assertEquals(75, policy.purchaseRate(paidContentCount = 4, purchasedContentCount = 3))
|
||||||
|
assertEquals(66, policy.purchaseRate(paidContentCount = 3, purchasedContentCount = 2))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("시리즈 탭 연재 요일은 RANDOM 포함 시 다른 요일을 무시하고 locale별 랜덤 문구를 반환한다")
|
||||||
|
fun shouldReturnRandomTextWhenDaysContainRandom() {
|
||||||
|
val days = setOf(SeriesPublishedDaysOfWeek.RANDOM, SeriesPublishedDaysOfWeek.MON)
|
||||||
|
|
||||||
|
assertEquals("랜덤", policy.publishedDaysOfWeekText(days, "ko"))
|
||||||
|
assertEquals("Random", policy.publishedDaysOfWeekText(days, "en"))
|
||||||
|
assertEquals("ランダム", policy.publishedDaysOfWeekText(days, "ja"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("시리즈 탭 연재 요일은 7개 요일이면 locale별 매일 문구를 반환한다")
|
||||||
|
fun shouldReturnEveryDayTextWhenDaysContainAllWeekdays() {
|
||||||
|
val days = setOf(
|
||||||
|
SeriesPublishedDaysOfWeek.SUN,
|
||||||
|
SeriesPublishedDaysOfWeek.MON,
|
||||||
|
SeriesPublishedDaysOfWeek.TUE,
|
||||||
|
SeriesPublishedDaysOfWeek.WED,
|
||||||
|
SeriesPublishedDaysOfWeek.THU,
|
||||||
|
SeriesPublishedDaysOfWeek.FRI,
|
||||||
|
SeriesPublishedDaysOfWeek.SAT
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals("매일", policy.publishedDaysOfWeekText(days, "ko"))
|
||||||
|
assertEquals("Every day", policy.publishedDaysOfWeekText(days, "en"))
|
||||||
|
assertEquals("毎日", policy.publishedDaysOfWeekText(days, "ja"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("시리즈 탭 연재 요일은 SUN부터 SAT 순서로 locale별 매주 문구를 반환한다")
|
||||||
|
fun shouldReturnWeeklyTextOrderedFromSundayToSaturday() {
|
||||||
|
val days = setOf(SeriesPublishedDaysOfWeek.SAT, SeriesPublishedDaysOfWeek.MON, SeriesPublishedDaysOfWeek.THU)
|
||||||
|
|
||||||
|
assertEquals("매주 월, 목, 토", policy.publishedDaysOfWeekText(days, "ko"))
|
||||||
|
assertEquals("Every Mon, Thu, Sat", policy.publishedDaysOfWeekText(days, "en"))
|
||||||
|
assertEquals("毎週 月, 木, 土", policy.publishedDaysOfWeekText(days, "ja"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("시리즈 탭 domain model과 port record는 Phase 1 계약 필드를 유지한다")
|
||||||
|
fun shouldKeepDomainAndPortContract() {
|
||||||
|
val tab = CreatorChannelSeriesTab(
|
||||||
|
seriesCount = 1,
|
||||||
|
series = listOf(
|
||||||
|
CreatorChannelSeries(
|
||||||
|
seriesId = 10L,
|
||||||
|
title = "title",
|
||||||
|
coverImageUrl = null,
|
||||||
|
publishedDaysOfWeek = "매일",
|
||||||
|
isOriginal = true,
|
||||||
|
isAdult = false,
|
||||||
|
isProceeding = true,
|
||||||
|
contentCount = 3,
|
||||||
|
purchasedContentCount = null,
|
||||||
|
paidContentCount = null,
|
||||||
|
purchasedPaidContentRate = null
|
||||||
|
)
|
||||||
|
),
|
||||||
|
sort = ContentSort.LATEST,
|
||||||
|
page = policy.createPage(page = 0, size = 20),
|
||||||
|
hasNext = false
|
||||||
|
)
|
||||||
|
val creatorRecord = CreatorChannelSeriesCreatorRecord(
|
||||||
|
creatorId = 1L,
|
||||||
|
role = MemberRole.CREATOR,
|
||||||
|
nickname = "creator"
|
||||||
|
)
|
||||||
|
val seriesRecord = CreatorChannelSeriesRecord(
|
||||||
|
seriesId = 10L,
|
||||||
|
title = "title",
|
||||||
|
coverImagePath = null,
|
||||||
|
publishedDaysOfWeek = setOf(SeriesPublishedDaysOfWeek.MON),
|
||||||
|
isOriginal = true,
|
||||||
|
isAdult = false,
|
||||||
|
state = SeriesState.PROCEEDING,
|
||||||
|
contentCount = 3,
|
||||||
|
purchasedContentCount = null,
|
||||||
|
paidContentCount = null
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals(1, tab.seriesCount)
|
||||||
|
assertTrue(tab.series.first().isProceeding)
|
||||||
|
assertNull(tab.series.first().purchasedPaidContentRate)
|
||||||
|
assertEquals(MemberRole.CREATOR, creatorRecord.role)
|
||||||
|
assertEquals(setOf(SeriesPublishedDaysOfWeek.MON), seriesRecord.publishedDaysOfWeek)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user