test #426
@@ -0,0 +1,201 @@
|
||||
package kr.co.vividnext.sodalive.v2.content.ranking.application
|
||||
|
||||
import kr.co.vividnext.sodalive.v2.content.ranking.domain.AudioRankingPeriod
|
||||
import kr.co.vividnext.sodalive.v2.content.ranking.domain.AudioRankingPeriodPolicy
|
||||
import kr.co.vividnext.sodalive.v2.content.ranking.domain.AudioRankingSchedulePolicy
|
||||
import kr.co.vividnext.sodalive.v2.content.ranking.domain.AudioRankingScorePolicy
|
||||
import kr.co.vividnext.sodalive.v2.content.ranking.domain.AudioRankingSnapshotCandidate
|
||||
import kr.co.vividnext.sodalive.v2.content.ranking.domain.AudioRankingType
|
||||
import kr.co.vividnext.sodalive.v2.content.ranking.domain.AudioRankingUtcRange
|
||||
import kr.co.vividnext.sodalive.v2.content.ranking.port.out.AudioRankingAggregationPort
|
||||
import kr.co.vividnext.sodalive.v2.content.ranking.port.out.AudioRankingSnapshotPort
|
||||
import kr.co.vividnext.sodalive.v2.content.ranking.port.out.AudioRankingSnapshotRecord
|
||||
import org.springframework.stereotype.Service
|
||||
import org.springframework.transaction.annotation.Transactional
|
||||
import java.time.ZonedDateTime
|
||||
import kotlin.math.max
|
||||
|
||||
@Service
|
||||
class AudioRankingSnapshotRefreshService(
|
||||
private val aggregationPort: AudioRankingAggregationPort,
|
||||
private val snapshotPort: AudioRankingSnapshotPort
|
||||
) {
|
||||
private val periodPolicy = AudioRankingPeriodPolicy()
|
||||
private val schedulePolicy = AudioRankingSchedulePolicy()
|
||||
private val scorePolicy = AudioRankingScorePolicy()
|
||||
|
||||
@Transactional
|
||||
fun refreshLastCompletedWeek(type: AudioRankingType, now: ZonedDateTime) {
|
||||
val period = periodPolicy.resolveLastCompletedWeek(now)
|
||||
val utcRange = periodPolicy.toUtcRange(period)
|
||||
val visibleFromAtUtc = schedulePolicy.resolveVisibleFromAt(period.endExclusiveKst)
|
||||
val candidates = resolveCandidates(type, utcRange)
|
||||
val snapshots = candidates.toSnapshotRecords(type, period, utcRange, visibleFromAtUtc)
|
||||
|
||||
snapshotPort.replaceSnapshots(
|
||||
rankingType = type,
|
||||
aggregationStartAtUtc = utcRange.startInclusiveUtc,
|
||||
aggregationEndAtUtc = utcRange.endExclusiveUtc,
|
||||
visibleFromAtUtc = visibleFromAtUtc,
|
||||
newSnapshots = snapshots
|
||||
)
|
||||
}
|
||||
|
||||
private fun resolveCandidates(
|
||||
type: AudioRankingType,
|
||||
utcRange: AudioRankingUtcRange
|
||||
): List<AudioRankingSnapshotCandidate> {
|
||||
return when (type) {
|
||||
AudioRankingType.WEEKLY_POPULAR -> aggregationPort.aggregateWeeklyPopularCandidates(
|
||||
utcRange.startInclusiveUtc,
|
||||
utcRange.endExclusiveUtc
|
||||
)
|
||||
AudioRankingType.RISING -> aggregationPort.aggregateRisingCandidates(
|
||||
utcRange.startInclusiveUtc,
|
||||
utcRange.endExclusiveUtc
|
||||
)
|
||||
AudioRankingType.REVENUE -> aggregationPort.aggregateRevenueCandidates(
|
||||
utcRange.startInclusiveUtc,
|
||||
utcRange.endExclusiveUtc
|
||||
)
|
||||
AudioRankingType.SALES_COUNT -> aggregationPort.aggregateSalesCountCandidates(
|
||||
utcRange.startInclusiveUtc,
|
||||
utcRange.endExclusiveUtc
|
||||
)
|
||||
AudioRankingType.COMMENT_COUNT -> aggregationPort.aggregateCommentCountCandidates(
|
||||
utcRange.startInclusiveUtc,
|
||||
utcRange.endExclusiveUtc
|
||||
)
|
||||
AudioRankingType.LIKE_COUNT -> aggregationPort.aggregateLikeCountCandidates(
|
||||
utcRange.startInclusiveUtc,
|
||||
utcRange.endExclusiveUtc
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun List<AudioRankingSnapshotCandidate>.toSnapshotRecords(
|
||||
type: AudioRankingType,
|
||||
period: AudioRankingPeriod,
|
||||
utcRange: AudioRankingUtcRange,
|
||||
visibleFromAtUtc: java.time.LocalDateTime
|
||||
): List<AudioRankingSnapshotRecord> {
|
||||
val scoredCandidates = when (type) {
|
||||
AudioRankingType.WEEKLY_POPULAR -> withWeeklyPopularScores()
|
||||
AudioRankingType.RISING -> withRisingScores(period)
|
||||
AudioRankingType.REVENUE,
|
||||
AudioRankingType.SALES_COUNT,
|
||||
AudioRankingType.COMMENT_COUNT,
|
||||
AudioRankingType.LIKE_COUNT -> this
|
||||
}
|
||||
|
||||
val rankedRecords = scoredCandidates
|
||||
.sortedWith(
|
||||
compareByDescending<AudioRankingSnapshotCandidate> { it.finalScore }
|
||||
.thenByDescending { it.releaseDate }
|
||||
.thenByDescending { it.contentId }
|
||||
)
|
||||
.mapIndexed { index, candidate -> candidate.toSnapshotRecord(type, utcRange, visibleFromAtUtc, index + 1) }
|
||||
|
||||
val globalContentIds = rankedRecords.take(SNAPSHOT_LIMIT).map { it.contentId }.toSet()
|
||||
val safeContentIds = rankedRecords.filter { !it.isAdult }.take(SNAPSHOT_LIMIT).map { it.contentId }.toSet()
|
||||
val selectedContentIds = globalContentIds + safeContentIds
|
||||
|
||||
return rankedRecords.filter { it.contentId in selectedContentIds }
|
||||
}
|
||||
|
||||
private fun List<AudioRankingSnapshotCandidate>.withWeeklyPopularScores(): List<AudioRankingSnapshotCandidate> {
|
||||
val rawScores = associateWith { candidate ->
|
||||
scorePolicy.calculateWeeklyPopularScore(
|
||||
revenue = candidate.revenueCanAmount,
|
||||
salesCount = candidate.salesCount,
|
||||
viewCount = candidate.viewCount,
|
||||
likeCount = candidate.likeCount,
|
||||
commentCount = candidate.commentCount,
|
||||
isPaid = candidate.isPaid
|
||||
)
|
||||
}
|
||||
val paidMaxScore = rawScores.filterKeys { it.isPaid }.values.maxOrNull() ?: 0.0
|
||||
val freeMaxScore = rawScores.filterKeys { !it.isPaid }.values.maxOrNull() ?: 0.0
|
||||
|
||||
return map { candidate ->
|
||||
val rawScore = rawScores.getValue(candidate)
|
||||
candidate.copy(
|
||||
finalScore = scorePolicy.normalizeScore(
|
||||
rawScore,
|
||||
if (candidate.isPaid) paidMaxScore else freeMaxScore
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun AudioRankingSnapshotCandidate.withRisingScore(period: AudioRankingPeriod): AudioRankingSnapshotCandidate {
|
||||
return copy(
|
||||
finalScore = scorePolicy.calculateRisingScore(
|
||||
recentSalesCount = salesCount,
|
||||
previousSalesCount = previousSalesCount,
|
||||
recentViewCount = viewCount,
|
||||
previousViewCount = previousViewCount,
|
||||
recentLikeCount = likeCount,
|
||||
previousLikeCount = previousLikeCount,
|
||||
recentCommentCount = commentCount,
|
||||
previousCommentCount = previousCommentCount,
|
||||
releaseDate = releaseDate,
|
||||
aggregationEndAt = period.endExclusiveKst,
|
||||
isPaid = isPaid
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun List<AudioRankingSnapshotCandidate>.withRisingScores(
|
||||
period: AudioRankingPeriod
|
||||
): List<AudioRankingSnapshotCandidate> {
|
||||
val scoredCandidates = map { it.withRisingScore(period) }
|
||||
val paidMaxScore = scoredCandidates.filter { it.isPaid }.maxOfOrNull { it.finalScore } ?: 0.0
|
||||
val freeMaxScore = scoredCandidates.filter { !it.isPaid }.maxOfOrNull { it.finalScore } ?: 0.0
|
||||
|
||||
return scoredCandidates.map { candidate ->
|
||||
candidate.copy(
|
||||
finalScore = scorePolicy.normalizeScore(
|
||||
candidate.finalScore,
|
||||
if (candidate.isPaid) paidMaxScore else freeMaxScore
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun AudioRankingSnapshotCandidate.toSnapshotRecord(
|
||||
type: AudioRankingType,
|
||||
utcRange: AudioRankingUtcRange,
|
||||
visibleFromAtUtc: java.time.LocalDateTime,
|
||||
rank: Int
|
||||
): AudioRankingSnapshotRecord {
|
||||
return AudioRankingSnapshotRecord(
|
||||
rankingType = type,
|
||||
aggregationStartAtUtc = utcRange.startInclusiveUtc,
|
||||
aggregationEndAtUtc = utcRange.endExclusiveUtc,
|
||||
visibleFromAtUtc = visibleFromAtUtc,
|
||||
contentId = contentId,
|
||||
title = title,
|
||||
creatorMemberId = creatorMemberId,
|
||||
creatorNickname = creatorNickname,
|
||||
coverImageUrl = coverImageUrl,
|
||||
releaseDate = releaseDate,
|
||||
isAdult = isAdult,
|
||||
rank = rank,
|
||||
finalScore = max(finalScore, 0.0),
|
||||
revenueCanAmount = revenueCanAmount,
|
||||
salesCount = salesCount,
|
||||
viewCount = viewCount,
|
||||
likeCount = likeCount,
|
||||
commentCount = commentCount,
|
||||
previousSalesCount = previousSalesCount,
|
||||
previousViewCount = previousViewCount,
|
||||
previousLikeCount = previousLikeCount,
|
||||
previousCommentCount = previousCommentCount
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val SNAPSHOT_LIMIT = 20
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,267 @@
|
||||
package kr.co.vividnext.sodalive.v2.content.ranking.application
|
||||
|
||||
import kr.co.vividnext.sodalive.v2.content.ranking.domain.AudioRankingSnapshotCandidate
|
||||
import kr.co.vividnext.sodalive.v2.content.ranking.domain.AudioRankingType
|
||||
import kr.co.vividnext.sodalive.v2.content.ranking.port.out.AudioRankingAggregationPort
|
||||
import kr.co.vividnext.sodalive.v2.content.ranking.port.out.AudioRankingSnapshotPort
|
||||
import kr.co.vividnext.sodalive.v2.content.ranking.port.out.AudioRankingSnapshotRecord
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneId
|
||||
import java.time.ZonedDateTime
|
||||
|
||||
class AudioRankingSnapshotRefreshServiceTest {
|
||||
@Test
|
||||
fun shouldStoreTopTwentyByScoreReleaseDateAndContentId() {
|
||||
val aggregationPort = FakeAudioRankingAggregationPort()
|
||||
val snapshotPort = FakeAudioRankingSnapshotPort()
|
||||
val service = service(aggregationPort = aggregationPort, snapshotPort = snapshotPort)
|
||||
aggregationPort.weeklyCandidates = (1L..18L).map { contentId ->
|
||||
candidate(contentId = contentId, salesCount = 100 - contentId, releaseDate = LocalDateTime.of(2026, 6, 1, 0, 0))
|
||||
} + listOf(
|
||||
candidate(contentId = 19L, salesCount = 10, releaseDate = LocalDateTime.of(2026, 6, 2, 0, 0)),
|
||||
candidate(contentId = 20L, salesCount = 10, releaseDate = LocalDateTime.of(2026, 6, 3, 0, 0)),
|
||||
candidate(contentId = 21L, salesCount = 10, releaseDate = LocalDateTime.of(2026, 6, 3, 0, 0)),
|
||||
candidate(contentId = 22L, salesCount = 1, releaseDate = LocalDateTime.of(2026, 6, 4, 0, 0))
|
||||
)
|
||||
|
||||
service.refreshLastCompletedWeek(AudioRankingType.WEEKLY_POPULAR, now())
|
||||
|
||||
assertEquals(20, snapshotPort.snapshots.size)
|
||||
assertEquals(listOf(21L, 20L), snapshotPort.snapshots.takeLast(2).map { it.contentId })
|
||||
assertEquals((1..20).toList(), snapshotPort.snapshots.map { it.rank })
|
||||
}
|
||||
|
||||
@Test
|
||||
fun shouldUseLastCompletedWeekUtcRangeAndVisibleFromAt() {
|
||||
val aggregationPort = FakeAudioRankingAggregationPort()
|
||||
val snapshotPort = FakeAudioRankingSnapshotPort()
|
||||
val service = service(aggregationPort = aggregationPort, snapshotPort = snapshotPort)
|
||||
aggregationPort.risingCandidates = listOf(candidate(contentId = 1L, viewCount = 20, previousViewCount = 10))
|
||||
|
||||
service.refreshLastCompletedWeek(AudioRankingType.RISING, now())
|
||||
|
||||
assertEquals(LocalDateTime.of(2026, 5, 31, 15, 0), aggregationPort.startInclusiveUtc)
|
||||
assertEquals(LocalDateTime.of(2026, 6, 7, 15, 0), aggregationPort.endExclusiveUtc)
|
||||
assertEquals(AudioRankingType.RISING, snapshotPort.rankingType)
|
||||
assertEquals(LocalDateTime.of(2026, 5, 31, 15, 0), snapshotPort.aggregationStartAtUtc)
|
||||
assertEquals(LocalDateTime.of(2026, 6, 7, 15, 0), snapshotPort.aggregationEndAtUtc)
|
||||
assertEquals(LocalDateTime.of(2026, 6, 8, 0, 0), snapshotPort.visibleFromAtUtc)
|
||||
assertEquals(LocalDateTime.of(2026, 6, 8, 0, 0), snapshotPort.snapshots.single().visibleFromAtUtc)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun shouldNormalizeRisingScoresByPaidAndFreeGroups() {
|
||||
val aggregationPort = FakeAudioRankingAggregationPort()
|
||||
val snapshotPort = FakeAudioRankingSnapshotPort()
|
||||
val service = service(aggregationPort = aggregationPort, snapshotPort = snapshotPort)
|
||||
aggregationPort.risingCandidates = listOf(
|
||||
candidate(contentId = 1L, salesCount = 6, previousSalesCount = 3),
|
||||
candidate(contentId = 2L, salesCount = 3, previousSalesCount = 3),
|
||||
candidate(contentId = 3L, viewCount = 20, previousViewCount = 10),
|
||||
candidate(contentId = 4L, viewCount = 10, previousViewCount = 10)
|
||||
)
|
||||
|
||||
service.refreshLastCompletedWeek(AudioRankingType.RISING, now())
|
||||
|
||||
val scoresByContentId = snapshotPort.snapshots.associate { it.contentId to it.finalScore }
|
||||
assertEquals(100.0, scoresByContentId.getValue(1L), 0.0001)
|
||||
assertEquals(0.0, scoresByContentId.getValue(2L), 0.0001)
|
||||
assertEquals(100.0, scoresByContentId.getValue(3L), 0.0001)
|
||||
assertEquals(0.0, scoresByContentId.getValue(4L), 0.0001)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun shouldUseAggregationPortForMetricRankingTypes() {
|
||||
val aggregationPort = FakeAudioRankingAggregationPort()
|
||||
val snapshotPort = FakeAudioRankingSnapshotPort()
|
||||
val service = service(aggregationPort = aggregationPort, snapshotPort = snapshotPort)
|
||||
aggregationPort.revenueCandidates = listOf(candidate(contentId = 1L, finalScore = 10.0))
|
||||
|
||||
service.refreshLastCompletedWeek(AudioRankingType.REVENUE, now())
|
||||
|
||||
assertEquals(AudioRankingType.REVENUE, aggregationPort.metricRankingType)
|
||||
assertEquals(listOf(1L), snapshotPort.snapshots.map { it.contentId })
|
||||
}
|
||||
|
||||
@Test
|
||||
fun shouldSortMetricTieScoresByReleaseDateAndContentId() {
|
||||
val aggregationPort = FakeAudioRankingAggregationPort()
|
||||
val snapshotPort = FakeAudioRankingSnapshotPort()
|
||||
val service = service(aggregationPort = aggregationPort, snapshotPort = snapshotPort)
|
||||
aggregationPort.revenueCandidates = listOf(
|
||||
candidate(contentId = 1L, finalScore = 10.0, releaseDate = LocalDateTime.of(2026, 6, 1, 0, 0)),
|
||||
candidate(contentId = 2L, finalScore = 10.0, releaseDate = LocalDateTime.of(2026, 6, 2, 0, 0)),
|
||||
candidate(contentId = 3L, finalScore = 10.0, releaseDate = LocalDateTime.of(2026, 6, 2, 0, 0))
|
||||
)
|
||||
|
||||
service.refreshLastCompletedWeek(AudioRankingType.REVENUE, now())
|
||||
|
||||
assertEquals(listOf(3L, 2L, 1L), snapshotPort.snapshots.map { it.contentId })
|
||||
}
|
||||
|
||||
@Test
|
||||
fun shouldStoreGlobalTopTwentyAndSafeTopTwentyCandidates() {
|
||||
val aggregationPort = FakeAudioRankingAggregationPort()
|
||||
val snapshotPort = FakeAudioRankingSnapshotPort()
|
||||
val service = service(aggregationPort = aggregationPort, snapshotPort = snapshotPort)
|
||||
aggregationPort.revenueCandidates = (1L..20L).map { contentId ->
|
||||
candidate(contentId = contentId, finalScore = (100 - contentId).toDouble(), isAdult = true)
|
||||
} + (21L..40L).map { contentId ->
|
||||
candidate(contentId = contentId, finalScore = (100 - contentId).toDouble(), isAdult = false)
|
||||
}
|
||||
|
||||
service.refreshLastCompletedWeek(AudioRankingType.REVENUE, now())
|
||||
|
||||
assertEquals(40, snapshotPort.snapshots.size)
|
||||
assertEquals((1L..20L).toList(), snapshotPort.snapshots.take(20).map { it.contentId })
|
||||
assertEquals((21L..40L).toList(), snapshotPort.snapshots.drop(20).map { it.contentId })
|
||||
assertEquals((1..40).toList(), snapshotPort.snapshots.map { it.rank })
|
||||
}
|
||||
|
||||
private fun service(
|
||||
aggregationPort: AudioRankingAggregationPort = FakeAudioRankingAggregationPort(),
|
||||
snapshotPort: AudioRankingSnapshotPort = FakeAudioRankingSnapshotPort()
|
||||
): AudioRankingSnapshotRefreshService {
|
||||
return AudioRankingSnapshotRefreshService(
|
||||
aggregationPort = aggregationPort,
|
||||
snapshotPort = snapshotPort
|
||||
)
|
||||
}
|
||||
|
||||
private fun now(): ZonedDateTime {
|
||||
return ZonedDateTime.of(2026, 6, 8, 6, 0, 0, 0, ZoneId.of("Asia/Seoul"))
|
||||
}
|
||||
|
||||
private fun candidate(
|
||||
contentId: Long,
|
||||
finalScore: Double = 0.0,
|
||||
salesCount: Long = 0,
|
||||
previousSalesCount: Long = 0,
|
||||
viewCount: Long = 0,
|
||||
previousViewCount: Long = 0,
|
||||
releaseDate: LocalDateTime = LocalDateTime.of(2026, 6, 1, 0, 0),
|
||||
isAdult: Boolean = false
|
||||
): AudioRankingSnapshotCandidate {
|
||||
return AudioRankingSnapshotCandidate(
|
||||
contentId = contentId,
|
||||
title = "audio-$contentId",
|
||||
creatorMemberId = 100L + contentId,
|
||||
creatorNickname = "creator-$contentId",
|
||||
coverImageUrl = "cover-$contentId.png",
|
||||
releaseDate = releaseDate,
|
||||
isAdult = isAdult,
|
||||
isPaid = salesCount > 0,
|
||||
finalScore = finalScore,
|
||||
salesCount = salesCount,
|
||||
previousSalesCount = previousSalesCount,
|
||||
viewCount = viewCount,
|
||||
previousViewCount = previousViewCount
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private class FakeAudioRankingAggregationPort : AudioRankingAggregationPort {
|
||||
var weeklyCandidates: List<AudioRankingSnapshotCandidate> = emptyList()
|
||||
var risingCandidates: List<AudioRankingSnapshotCandidate> = emptyList()
|
||||
var revenueCandidates: List<AudioRankingSnapshotCandidate> = emptyList()
|
||||
var salesCountCandidates: List<AudioRankingSnapshotCandidate> = emptyList()
|
||||
var commentCountCandidates: List<AudioRankingSnapshotCandidate> = emptyList()
|
||||
var likeCountCandidates: List<AudioRankingSnapshotCandidate> = emptyList()
|
||||
var startInclusiveUtc: LocalDateTime? = null
|
||||
var endExclusiveUtc: LocalDateTime? = null
|
||||
var metricRankingType: AudioRankingType? = null
|
||||
|
||||
override fun aggregateWeeklyPopularCandidates(
|
||||
startInclusiveUtc: LocalDateTime,
|
||||
endExclusiveUtc: LocalDateTime
|
||||
): List<AudioRankingSnapshotCandidate> {
|
||||
this.startInclusiveUtc = startInclusiveUtc
|
||||
this.endExclusiveUtc = endExclusiveUtc
|
||||
return weeklyCandidates
|
||||
}
|
||||
|
||||
override fun aggregateRisingCandidates(
|
||||
startInclusiveUtc: LocalDateTime,
|
||||
endExclusiveUtc: LocalDateTime
|
||||
): List<AudioRankingSnapshotCandidate> {
|
||||
this.startInclusiveUtc = startInclusiveUtc
|
||||
this.endExclusiveUtc = endExclusiveUtc
|
||||
return risingCandidates
|
||||
}
|
||||
|
||||
override fun aggregateRevenueCandidates(
|
||||
startInclusiveUtc: LocalDateTime,
|
||||
endExclusiveUtc: LocalDateTime
|
||||
): List<AudioRankingSnapshotCandidate> {
|
||||
this.startInclusiveUtc = startInclusiveUtc
|
||||
this.endExclusiveUtc = endExclusiveUtc
|
||||
metricRankingType = AudioRankingType.REVENUE
|
||||
return revenueCandidates
|
||||
}
|
||||
|
||||
override fun aggregateSalesCountCandidates(
|
||||
startInclusiveUtc: LocalDateTime,
|
||||
endExclusiveUtc: LocalDateTime
|
||||
): List<AudioRankingSnapshotCandidate> {
|
||||
this.startInclusiveUtc = startInclusiveUtc
|
||||
this.endExclusiveUtc = endExclusiveUtc
|
||||
metricRankingType = AudioRankingType.SALES_COUNT
|
||||
return salesCountCandidates
|
||||
}
|
||||
|
||||
override fun aggregateCommentCountCandidates(
|
||||
startInclusiveUtc: LocalDateTime,
|
||||
endExclusiveUtc: LocalDateTime
|
||||
): List<AudioRankingSnapshotCandidate> {
|
||||
this.startInclusiveUtc = startInclusiveUtc
|
||||
this.endExclusiveUtc = endExclusiveUtc
|
||||
metricRankingType = AudioRankingType.COMMENT_COUNT
|
||||
return commentCountCandidates
|
||||
}
|
||||
|
||||
override fun aggregateLikeCountCandidates(
|
||||
startInclusiveUtc: LocalDateTime,
|
||||
endExclusiveUtc: LocalDateTime
|
||||
): List<AudioRankingSnapshotCandidate> {
|
||||
this.startInclusiveUtc = startInclusiveUtc
|
||||
this.endExclusiveUtc = endExclusiveUtc
|
||||
metricRankingType = AudioRankingType.LIKE_COUNT
|
||||
return likeCountCandidates
|
||||
}
|
||||
}
|
||||
|
||||
private class FakeAudioRankingSnapshotPort : AudioRankingSnapshotPort {
|
||||
val snapshots = mutableListOf<AudioRankingSnapshotRecord>()
|
||||
var rankingType: AudioRankingType? = null
|
||||
var aggregationStartAtUtc: LocalDateTime? = null
|
||||
var aggregationEndAtUtc: LocalDateTime? = null
|
||||
var visibleFromAtUtc: LocalDateTime? = null
|
||||
|
||||
override fun findLatestVisibleSnapshots(
|
||||
rankingType: AudioRankingType,
|
||||
nowUtc: LocalDateTime
|
||||
): List<AudioRankingSnapshotRecord> = snapshots
|
||||
|
||||
override fun findPreviousVisibleSnapshots(
|
||||
rankingType: AudioRankingType,
|
||||
currentAggregationStartAtUtc: LocalDateTime,
|
||||
nowUtc: LocalDateTime
|
||||
): List<AudioRankingSnapshotRecord> = snapshots
|
||||
|
||||
override fun replaceSnapshots(
|
||||
rankingType: AudioRankingType,
|
||||
aggregationStartAtUtc: LocalDateTime,
|
||||
aggregationEndAtUtc: LocalDateTime,
|
||||
visibleFromAtUtc: LocalDateTime,
|
||||
newSnapshots: List<AudioRankingSnapshotRecord>
|
||||
) {
|
||||
this.rankingType = rankingType
|
||||
this.aggregationStartAtUtc = aggregationStartAtUtc
|
||||
this.aggregationEndAtUtc = aggregationEndAtUtc
|
||||
this.visibleFromAtUtc = visibleFromAtUtc
|
||||
snapshots.clear()
|
||||
snapshots.addAll(newSnapshots)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user