feat(content-ranking): 랭킹 스냅샷 저장소를 추가한다
This commit is contained in:
+198
@@ -0,0 +1,198 @@
|
||||
package kr.co.vividnext.sodalive.v2.content.ranking.adapter.out.persistence
|
||||
|
||||
import kr.co.vividnext.sodalive.configs.QueryDslConfig
|
||||
import kr.co.vividnext.sodalive.v2.content.ranking.domain.AudioRankingType
|
||||
import kr.co.vividnext.sodalive.v2.content.ranking.port.out.AudioRankingSnapshotRecord
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.DisplayName
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest
|
||||
import org.springframework.context.annotation.Import
|
||||
import java.time.LocalDateTime
|
||||
|
||||
@DataJpaTest(
|
||||
properties = [
|
||||
"spring.cache.type=none",
|
||||
"spring.datasource.url=jdbc:h2:mem:testdb;MODE=MySQL;NON_KEYWORDS=VALUE"
|
||||
]
|
||||
)
|
||||
@Import(QueryDslConfig::class)
|
||||
class DefaultAudioRankingSnapshotPersistenceAdapterTest @Autowired constructor(
|
||||
private val repository: AudioRankingSnapshotRepository
|
||||
) {
|
||||
private val adapter = DefaultAudioRankingSnapshotPersistenceAdapter(repository)
|
||||
|
||||
@Test
|
||||
@DisplayName("최신 visible 스냅샷만 랭킹 타입별 rank 순서로 조회한다")
|
||||
fun shouldFindLatestVisibleSnapshotsByRankingTypeAndVisibleFromAt() {
|
||||
val previousVisibleAt = LocalDateTime.of(2026, 6, 1, 0, 0)
|
||||
val latestVisibleAt = LocalDateTime.of(2026, 6, 8, 0, 0)
|
||||
val hiddenVisibleAt = LocalDateTime.of(2026, 6, 15, 0, 0)
|
||||
repository.saveAll(
|
||||
listOf(
|
||||
snapshot(
|
||||
contentId = 1L,
|
||||
rankingType = AudioRankingType.WEEKLY_POPULAR,
|
||||
visibleFromAtUtc = previousVisibleAt
|
||||
),
|
||||
snapshot(
|
||||
contentId = 2L,
|
||||
rankingType = AudioRankingType.WEEKLY_POPULAR,
|
||||
visibleFromAtUtc = latestVisibleAt,
|
||||
rank = 2
|
||||
),
|
||||
snapshot(
|
||||
contentId = 3L,
|
||||
rankingType = AudioRankingType.WEEKLY_POPULAR,
|
||||
visibleFromAtUtc = latestVisibleAt,
|
||||
rank = 1
|
||||
),
|
||||
snapshot(
|
||||
contentId = 4L,
|
||||
rankingType = AudioRankingType.WEEKLY_POPULAR,
|
||||
visibleFromAtUtc = hiddenVisibleAt
|
||||
),
|
||||
snapshot(contentId = 5L, rankingType = AudioRankingType.RISING, visibleFromAtUtc = latestVisibleAt)
|
||||
)
|
||||
)
|
||||
|
||||
val snapshots = adapter.findLatestVisibleSnapshots(
|
||||
rankingType = AudioRankingType.WEEKLY_POPULAR,
|
||||
nowUtc = LocalDateTime.of(2026, 6, 8, 23, 59)
|
||||
)
|
||||
|
||||
assertEquals(listOf(3L, 2L), snapshots.map { it.contentId })
|
||||
assertEquals(listOf(latestVisibleAt, latestVisibleAt), snapshots.map { it.visibleFromAtUtc })
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("09시 전 생성된 신규 스냅샷은 visible 전까지 이전 visible 스냅샷을 반환한다")
|
||||
fun shouldReturnPreviousVisibleSnapshotsBeforeNewVisibleFromAt() {
|
||||
val previousStartAt = LocalDateTime.of(2026, 5, 24, 15, 0)
|
||||
val previousEndAt = LocalDateTime.of(2026, 5, 31, 15, 0)
|
||||
val currentStartAt = LocalDateTime.of(2026, 5, 31, 15, 0)
|
||||
val currentEndAt = LocalDateTime.of(2026, 6, 7, 15, 0)
|
||||
repository.saveAll(
|
||||
listOf(
|
||||
snapshot(
|
||||
contentId = 1L,
|
||||
aggregationStartAtUtc = previousStartAt,
|
||||
aggregationEndAtUtc = previousEndAt,
|
||||
visibleFromAtUtc = LocalDateTime.of(2026, 6, 1, 0, 0)
|
||||
),
|
||||
snapshot(
|
||||
contentId = 2L,
|
||||
aggregationStartAtUtc = currentStartAt,
|
||||
aggregationEndAtUtc = currentEndAt,
|
||||
visibleFromAtUtc = LocalDateTime.of(2026, 6, 8, 0, 0)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
val snapshots = adapter.findLatestVisibleSnapshots(
|
||||
rankingType = AudioRankingType.WEEKLY_POPULAR,
|
||||
nowUtc = LocalDateTime.of(2026, 6, 7, 23, 59)
|
||||
)
|
||||
|
||||
assertEquals(listOf(1L), snapshots.map { it.contentId })
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("스냅샷 교체는 같은 랭킹 타입과 집계 기간 row만 삭제한다")
|
||||
fun shouldReplaceSnapshotsOnlyForSameRankingTypeAndPeriod() {
|
||||
val startAt = LocalDateTime.of(2026, 5, 31, 15, 0)
|
||||
val endAt = LocalDateTime.of(2026, 6, 7, 15, 0)
|
||||
val visibleAt = LocalDateTime.of(2026, 6, 8, 0, 0)
|
||||
repository.saveAll(
|
||||
listOf(
|
||||
snapshot(
|
||||
contentId = 1L,
|
||||
rankingType = AudioRankingType.WEEKLY_POPULAR,
|
||||
aggregationStartAtUtc = startAt,
|
||||
aggregationEndAtUtc = endAt
|
||||
),
|
||||
snapshot(
|
||||
contentId = 2L,
|
||||
rankingType = AudioRankingType.RISING,
|
||||
aggregationStartAtUtc = startAt,
|
||||
aggregationEndAtUtc = endAt
|
||||
),
|
||||
snapshot(
|
||||
contentId = 3L,
|
||||
rankingType = AudioRankingType.WEEKLY_POPULAR,
|
||||
aggregationStartAtUtc = startAt.minusWeeks(1),
|
||||
aggregationEndAtUtc = endAt.minusWeeks(1)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
adapter.replaceSnapshots(
|
||||
rankingType = AudioRankingType.WEEKLY_POPULAR,
|
||||
aggregationStartAtUtc = startAt,
|
||||
aggregationEndAtUtc = endAt,
|
||||
visibleFromAtUtc = visibleAt,
|
||||
newSnapshots = listOf(
|
||||
snapshotRecord(
|
||||
contentId = 4L,
|
||||
aggregationStartAtUtc = startAt,
|
||||
aggregationEndAtUtc = endAt,
|
||||
visibleFromAtUtc = visibleAt
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
val all = repository.findAll().map { it.contentId }.sorted()
|
||||
assertEquals(listOf(2L, 3L, 4L), all)
|
||||
}
|
||||
|
||||
private fun snapshot(
|
||||
contentId: Long,
|
||||
rankingType: AudioRankingType = AudioRankingType.WEEKLY_POPULAR,
|
||||
aggregationStartAtUtc: LocalDateTime = LocalDateTime.of(2026, 5, 31, 15, 0),
|
||||
aggregationEndAtUtc: LocalDateTime = LocalDateTime.of(2026, 6, 7, 15, 0),
|
||||
visibleFromAtUtc: LocalDateTime = LocalDateTime.of(2026, 6, 8, 0, 0),
|
||||
rank: Int = 1,
|
||||
isAdult: Boolean = false
|
||||
): AudioRankingSnapshot {
|
||||
return AudioRankingSnapshot(
|
||||
rankingType = rankingType,
|
||||
aggregationStartAtUtc = aggregationStartAtUtc,
|
||||
aggregationEndAtUtc = aggregationEndAtUtc,
|
||||
visibleFromAtUtc = visibleFromAtUtc,
|
||||
contentId = contentId,
|
||||
title = "audio-$contentId",
|
||||
creatorMemberId = 100L + contentId,
|
||||
creatorNickname = "creator-$contentId",
|
||||
coverImageUrl = "cover-$contentId.png",
|
||||
releaseDate = LocalDateTime.of(2026, 6, 1, 0, 0),
|
||||
isAdult = isAdult,
|
||||
rank = rank,
|
||||
finalScore = 100.0
|
||||
)
|
||||
}
|
||||
|
||||
private fun snapshotRecord(
|
||||
contentId: Long,
|
||||
aggregationStartAtUtc: LocalDateTime,
|
||||
aggregationEndAtUtc: LocalDateTime,
|
||||
visibleFromAtUtc: LocalDateTime,
|
||||
isAdult: Boolean = false
|
||||
): AudioRankingSnapshotRecord {
|
||||
return AudioRankingSnapshotRecord(
|
||||
rankingType = AudioRankingType.WEEKLY_POPULAR,
|
||||
aggregationStartAtUtc = aggregationStartAtUtc,
|
||||
aggregationEndAtUtc = aggregationEndAtUtc,
|
||||
visibleFromAtUtc = visibleFromAtUtc,
|
||||
contentId = contentId,
|
||||
title = "audio-$contentId",
|
||||
creatorMemberId = 100L + contentId,
|
||||
creatorNickname = "creator-$contentId",
|
||||
coverImageUrl = "cover-$contentId.png",
|
||||
releaseDate = LocalDateTime.of(2026, 6, 1, 0, 0),
|
||||
isAdult = isAdult,
|
||||
rank = 1,
|
||||
finalScore = 100.0
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user