feat(creator): 채널 홈 조회 정책을 추가한다
This commit is contained in:
@@ -0,0 +1,37 @@
|
|||||||
|
package kr.co.vividnext.sodalive.v2.creator.channel.domain
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.v2.common.domain.CreatorActivityType
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
|
||||||
|
class CreatorChannelHomeQueryPolicy {
|
||||||
|
fun limitSchedules(
|
||||||
|
schedules: List<CreatorChannelSchedule>,
|
||||||
|
now: LocalDateTime
|
||||||
|
): List<CreatorChannelSchedule> {
|
||||||
|
return schedules
|
||||||
|
.filter { it.scheduledAt > now }
|
||||||
|
.sortedWith(compareBy<CreatorChannelSchedule> { it.scheduledAt }.thenBy { it.type.scheduleOrder() })
|
||||||
|
.take(3)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun excludeLatestAudioContent(
|
||||||
|
audioContents: List<CreatorChannelAudioContent>,
|
||||||
|
latestAudioContentId: Long?
|
||||||
|
): List<CreatorChannelAudioContent> {
|
||||||
|
return audioContents.filter { it.audioContentId != latestAudioContentId }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun markFirstAudioContent(audioContents: List<CreatorChannelAudioContent>): List<CreatorChannelAudioContent> {
|
||||||
|
val firstAudioContentId = audioContents
|
||||||
|
.minWithOrNull(compareBy<CreatorChannelAudioContent> { it.publishedAt }.thenBy { it.audioContentId })
|
||||||
|
?.audioContentId
|
||||||
|
|
||||||
|
return audioContents.map { audioContent ->
|
||||||
|
audioContent.copy(isFirstContent = audioContent.audioContentId == firstAudioContentId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun CreatorActivityType.scheduleOrder(): Int {
|
||||||
|
return if (this == CreatorActivityType.LIVE) 0 else 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
package kr.co.vividnext.sodalive.v2.creator.channel.domain
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.v2.common.domain.CreatorActivityType
|
||||||
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
import org.junit.jupiter.api.Assertions.assertFalse
|
||||||
|
import org.junit.jupiter.api.Assertions.assertTrue
|
||||||
|
import org.junit.jupiter.api.DisplayName
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
|
||||||
|
class CreatorChannelHomeQueryPolicyTest {
|
||||||
|
private val policy = CreatorChannelHomeQueryPolicy()
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("스케줄은 예약 시각 오름차순 최대 3개만 남긴다")
|
||||||
|
fun shouldLimitSchedulesToEarliestThree() {
|
||||||
|
val now = LocalDateTime.of(2026, 6, 12, 9, 0)
|
||||||
|
val schedules = listOf(
|
||||||
|
schedule(targetId = 4L, scheduledAt = LocalDateTime.of(2026, 6, 12, 13, 0)),
|
||||||
|
schedule(targetId = 2L, scheduledAt = LocalDateTime.of(2026, 6, 12, 11, 0)),
|
||||||
|
schedule(targetId = 1L, scheduledAt = LocalDateTime.of(2026, 6, 12, 10, 0)),
|
||||||
|
schedule(targetId = 3L, scheduledAt = LocalDateTime.of(2026, 6, 12, 12, 0))
|
||||||
|
)
|
||||||
|
|
||||||
|
val limited = policy.limitSchedules(schedules, now)
|
||||||
|
|
||||||
|
assertEquals(listOf(1L, 2L, 3L), limited.map { it.targetId })
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("스케줄은 현재 시각 이후 예약만 남긴다")
|
||||||
|
fun shouldOnlyKeepSchedulesAfterNow() {
|
||||||
|
val now = LocalDateTime.of(2026, 6, 12, 10, 0)
|
||||||
|
val schedules = listOf(
|
||||||
|
schedule(targetId = 1L, scheduledAt = now.minusMinutes(1)),
|
||||||
|
schedule(targetId = 2L, scheduledAt = now),
|
||||||
|
schedule(targetId = 3L, scheduledAt = now.plusMinutes(1))
|
||||||
|
)
|
||||||
|
|
||||||
|
val limited = policy.limitSchedules(schedules, now)
|
||||||
|
|
||||||
|
assertEquals(listOf(3L), limited.map { it.targetId })
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("같은 예약 시각이면 라이브가 오디오보다 먼저 온다")
|
||||||
|
fun shouldSortLiveBeforeAudioWhenScheduledAtIsSame() {
|
||||||
|
val scheduledAt = LocalDateTime.of(2026, 6, 12, 10, 0)
|
||||||
|
val schedules = listOf(
|
||||||
|
schedule(targetId = 2L, scheduledAt = scheduledAt, type = CreatorActivityType.AUDIO),
|
||||||
|
schedule(targetId = 1L, scheduledAt = scheduledAt, type = CreatorActivityType.LIVE)
|
||||||
|
)
|
||||||
|
|
||||||
|
val limited = policy.limitSchedules(schedules, scheduledAt.minusMinutes(1))
|
||||||
|
|
||||||
|
assertEquals(listOf(CreatorActivityType.LIVE, CreatorActivityType.AUDIO), limited.map { it.type })
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("오디오 목록에서는 latestAudioContentId와 같은 콘텐츠를 제외한다")
|
||||||
|
fun shouldExcludeLatestAudioContent() {
|
||||||
|
val audioContents = listOf(audioContent(1L), audioContent(2L), audioContent(3L))
|
||||||
|
|
||||||
|
val filtered = policy.excludeLatestAudioContent(audioContents, latestAudioContentId = 2L)
|
||||||
|
|
||||||
|
assertEquals(listOf(1L, 3L), filtered.map { it.audioContentId })
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("오디오 콘텐츠의 첫 공개 콘텐츠 여부는 공개 시각 오름차순, 동일 시각이면 id 오름차순으로 판정한다")
|
||||||
|
fun shouldMarkFirstAudioContentByPublishedAtAndId() {
|
||||||
|
val publishedAt = LocalDateTime.of(2026, 6, 12, 10, 0)
|
||||||
|
val audioContents = listOf(
|
||||||
|
audioContent(3L, publishedAt = publishedAt.plusDays(1)),
|
||||||
|
audioContent(2L, publishedAt = publishedAt),
|
||||||
|
audioContent(1L, publishedAt = publishedAt)
|
||||||
|
)
|
||||||
|
|
||||||
|
val marked = policy.markFirstAudioContent(audioContents)
|
||||||
|
|
||||||
|
assertTrue(marked.first { it.audioContentId == 1L }.isFirstContent)
|
||||||
|
assertFalse(marked.first { it.audioContentId == 2L }.isFirstContent)
|
||||||
|
assertFalse(marked.first { it.audioContentId == 3L }.isFirstContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun schedule(
|
||||||
|
targetId: Long,
|
||||||
|
scheduledAt: LocalDateTime,
|
||||||
|
type: CreatorActivityType = CreatorActivityType.LIVE
|
||||||
|
): CreatorChannelSchedule {
|
||||||
|
return CreatorChannelSchedule(
|
||||||
|
scheduledAt = scheduledAt,
|
||||||
|
title = "schedule-$targetId",
|
||||||
|
type = type,
|
||||||
|
targetId = targetId
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun audioContent(
|
||||||
|
audioContentId: Long,
|
||||||
|
publishedAt: LocalDateTime = LocalDateTime.of(2026, 6, 12, 10, 0)
|
||||||
|
): CreatorChannelAudioContent {
|
||||||
|
return CreatorChannelAudioContent(
|
||||||
|
audioContentId = audioContentId,
|
||||||
|
title = "audio-$audioContentId",
|
||||||
|
duration = null,
|
||||||
|
imageUrl = null,
|
||||||
|
price = 0,
|
||||||
|
isAdult = false,
|
||||||
|
isPointAvailable = false,
|
||||||
|
isFirstContent = false,
|
||||||
|
publishedAt = publishedAt,
|
||||||
|
seriesName = null,
|
||||||
|
isOriginalSeries = null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user