feat(ranking): 랭킹 스냅샷 저장소를 추가한다
This commit is contained in:
@@ -0,0 +1,68 @@
|
|||||||
|
package kr.co.vividnext.sodalive.v2.ranking.adapter.out.persistence
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.common.BaseEntity
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
import javax.persistence.Column
|
||||||
|
import javax.persistence.Entity
|
||||||
|
import javax.persistence.Table
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "creator_ranking_snapshot")
|
||||||
|
class CreatorRankingSnapshot(
|
||||||
|
@Column(name = "aggregation_start_at_utc", nullable = false, updatable = false)
|
||||||
|
val aggregationStartAtUtc: LocalDateTime,
|
||||||
|
|
||||||
|
@Column(name = "aggregation_end_at_utc", nullable = false, updatable = false)
|
||||||
|
val aggregationEndAtUtc: LocalDateTime,
|
||||||
|
|
||||||
|
@Column(name = "creator_id", nullable = false, updatable = false)
|
||||||
|
val creatorId: Long,
|
||||||
|
|
||||||
|
@Column(name = "nickname", nullable = false, updatable = false, length = 100)
|
||||||
|
val nickname: String,
|
||||||
|
|
||||||
|
@Column(name = "profile_image_url", updatable = false, length = 500)
|
||||||
|
val profileImageUrl: String?,
|
||||||
|
|
||||||
|
@Column(name = "final_score", nullable = false, updatable = false)
|
||||||
|
val finalScore: Double,
|
||||||
|
|
||||||
|
@Column(name = "content_live_score", nullable = false, updatable = false)
|
||||||
|
val contentLiveScore: Double,
|
||||||
|
|
||||||
|
@Column(name = "engagement_score", nullable = false, updatable = false)
|
||||||
|
val engagementScore: Double,
|
||||||
|
|
||||||
|
@Column(name = "support_score", nullable = false, updatable = false)
|
||||||
|
val supportScore: Double,
|
||||||
|
|
||||||
|
@Column(name = "fan_loyalty_score", nullable = false, updatable = false)
|
||||||
|
val fanLoyaltyScore: Double,
|
||||||
|
|
||||||
|
@Column(name = "live_can_amount", nullable = false, updatable = false)
|
||||||
|
val liveCanAmount: Long,
|
||||||
|
|
||||||
|
@Column(name = "content_purchase_can_amount", nullable = false, updatable = false)
|
||||||
|
val contentPurchaseCanAmount: Long,
|
||||||
|
|
||||||
|
@Column(name = "content_like_count", nullable = false, updatable = false)
|
||||||
|
val contentLikeCount: Long,
|
||||||
|
|
||||||
|
@Column(name = "content_comment_count", nullable = false, updatable = false)
|
||||||
|
val contentCommentCount: Long,
|
||||||
|
|
||||||
|
@Column(name = "channel_donation_can_amount", nullable = false, updatable = false)
|
||||||
|
val channelDonationCanAmount: Long,
|
||||||
|
|
||||||
|
@Column(name = "channel_donation_count", nullable = false, updatable = false)
|
||||||
|
val channelDonationCount: Long,
|
||||||
|
|
||||||
|
@Column(name = "fan_talk_count", nullable = false, updatable = false)
|
||||||
|
val fanTalkCount: Long,
|
||||||
|
|
||||||
|
@Column(name = "final_follower_count", nullable = false, updatable = false)
|
||||||
|
val finalFollowerCount: Long,
|
||||||
|
|
||||||
|
@Column(name = "follow_increase", nullable = false, updatable = false)
|
||||||
|
val followIncrease: Long
|
||||||
|
) : BaseEntity()
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package kr.co.vividnext.sodalive.v2.ranking.adapter.out.persistence
|
||||||
|
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository
|
||||||
|
import org.springframework.data.jpa.repository.Query
|
||||||
|
import org.springframework.data.repository.query.Param
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
|
||||||
|
interface CreatorRankingSnapshotRepository : JpaRepository<CreatorRankingSnapshot, Long> {
|
||||||
|
fun findAllByAggregationStartAtUtcAndAggregationEndAtUtcOrderByFinalScoreDesc(
|
||||||
|
aggregationStartAtUtc: LocalDateTime,
|
||||||
|
aggregationEndAtUtc: LocalDateTime
|
||||||
|
): List<CreatorRankingSnapshot>
|
||||||
|
|
||||||
|
@Query(
|
||||||
|
value = """
|
||||||
|
select *
|
||||||
|
from creator_ranking_snapshot crs
|
||||||
|
where crs.aggregation_end_at_utc = (
|
||||||
|
select max(latest.aggregation_end_at_utc)
|
||||||
|
from creator_ranking_snapshot latest
|
||||||
|
)
|
||||||
|
order by crs.final_score desc
|
||||||
|
""",
|
||||||
|
nativeQuery = true
|
||||||
|
)
|
||||||
|
fun findLatestSnapshots(): List<CreatorRankingSnapshot>
|
||||||
|
|
||||||
|
@Query(
|
||||||
|
value = """
|
||||||
|
select *
|
||||||
|
from creator_ranking_snapshot crs
|
||||||
|
where crs.aggregation_end_at_utc = (
|
||||||
|
select max(previous.aggregation_end_at_utc)
|
||||||
|
from creator_ranking_snapshot previous
|
||||||
|
where previous.aggregation_end_at_utc < (
|
||||||
|
select max(latest.aggregation_end_at_utc)
|
||||||
|
from creator_ranking_snapshot latest
|
||||||
|
)
|
||||||
|
)
|
||||||
|
order by crs.final_score desc
|
||||||
|
""",
|
||||||
|
nativeQuery = true
|
||||||
|
)
|
||||||
|
fun findPreviousCompletedSnapshots(): List<CreatorRankingSnapshot>
|
||||||
|
|
||||||
|
fun deleteByAggregationStartAtUtcAndAggregationEndAtUtc(
|
||||||
|
@Param("aggregationStartAtUtc") aggregationStartAtUtc: LocalDateTime,
|
||||||
|
@Param("aggregationEndAtUtc") aggregationEndAtUtc: LocalDateTime
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
package kr.co.vividnext.sodalive.v2.ranking.adapter.out.persistence
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.v2.ranking.port.out.CreatorRankingSnapshotPort
|
||||||
|
import kr.co.vividnext.sodalive.v2.ranking.port.out.CreatorRankingSnapshotRecord
|
||||||
|
import org.springframework.stereotype.Repository
|
||||||
|
import org.springframework.transaction.annotation.Transactional
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
class DefaultCreatorRankingSnapshotRepository(
|
||||||
|
private val repository: CreatorRankingSnapshotRepository
|
||||||
|
) : CreatorRankingSnapshotPort {
|
||||||
|
override fun findSnapshotsByAggregationPeriod(
|
||||||
|
aggregationStartAtUtc: LocalDateTime,
|
||||||
|
aggregationEndAtUtc: LocalDateTime
|
||||||
|
): List<CreatorRankingSnapshotRecord> {
|
||||||
|
return repository.findAllByAggregationStartAtUtcAndAggregationEndAtUtcOrderByFinalScoreDesc(
|
||||||
|
aggregationStartAtUtc = aggregationStartAtUtc,
|
||||||
|
aggregationEndAtUtc = aggregationEndAtUtc
|
||||||
|
).map { it.toRecord() }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun findLatestSnapshots(): List<CreatorRankingSnapshotRecord> {
|
||||||
|
return repository.findLatestSnapshots().map { it.toRecord() }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun findPreviousCompletedSnapshots(): List<CreatorRankingSnapshotRecord> {
|
||||||
|
return repository.findPreviousCompletedSnapshots().map { it.toRecord() }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
override fun replaceSnapshots(
|
||||||
|
aggregationStartAtUtc: LocalDateTime,
|
||||||
|
aggregationEndAtUtc: LocalDateTime,
|
||||||
|
newSnapshots: List<CreatorRankingSnapshotRecord>
|
||||||
|
) {
|
||||||
|
repository.deleteByAggregationStartAtUtcAndAggregationEndAtUtc(
|
||||||
|
aggregationStartAtUtc = aggregationStartAtUtc,
|
||||||
|
aggregationEndAtUtc = aggregationEndAtUtc
|
||||||
|
)
|
||||||
|
repository.saveAll(newSnapshots.map { it.toEntity() })
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun CreatorRankingSnapshot.toRecord(): CreatorRankingSnapshotRecord {
|
||||||
|
return CreatorRankingSnapshotRecord(
|
||||||
|
aggregationStartAtUtc = aggregationStartAtUtc,
|
||||||
|
aggregationEndAtUtc = aggregationEndAtUtc,
|
||||||
|
creatorId = creatorId,
|
||||||
|
nickname = nickname,
|
||||||
|
profileImageUrl = profileImageUrl,
|
||||||
|
finalScore = finalScore,
|
||||||
|
contentLiveScore = contentLiveScore,
|
||||||
|
engagementScore = engagementScore,
|
||||||
|
supportScore = supportScore,
|
||||||
|
fanLoyaltyScore = fanLoyaltyScore,
|
||||||
|
liveCanAmount = liveCanAmount,
|
||||||
|
contentPurchaseCanAmount = contentPurchaseCanAmount,
|
||||||
|
contentLikeCount = contentLikeCount,
|
||||||
|
contentCommentCount = contentCommentCount,
|
||||||
|
channelDonationCanAmount = channelDonationCanAmount,
|
||||||
|
channelDonationCount = channelDonationCount,
|
||||||
|
fanTalkCount = fanTalkCount,
|
||||||
|
finalFollowerCount = finalFollowerCount,
|
||||||
|
followIncrease = followIncrease
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun CreatorRankingSnapshotRecord.toEntity(): CreatorRankingSnapshot {
|
||||||
|
return CreatorRankingSnapshot(
|
||||||
|
aggregationStartAtUtc = aggregationStartAtUtc,
|
||||||
|
aggregationEndAtUtc = aggregationEndAtUtc,
|
||||||
|
creatorId = creatorId,
|
||||||
|
nickname = nickname,
|
||||||
|
profileImageUrl = profileImageUrl,
|
||||||
|
finalScore = finalScore,
|
||||||
|
contentLiveScore = contentLiveScore,
|
||||||
|
engagementScore = engagementScore,
|
||||||
|
supportScore = supportScore,
|
||||||
|
fanLoyaltyScore = fanLoyaltyScore,
|
||||||
|
liveCanAmount = liveCanAmount,
|
||||||
|
contentPurchaseCanAmount = contentPurchaseCanAmount,
|
||||||
|
contentLikeCount = contentLikeCount,
|
||||||
|
contentCommentCount = contentCommentCount,
|
||||||
|
channelDonationCanAmount = channelDonationCanAmount,
|
||||||
|
channelDonationCount = channelDonationCount,
|
||||||
|
fanTalkCount = fanTalkCount,
|
||||||
|
finalFollowerCount = finalFollowerCount,
|
||||||
|
followIncrease = followIncrease
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package kr.co.vividnext.sodalive.v2.ranking.port.out
|
||||||
|
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
|
||||||
|
interface CreatorRankingSnapshotPort {
|
||||||
|
fun findSnapshotsByAggregationPeriod(
|
||||||
|
aggregationStartAtUtc: LocalDateTime,
|
||||||
|
aggregationEndAtUtc: LocalDateTime
|
||||||
|
): List<CreatorRankingSnapshotRecord>
|
||||||
|
|
||||||
|
fun findLatestSnapshots(): List<CreatorRankingSnapshotRecord>
|
||||||
|
|
||||||
|
fun findPreviousCompletedSnapshots(): List<CreatorRankingSnapshotRecord>
|
||||||
|
|
||||||
|
fun replaceSnapshots(
|
||||||
|
aggregationStartAtUtc: LocalDateTime,
|
||||||
|
aggregationEndAtUtc: LocalDateTime,
|
||||||
|
newSnapshots: List<CreatorRankingSnapshotRecord>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
data class CreatorRankingSnapshotRecord(
|
||||||
|
val aggregationStartAtUtc: LocalDateTime,
|
||||||
|
val aggregationEndAtUtc: LocalDateTime,
|
||||||
|
val creatorId: Long,
|
||||||
|
val nickname: String,
|
||||||
|
val profileImageUrl: String?,
|
||||||
|
val finalScore: Double,
|
||||||
|
val contentLiveScore: Double,
|
||||||
|
val engagementScore: Double,
|
||||||
|
val supportScore: Double,
|
||||||
|
val fanLoyaltyScore: Double,
|
||||||
|
val liveCanAmount: Long,
|
||||||
|
val contentPurchaseCanAmount: Long,
|
||||||
|
val contentLikeCount: Long,
|
||||||
|
val contentCommentCount: Long,
|
||||||
|
val channelDonationCanAmount: Long,
|
||||||
|
val channelDonationCount: Long,
|
||||||
|
val fanTalkCount: Long,
|
||||||
|
val finalFollowerCount: Long,
|
||||||
|
val followIncrease: Long
|
||||||
|
)
|
||||||
@@ -0,0 +1,232 @@
|
|||||||
|
package kr.co.vividnext.sodalive.v2.ranking.adapter.out.persistence
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.configs.QueryDslConfig
|
||||||
|
import kr.co.vividnext.sodalive.v2.ranking.port.out.CreatorRankingSnapshotRecord
|
||||||
|
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 DefaultCreatorRankingSnapshotRepositoryTest @Autowired constructor(
|
||||||
|
private val repository: CreatorRankingSnapshotRepository
|
||||||
|
) {
|
||||||
|
private val adapter = DefaultCreatorRankingSnapshotRepository(repository)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("같은 집계 기간의 스냅샷은 삭제 후 새 후보로 교체한다")
|
||||||
|
fun shouldReplaceSnapshotsByAggregationPeriod() {
|
||||||
|
val startAt = LocalDateTime.of(2026, 5, 31, 15, 0)
|
||||||
|
val endAt = LocalDateTime.of(2026, 6, 7, 15, 0)
|
||||||
|
val oldStartAt = startAt.minusWeeks(1)
|
||||||
|
val oldEndAt = endAt.minusWeeks(1)
|
||||||
|
repository.saveAll(
|
||||||
|
listOf(
|
||||||
|
snapshot(creatorId = 1L, aggregationStartAtUtc = oldStartAt, aggregationEndAtUtc = oldEndAt),
|
||||||
|
snapshot(creatorId = 2L, aggregationStartAtUtc = startAt, aggregationEndAtUtc = endAt)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
adapter.replaceSnapshots(
|
||||||
|
aggregationStartAtUtc = startAt,
|
||||||
|
aggregationEndAtUtc = endAt,
|
||||||
|
newSnapshots = listOf(snapshotRecord(creatorId = 3L, aggregationStartAtUtc = startAt, aggregationEndAtUtc = endAt))
|
||||||
|
)
|
||||||
|
|
||||||
|
val allSnapshots = repository.findAll()
|
||||||
|
assertEquals(listOf(1L, 3L), allSnapshots.map { it.creatorId }.sorted())
|
||||||
|
assertEquals(listOf(3L), adapter.findLatestSnapshots().map { it.creatorId })
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("최신 완료 주차 스냅샷은 최신 종료 시각 기준으로 최종 점수 내림차순 조회한다")
|
||||||
|
fun shouldFindLatestSnapshotsByLatestAggregationEndAndFinalScoreDescending() {
|
||||||
|
val oldStartAt = LocalDateTime.of(2026, 5, 24, 15, 0)
|
||||||
|
val oldEndAt = LocalDateTime.of(2026, 5, 31, 15, 0)
|
||||||
|
val latestStartAt = LocalDateTime.of(2026, 5, 31, 15, 0)
|
||||||
|
val latestEndAt = LocalDateTime.of(2026, 6, 7, 15, 0)
|
||||||
|
repository.saveAll(
|
||||||
|
listOf(
|
||||||
|
snapshot(
|
||||||
|
creatorId = 1L,
|
||||||
|
finalScore = 999.0,
|
||||||
|
aggregationStartAtUtc = oldStartAt,
|
||||||
|
aggregationEndAtUtc = oldEndAt
|
||||||
|
),
|
||||||
|
snapshot(
|
||||||
|
creatorId = 2L,
|
||||||
|
finalScore = 100.0,
|
||||||
|
aggregationStartAtUtc = latestStartAt,
|
||||||
|
aggregationEndAtUtc = latestEndAt
|
||||||
|
),
|
||||||
|
snapshot(
|
||||||
|
creatorId = 3L,
|
||||||
|
finalScore = 300.0,
|
||||||
|
aggregationStartAtUtc = latestStartAt,
|
||||||
|
aggregationEndAtUtc = latestEndAt
|
||||||
|
),
|
||||||
|
snapshot(
|
||||||
|
creatorId = 4L,
|
||||||
|
finalScore = 200.0,
|
||||||
|
aggregationStartAtUtc = latestStartAt,
|
||||||
|
aggregationEndAtUtc = latestEndAt
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
val latestSnapshots = adapter.findLatestSnapshots()
|
||||||
|
|
||||||
|
assertEquals(listOf(3L, 4L, 2L), latestSnapshots.map { it.creatorId })
|
||||||
|
assertEquals(listOf(latestStartAt, latestStartAt, latestStartAt), latestSnapshots.map { it.aggregationStartAtUtc })
|
||||||
|
assertEquals(listOf(latestEndAt, latestEndAt, latestEndAt), latestSnapshots.map { it.aggregationEndAtUtc })
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("직전 완료 주차 스냅샷은 최신 종료 시각보다 이전인 가장 큰 종료 시각 기준으로 조회한다")
|
||||||
|
fun shouldFindPreviousCompletedSnapshotsBeforeLatestPeriod() {
|
||||||
|
val oldestStartAt = LocalDateTime.of(2026, 5, 17, 15, 0)
|
||||||
|
val oldestEndAt = LocalDateTime.of(2026, 5, 24, 15, 0)
|
||||||
|
val previousStartAt = LocalDateTime.of(2026, 5, 24, 15, 0)
|
||||||
|
val previousEndAt = LocalDateTime.of(2026, 5, 31, 15, 0)
|
||||||
|
val latestStartAt = LocalDateTime.of(2026, 5, 31, 15, 0)
|
||||||
|
val latestEndAt = LocalDateTime.of(2026, 6, 7, 15, 0)
|
||||||
|
repository.saveAll(
|
||||||
|
listOf(
|
||||||
|
snapshot(creatorId = 1L, aggregationStartAtUtc = oldestStartAt, aggregationEndAtUtc = oldestEndAt),
|
||||||
|
snapshot(
|
||||||
|
creatorId = 2L,
|
||||||
|
finalScore = 200.0,
|
||||||
|
aggregationStartAtUtc = previousStartAt,
|
||||||
|
aggregationEndAtUtc = previousEndAt
|
||||||
|
),
|
||||||
|
snapshot(
|
||||||
|
creatorId = 3L,
|
||||||
|
finalScore = 300.0,
|
||||||
|
aggregationStartAtUtc = previousStartAt,
|
||||||
|
aggregationEndAtUtc = previousEndAt
|
||||||
|
),
|
||||||
|
snapshot(creatorId = 4L, aggregationStartAtUtc = latestStartAt, aggregationEndAtUtc = latestEndAt)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
val previousSnapshots = adapter.findPreviousCompletedSnapshots()
|
||||||
|
|
||||||
|
assertEquals(listOf(3L, 2L), previousSnapshots.map { it.creatorId })
|
||||||
|
assertEquals(listOf(previousEndAt, previousEndAt), previousSnapshots.map { it.aggregationEndAtUtc })
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("요청한 집계 기간에 스냅샷이 없으면 이전 주차를 대신 반환하지 않는다")
|
||||||
|
fun shouldReturnEmptyWhenRequestedAggregationPeriodHasNoSnapshots() {
|
||||||
|
val previousStartAt = LocalDateTime.of(2026, 5, 24, 15, 0)
|
||||||
|
val previousEndAt = LocalDateTime.of(2026, 5, 31, 15, 0)
|
||||||
|
val requestedStartAt = LocalDateTime.of(2026, 5, 31, 15, 0)
|
||||||
|
val requestedEndAt = LocalDateTime.of(2026, 6, 7, 15, 0)
|
||||||
|
repository.save(
|
||||||
|
snapshot(
|
||||||
|
creatorId = 1L,
|
||||||
|
aggregationStartAtUtc = previousStartAt,
|
||||||
|
aggregationEndAtUtc = previousEndAt
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
val snapshots = adapter.findSnapshotsByAggregationPeriod(
|
||||||
|
aggregationStartAtUtc = requestedStartAt,
|
||||||
|
aggregationEndAtUtc = requestedEndAt
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals(emptyList<CreatorRankingSnapshotRecord>(), snapshots)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("20위 점수 경계 동점 후보는 저장소에서 누락 없이 저장하고 조회할 수 있다")
|
||||||
|
fun shouldPersistAllCandidatesTiedAtTwentiethScoreBoundary() {
|
||||||
|
val startAt = LocalDateTime.of(2026, 5, 31, 15, 0)
|
||||||
|
val endAt = LocalDateTime.of(2026, 6, 7, 15, 0)
|
||||||
|
val candidates = (1L..19L).map { creatorId ->
|
||||||
|
snapshotRecord(
|
||||||
|
creatorId = creatorId,
|
||||||
|
finalScore = 1000.0 - creatorId,
|
||||||
|
aggregationStartAtUtc = startAt,
|
||||||
|
aggregationEndAtUtc = endAt
|
||||||
|
)
|
||||||
|
} + listOf(
|
||||||
|
snapshotRecord(creatorId = 20L, finalScore = 500.0, aggregationStartAtUtc = startAt, aggregationEndAtUtc = endAt),
|
||||||
|
snapshotRecord(creatorId = 21L, finalScore = 500.0, aggregationStartAtUtc = startAt, aggregationEndAtUtc = endAt),
|
||||||
|
snapshotRecord(creatorId = 22L, finalScore = 500.0, aggregationStartAtUtc = startAt, aggregationEndAtUtc = endAt)
|
||||||
|
)
|
||||||
|
|
||||||
|
adapter.replaceSnapshots(startAt, endAt, candidates)
|
||||||
|
|
||||||
|
val latestSnapshots = adapter.findLatestSnapshots()
|
||||||
|
assertEquals(22, latestSnapshots.size)
|
||||||
|
assertEquals(setOf(20L, 21L, 22L), latestSnapshots.takeLast(3).map { it.creatorId }.toSet())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun snapshot(
|
||||||
|
creatorId: Long,
|
||||||
|
finalScore: Double = 100.0,
|
||||||
|
aggregationStartAtUtc: LocalDateTime,
|
||||||
|
aggregationEndAtUtc: LocalDateTime
|
||||||
|
): CreatorRankingSnapshot {
|
||||||
|
return CreatorRankingSnapshot(
|
||||||
|
aggregationStartAtUtc = aggregationStartAtUtc,
|
||||||
|
aggregationEndAtUtc = aggregationEndAtUtc,
|
||||||
|
creatorId = creatorId,
|
||||||
|
nickname = "creator-$creatorId",
|
||||||
|
profileImageUrl = "profile-$creatorId.png",
|
||||||
|
finalScore = finalScore,
|
||||||
|
contentLiveScore = 10.0,
|
||||||
|
engagementScore = 20.0,
|
||||||
|
supportScore = 30.0,
|
||||||
|
fanLoyaltyScore = 40.0,
|
||||||
|
liveCanAmount = 100,
|
||||||
|
contentPurchaseCanAmount = 200,
|
||||||
|
contentLikeCount = 3,
|
||||||
|
contentCommentCount = 4,
|
||||||
|
channelDonationCanAmount = 500,
|
||||||
|
channelDonationCount = 6,
|
||||||
|
fanTalkCount = 7,
|
||||||
|
finalFollowerCount = 8,
|
||||||
|
followIncrease = -1
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun snapshotRecord(
|
||||||
|
creatorId: Long,
|
||||||
|
finalScore: Double = 100.0,
|
||||||
|
aggregationStartAtUtc: LocalDateTime,
|
||||||
|
aggregationEndAtUtc: LocalDateTime
|
||||||
|
): CreatorRankingSnapshotRecord {
|
||||||
|
return CreatorRankingSnapshotRecord(
|
||||||
|
aggregationStartAtUtc = aggregationStartAtUtc,
|
||||||
|
aggregationEndAtUtc = aggregationEndAtUtc,
|
||||||
|
creatorId = creatorId,
|
||||||
|
nickname = "creator-$creatorId",
|
||||||
|
profileImageUrl = "profile-$creatorId.png",
|
||||||
|
finalScore = finalScore,
|
||||||
|
contentLiveScore = 10.0,
|
||||||
|
engagementScore = 20.0,
|
||||||
|
supportScore = 30.0,
|
||||||
|
fanLoyaltyScore = 40.0,
|
||||||
|
liveCanAmount = 100,
|
||||||
|
contentPurchaseCanAmount = 200,
|
||||||
|
contentLikeCount = 3,
|
||||||
|
contentCommentCount = 4,
|
||||||
|
channelDonationCanAmount = 500,
|
||||||
|
channelDonationCount = 6,
|
||||||
|
fanTalkCount = 7,
|
||||||
|
finalFollowerCount = 8,
|
||||||
|
followIncrease = -1
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user