feat(home): 크리에이터 랭킹 후보 집계를 변경한다
This commit is contained in:
+239
-196
@@ -5,18 +5,20 @@ import kr.co.vividnext.sodalive.can.use.CanUsage
|
||||
import kr.co.vividnext.sodalive.can.use.UseCan
|
||||
import kr.co.vividnext.sodalive.can.use.UseCanCalculate
|
||||
import kr.co.vividnext.sodalive.can.use.UseCanCalculateStatus
|
||||
import kr.co.vividnext.sodalive.chat.character.ChatCharacter
|
||||
import kr.co.vividnext.sodalive.chat.room.ChatMessage
|
||||
import kr.co.vividnext.sodalive.chat.room.ChatParticipant
|
||||
import kr.co.vividnext.sodalive.chat.room.ChatRoom
|
||||
import kr.co.vividnext.sodalive.chat.room.ParticipantType
|
||||
import kr.co.vividnext.sodalive.configs.QueryDslConfig
|
||||
import kr.co.vividnext.sodalive.content.AudioContent
|
||||
import kr.co.vividnext.sodalive.content.comment.AudioContentComment
|
||||
import kr.co.vividnext.sodalive.content.like.AudioContentLike
|
||||
import kr.co.vividnext.sodalive.content.theme.AudioContentTheme
|
||||
import kr.co.vividnext.sodalive.explorer.profile.CreatorCheers
|
||||
import kr.co.vividnext.sodalive.live.room.LiveRoom
|
||||
import kr.co.vividnext.sodalive.member.Member
|
||||
import kr.co.vividnext.sodalive.member.MemberRole
|
||||
import kr.co.vividnext.sodalive.member.following.CreatorFollowing
|
||||
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 org.springframework.beans.factory.annotation.Autowired
|
||||
@@ -28,25 +30,41 @@ import javax.persistence.EntityManager
|
||||
@DataJpaTest(
|
||||
properties = [
|
||||
"spring.cache.type=none",
|
||||
"spring.datasource.url=jdbc:h2:mem:testdb;MODE=MySQL;NON_KEYWORDS=VALUE"
|
||||
"spring.datasource.url=jdbc:h2:mem:creator-ranking-aggregation;" +
|
||||
"MODE=MySQL;DATABASE_TO_UPPER=false;NON_KEYWORDS=VALUE;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE"
|
||||
]
|
||||
)
|
||||
@Import(QueryDslConfig::class)
|
||||
@Import(QueryDslConfig::class, DefaultCreatorRankingAggregationRepository::class)
|
||||
class DefaultCreatorRankingAggregationRepositoryTest @Autowired constructor(
|
||||
private val adapter: DefaultCreatorRankingAggregationRepository,
|
||||
private val entityManager: EntityManager
|
||||
) {
|
||||
private val adapter = DefaultCreatorRankingAggregationRepository(entityManager)
|
||||
private val startAt = LocalDateTime.of(2026, 5, 31, 15, 0)
|
||||
private val endAt = LocalDateTime.of(2026, 6, 7, 15, 0)
|
||||
private val inPeriod = LocalDateTime.of(2026, 6, 1, 0, 0)
|
||||
@Test
|
||||
@DisplayName("활성 크리에이터만 V2 raw metric 후보로 집계한다")
|
||||
fun shouldAggregateOnlyActiveCreators() {
|
||||
val activeCreator = saveCreator("active-creator")
|
||||
val inactiveCreator = saveCreator("inactive-creator", isActive = false)
|
||||
val nonCreator = saveUser("non-creator")
|
||||
val user = saveUser("active-user")
|
||||
saveUseCanCalculate(user, activeCreator, CanUsage.DONATION, 10, UseCanCalculateStatus.RECEIVED, false, IN_PERIOD)
|
||||
saveUseCanCalculate(user, inactiveCreator, CanUsage.DONATION, 100, UseCanCalculateStatus.RECEIVED, false, IN_PERIOD)
|
||||
saveUseCanCalculate(user, nonCreator, CanUsage.DONATION, 200, UseCanCalculateStatus.RECEIVED, false, IN_PERIOD)
|
||||
flushAndClear()
|
||||
|
||||
val candidates = aggregate()
|
||||
|
||||
assertEquals(listOf(activeCreator.id), candidates.map { it.creatorId })
|
||||
assertFalse(candidates.any { it.creatorId == inactiveCreator.id })
|
||||
assertFalse(candidates.any { it.creatorId == nonCreator.id })
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("콘텐츠/라이브 캔은 사용 구분, 정산 상태, 환불 여부, UTC 기간으로 집계한다")
|
||||
fun shouldAggregateLiveAndContentCanAmountsByUsageStatusRefundAndUtcPeriod() {
|
||||
@DisplayName("캔 지표는 사용 구분, 정산 상태, 환불 여부, half-open UTC 기간으로 집계한다")
|
||||
fun shouldAggregateCanMetricsByUsageStatusRefundAndHalfOpenPeriod() {
|
||||
val creator = saveCreator("can-creator")
|
||||
val user = saveUser("can-user")
|
||||
saveUseCanCalculate(user, creator, CanUsage.DONATION, 10, UseCanCalculateStatus.RECEIVED, false, startAt)
|
||||
saveUseCanCalculate(user, creator, CanUsage.LIVE, 20, UseCanCalculateStatus.RECEIVED, false, inPeriod)
|
||||
saveUseCanCalculate(user, creator, CanUsage.DONATION, 10, UseCanCalculateStatus.RECEIVED, false, START_AT)
|
||||
saveUseCanCalculate(user, creator, CanUsage.LIVE, 20, UseCanCalculateStatus.RECEIVED, false, IN_PERIOD)
|
||||
saveUseCanCalculate(
|
||||
user,
|
||||
creator,
|
||||
@@ -54,11 +72,11 @@ class DefaultCreatorRankingAggregationRepositoryTest @Autowired constructor(
|
||||
30,
|
||||
UseCanCalculateStatus.RECEIVED,
|
||||
false,
|
||||
endAt.minusSeconds(1)
|
||||
END_AT.minusSeconds(1)
|
||||
)
|
||||
saveUseCanCalculate(user, creator, CanUsage.ORDER_CONTENT, 40, UseCanCalculateStatus.RECEIVED, false, inPeriod)
|
||||
saveUseCanCalculate(user, creator, CanUsage.DONATION, 100, UseCanCalculateStatus.RECEIVED, true, inPeriod)
|
||||
saveUseCanCalculate(user, creator, CanUsage.LIVE, 200, UseCanCalculateStatus.CALCULATE_COMPLETE, false, inPeriod)
|
||||
saveUseCanCalculate(user, creator, CanUsage.ORDER_CONTENT, 40, UseCanCalculateStatus.RECEIVED, false, IN_PERIOD)
|
||||
saveUseCanCalculate(user, creator, CanUsage.DONATION, 100, UseCanCalculateStatus.RECEIVED, true, IN_PERIOD)
|
||||
saveUseCanCalculate(user, creator, CanUsage.LIVE, 200, UseCanCalculateStatus.CALCULATE_COMPLETE, false, IN_PERIOD)
|
||||
saveUseCanCalculate(
|
||||
user,
|
||||
creator,
|
||||
@@ -66,9 +84,9 @@ class DefaultCreatorRankingAggregationRepositoryTest @Autowired constructor(
|
||||
300,
|
||||
UseCanCalculateStatus.RECEIVED,
|
||||
false,
|
||||
startAt.minusSeconds(1)
|
||||
START_AT.minusSeconds(1)
|
||||
)
|
||||
saveUseCanCalculate(user, creator, CanUsage.ORDER_CONTENT, 400, UseCanCalculateStatus.RECEIVED, false, endAt)
|
||||
saveUseCanCalculate(user, creator, CanUsage.ORDER_CONTENT, 400, UseCanCalculateStatus.RECEIVED, false, END_AT)
|
||||
flushAndClear()
|
||||
|
||||
val candidate = aggregate().single()
|
||||
@@ -78,139 +96,136 @@ class DefaultCreatorRankingAggregationRepositoryTest @Autowired constructor(
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("활성 콘텐츠의 활성 좋아요와 작성자 본인이 아닌 활성 댓글/대댓글만 집계한다")
|
||||
fun shouldAggregateActiveContentLikesAndCommentsExcludingCreatorSelfResponses() {
|
||||
val creator = saveCreator("engagement-creator")
|
||||
val otherCreator = saveCreator("inactive-content-creator")
|
||||
val user = saveUser("engagement-user")
|
||||
val content = saveAudioContent(creator, isActive = true)
|
||||
val inactiveContent = saveAudioContent(otherCreator, isActive = false)
|
||||
saveUseCanCalculate(user, creator, CanUsage.DONATION, 10, UseCanCalculateStatus.RECEIVED, false, inPeriod)
|
||||
saveContentLike(content, user, isActive = true, createdAt = inPeriod)
|
||||
saveContentLike(content, user, isActive = false, createdAt = inPeriod)
|
||||
saveContentLike(content, user, isActive = true, createdAt = startAt.minusSeconds(1))
|
||||
saveContentLike(inactiveContent, user, isActive = true, createdAt = inPeriod)
|
||||
val parent = saveContentComment(content, user, isActive = true, createdAt = inPeriod)
|
||||
saveContentComment(content, user, parent = parent, isActive = true, createdAt = inPeriod)
|
||||
saveContentComment(content, creator, isActive = true, createdAt = inPeriod)
|
||||
saveContentComment(content, user, isActive = false, createdAt = inPeriod)
|
||||
saveContentComment(content, user, isActive = true, createdAt = endAt)
|
||||
saveContentComment(inactiveContent, user, isActive = true, createdAt = inPeriod)
|
||||
flushAndClear()
|
||||
|
||||
val candidate = aggregate().single { it.creatorId == creator.id }
|
||||
|
||||
assertEquals(1, candidate.contentLikeCount)
|
||||
assertEquals(2, candidate.contentCommentCount)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("채널 후원 캔/건수와 최상위 활성 팬 Talk만 집계한다")
|
||||
fun shouldAggregateChannelDonationAndTopLevelActiveFanTalks() {
|
||||
val creator = saveCreator("support-creator")
|
||||
val user = saveUser("support-user")
|
||||
saveUseCanCalculate(user, creator, CanUsage.CHANNEL_DONATION, 100, UseCanCalculateStatus.RECEIVED, false, inPeriod)
|
||||
saveUseCanCalculate(user, creator, CanUsage.CHANNEL_DONATION, 200, UseCanCalculateStatus.RECEIVED, false, inPeriod)
|
||||
saveUseCanCalculate(user, creator, CanUsage.CHANNEL_DONATION, 300, UseCanCalculateStatus.RECEIVED, true, inPeriod)
|
||||
saveUseCanCalculate(user, creator, CanUsage.CHANNEL_DONATION, 400, UseCanCalculateStatus.REFUND, false, inPeriod)
|
||||
val topLevel = saveCreatorCheers(creator, user, isActive = true, createdAt = inPeriod)
|
||||
saveCreatorCheers(creator, user, parent = topLevel, isActive = true, createdAt = inPeriod)
|
||||
saveCreatorCheers(creator, user, isActive = false, createdAt = inPeriod)
|
||||
saveCreatorCheers(creator, user, isActive = true, createdAt = endAt)
|
||||
flushAndClear()
|
||||
|
||||
val candidate = aggregate().single()
|
||||
|
||||
assertEquals(300, candidate.channelDonationCanAmount)
|
||||
assertEquals(2, candidate.channelDonationCount)
|
||||
assertEquals(1, candidate.fanTalkCount)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("팔로우 최종 활성 수와 현재 row 기준 생성/비활성 변경 증가 수를 집계한다")
|
||||
fun shouldAggregateFinalFollowerCountAndFollowIncreaseFromCurrentRows() {
|
||||
val creator = saveCreator("follow-creator")
|
||||
val activeFollower = saveUser("active-follower")
|
||||
val newFollower = saveUser("new-follower")
|
||||
val unfollower = saveUser("unfollower")
|
||||
val oldInactiveFollower = saveUser("old-inactive-follower")
|
||||
saveUseCanCalculate(activeFollower, creator, CanUsage.DONATION, 10, UseCanCalculateStatus.RECEIVED, false, inPeriod)
|
||||
saveFollowing(
|
||||
creator,
|
||||
activeFollower,
|
||||
isActive = true,
|
||||
createdAt = startAt.minusDays(5),
|
||||
updatedAt = startAt.minusDays(5)
|
||||
)
|
||||
saveFollowing(creator, newFollower, isActive = true, createdAt = inPeriod, updatedAt = inPeriod)
|
||||
saveFollowing(creator, unfollower, isActive = false, createdAt = startAt.minusDays(10), updatedAt = inPeriod)
|
||||
saveFollowing(
|
||||
creator,
|
||||
oldInactiveFollower,
|
||||
isActive = false,
|
||||
createdAt = startAt.minusDays(10),
|
||||
updatedAt = startAt.minusSeconds(1)
|
||||
)
|
||||
flushAndClear()
|
||||
|
||||
val candidate = aggregate().single()
|
||||
|
||||
assertEquals(2, candidate.finalFollowerCount)
|
||||
// 현재 CreatorFollowing row만으로 집계하므로 기간 내 재팔로우 이력은 별도 이벤트로 복원하지 않는다.
|
||||
assertEquals(0, candidate.followIncrease)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("활성 크리에이터별 원천 지표를 합쳐 1점 이상 후보만 점수와 함께 반환한다")
|
||||
fun shouldMergeMetricsForActiveCreatorsAndExcludeInactiveNonCreatorAndLowScoreCandidates() {
|
||||
val creator = saveCreator("merged-creator", profileImage = "merged.png")
|
||||
val lowScoreCreator = saveCreator("low-score-creator")
|
||||
val inactiveCreator = saveCreator("inactive-creator", isActive = false)
|
||||
val nonCreator = saveUser("non-creator")
|
||||
val user = saveUser("merged-user")
|
||||
saveUseCanCalculate(user, creator, CanUsage.DONATION, 10, UseCanCalculateStatus.RECEIVED, false, inPeriod)
|
||||
saveContentLike(saveAudioContent(creator, isActive = true), user, isActive = true, createdAt = inPeriod)
|
||||
saveUseCanCalculate(user, inactiveCreator, CanUsage.DONATION, 1000, UseCanCalculateStatus.RECEIVED, false, inPeriod)
|
||||
saveUseCanCalculate(user, nonCreator, CanUsage.DONATION, 1000, UseCanCalculateStatus.RECEIVED, false, inPeriod)
|
||||
saveFollowing(lowScoreCreator, user, isActive = true, createdAt = startAt.minusDays(1), updatedAt = startAt.minusDays(1))
|
||||
@DisplayName("팔로우 증가는 음수를 0으로 보정하고 양수 신규 팔로우는 후보를 만든다")
|
||||
fun shouldClampNegativeFollowIncreaseAndIncludePositiveFollowCandidate() {
|
||||
val negativeCreator = saveCreator("negative-follow-creator")
|
||||
val positiveCreator = saveCreator("positive-follow-creator")
|
||||
val user = saveUser("follow-user")
|
||||
val firstUnfollower = saveUser("first-unfollower")
|
||||
val secondUnfollower = saveUser("second-unfollower")
|
||||
saveUseCanCalculate(user, negativeCreator, CanUsage.DONATION, 1, UseCanCalculateStatus.RECEIVED, false, IN_PERIOD)
|
||||
saveFollowing(negativeCreator, firstUnfollower, isActive = false, START_AT.minusDays(3), IN_PERIOD)
|
||||
saveFollowing(negativeCreator, secondUnfollower, isActive = false, START_AT.minusDays(2), IN_PERIOD)
|
||||
saveFollowing(positiveCreator, user, isActive = true, IN_PERIOD, IN_PERIOD)
|
||||
flushAndClear()
|
||||
|
||||
val candidates = aggregate()
|
||||
|
||||
assertEquals(listOf(creator.id), candidates.map { it.creatorId })
|
||||
val candidate = candidates.single()
|
||||
assertEquals("merged-creator", candidate.nickname)
|
||||
assertEquals("merged.png", candidate.profileImageUrl)
|
||||
assertEquals(10, candidate.liveCanAmount)
|
||||
assertEquals(0, candidate.contentPurchaseCanAmount)
|
||||
assertEquals(1, candidate.contentLikeCount)
|
||||
assertEquals(7.0, candidate.contentLiveScore, 0.0001)
|
||||
assertEquals(0.5, candidate.engagementScore, 0.0001)
|
||||
assertEquals(0.0, candidate.supportScore, 0.0001)
|
||||
assertEquals(0.0, candidate.fanLoyaltyScore, 0.0001)
|
||||
assertEquals(2.6, candidate.finalScore, 0.0001)
|
||||
assertTrue(candidate.finalScore >= 1.0)
|
||||
assertFalse(candidates.any { it.creatorId == lowScoreCreator.id })
|
||||
assertFalse(candidates.any { it.creatorId == inactiveCreator.id })
|
||||
assertFalse(candidates.any { it.creatorId == nonCreator.id })
|
||||
assertEquals(0, candidates.single { it.creatorId == negativeCreator.id }.followIncrease)
|
||||
assertEquals(1, candidates.single { it.creatorId == positiveCreator.id }.followIncrease)
|
||||
}
|
||||
|
||||
private fun aggregate() = adapter.aggregateCandidates(startAt, endAt)
|
||||
@Test
|
||||
@DisplayName("AI 채팅 수는 활성 메시지/캐릭터 참여자/캐릭터/크리에이터와 half-open 기간만 집계한다")
|
||||
fun shouldAggregateAiChatCountOnlyForActiveCharacterConversationInHalfOpenPeriod() {
|
||||
val creator = saveCreator("chat-creator")
|
||||
val inactiveCreator = saveCreator("inactive-chat-creator", isActive = false)
|
||||
val activeCharacter = saveCharacter(creator, "active-character", isActive = true)
|
||||
val inactiveCharacter = saveCharacter(saveCreator("inactive-character-creator"), "inactive-character", isActive = false)
|
||||
val inactiveCreatorCharacter = saveCharacter(inactiveCreator, "inactive-creator-character", isActive = true)
|
||||
val room = saveChatRoom("chat-room")
|
||||
val activeParticipant = saveParticipant(room, ParticipantType.CHARACTER, character = activeCharacter, isActive = true)
|
||||
val inactiveParticipant = saveParticipant(room, ParticipantType.CHARACTER, character = activeCharacter, isActive = false)
|
||||
val inactiveCharacterParticipant = saveParticipant(
|
||||
room,
|
||||
ParticipantType.CHARACTER,
|
||||
character = inactiveCharacter,
|
||||
isActive = true
|
||||
)
|
||||
val inactiveCreatorParticipant = saveParticipant(
|
||||
room,
|
||||
ParticipantType.CHARACTER,
|
||||
character = inactiveCreatorCharacter,
|
||||
isActive = true
|
||||
)
|
||||
saveChatMessage(room, activeParticipant, isActive = true, START_AT)
|
||||
saveChatMessage(room, activeParticipant, isActive = true, END_AT.minusSeconds(1))
|
||||
saveChatMessage(room, activeParticipant, isActive = false, IN_PERIOD)
|
||||
saveChatMessage(room, inactiveParticipant, isActive = true, IN_PERIOD)
|
||||
saveChatMessage(room, inactiveCharacterParticipant, isActive = true, IN_PERIOD)
|
||||
saveChatMessage(room, inactiveCreatorParticipant, isActive = true, IN_PERIOD)
|
||||
saveChatMessage(room, activeParticipant, isActive = true, START_AT.minusSeconds(1))
|
||||
saveChatMessage(room, activeParticipant, isActive = true, END_AT)
|
||||
flushAndClear()
|
||||
|
||||
private fun saveCreator(nickname: String, profileImage: String? = null, isActive: Boolean = true): Member {
|
||||
return saveMember(nickname, MemberRole.CREATOR, profileImage, isActive)
|
||||
val candidate = aggregate().single()
|
||||
|
||||
assertEquals(creator.id, candidate.creatorId)
|
||||
assertEquals(2, candidate.aiChatCount)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("크리에이터 데뷔일은 첫 라이브와 첫 콘텐츠 공개일 중 빠른 값이고 없으면 null이다")
|
||||
fun shouldUseEarliestLiveOrContentAsCreatorDebutAtAndNullWhenMissing() {
|
||||
val liveFirstCreator = saveCreator("live-first-creator")
|
||||
val contentFirstCreator = saveCreator("content-first-creator")
|
||||
val noDebutCreator = saveCreator("no-debut-creator")
|
||||
val user = saveUser("debut-user")
|
||||
val futureDate = LocalDateTime.now().plusYears(1)
|
||||
saveLiveRoom(liveFirstCreator, LocalDateTime.of(2025, 12, 1, 0, 0), channelName = null)
|
||||
saveLiveRoom(liveFirstCreator, LocalDateTime.of(2026, 1, 10, 0, 0))
|
||||
saveAudioContent(liveFirstCreator, LocalDateTime.of(2026, 2, 10, 0, 0))
|
||||
saveLiveRoom(contentFirstCreator, LocalDateTime.of(2026, 3, 10, 0, 0))
|
||||
saveAudioContent(contentFirstCreator, LocalDateTime.of(2026, 1, 20, 0, 0))
|
||||
saveLiveRoom(contentFirstCreator, futureDate)
|
||||
saveAudioContent(contentFirstCreator, futureDate)
|
||||
saveUseCanCalculate(user, liveFirstCreator, CanUsage.DONATION, 1, UseCanCalculateStatus.RECEIVED, false, IN_PERIOD)
|
||||
saveUseCanCalculate(user, contentFirstCreator, CanUsage.DONATION, 1, UseCanCalculateStatus.RECEIVED, false, IN_PERIOD)
|
||||
saveUseCanCalculate(user, noDebutCreator, CanUsage.DONATION, 1, UseCanCalculateStatus.RECEIVED, false, IN_PERIOD)
|
||||
flushAndClear()
|
||||
|
||||
val candidates = aggregate().associateBy { it.creatorId }
|
||||
|
||||
assertEquals(LocalDateTime.of(2026, 1, 10, 0, 0), candidates.getValue(liveFirstCreator.id!!).creatorDebutAt)
|
||||
assertEquals(LocalDateTime.of(2026, 1, 20, 0, 0), candidates.getValue(contentFirstCreator.id!!).creatorDebutAt)
|
||||
assertEquals(null, candidates.getValue(noDebutCreator.id!!).creatorDebutAt)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("모든 지표가 0인 후보는 제외하고 하나라도 양수인 후보는 포함한다")
|
||||
fun shouldExcludeAllZeroCandidatesAndIncludeAnyPositiveMetricCandidate() {
|
||||
val zeroCreator = saveCreator("zero-creator")
|
||||
val liveCanCreator = saveCreator("live-can-creator")
|
||||
val contentCanCreator = saveCreator("content-can-creator")
|
||||
val followCreator = saveCreator("follow-creator")
|
||||
val chatCreator = saveCreator("chat-positive-creator")
|
||||
val user = saveUser("positive-user")
|
||||
saveLiveRoom(zeroCreator, IN_PERIOD)
|
||||
saveUseCanCalculate(user, liveCanCreator, CanUsage.DONATION, 1, UseCanCalculateStatus.RECEIVED, false, IN_PERIOD)
|
||||
saveUseCanCalculate(user, contentCanCreator, CanUsage.ORDER_CONTENT, 1, UseCanCalculateStatus.RECEIVED, false, IN_PERIOD)
|
||||
saveFollowing(followCreator, user, isActive = true, IN_PERIOD, IN_PERIOD)
|
||||
val room = saveChatRoom("positive-chat-room")
|
||||
val participant = saveParticipant(
|
||||
room,
|
||||
ParticipantType.CHARACTER,
|
||||
character = saveCharacter(chatCreator, "chat-positive-character"),
|
||||
isActive = true
|
||||
)
|
||||
saveChatMessage(room, participant, isActive = true, IN_PERIOD)
|
||||
flushAndClear()
|
||||
|
||||
val creatorIds = aggregate().map { it.creatorId }.toSet()
|
||||
|
||||
assertFalse(creatorIds.contains(zeroCreator.id))
|
||||
assertEquals(setOf(liveCanCreator.id, contentCanCreator.id, followCreator.id, chatCreator.id), creatorIds)
|
||||
}
|
||||
|
||||
private fun aggregate() = adapter.aggregateCandidates(START_AT, END_AT)
|
||||
|
||||
private fun saveCreator(nickname: String, isActive: Boolean = true): Member {
|
||||
return saveMember(nickname = nickname, role = MemberRole.CREATOR, isActive = isActive)
|
||||
}
|
||||
|
||||
private fun saveUser(nickname: String): Member {
|
||||
return saveMember(nickname, MemberRole.USER, null, true)
|
||||
return saveMember(nickname = nickname, role = MemberRole.USER, isActive = true)
|
||||
}
|
||||
|
||||
private fun saveMember(nickname: String, role: MemberRole, profileImage: String?, isActive: Boolean): Member {
|
||||
private fun saveMember(nickname: String, role: MemberRole, isActive: Boolean): Member {
|
||||
val member = Member(
|
||||
email = "$nickname@test.com",
|
||||
password = "password",
|
||||
nickname = nickname,
|
||||
profileImage = profileImage,
|
||||
profileImage = "$nickname.png",
|
||||
role = role,
|
||||
isActive = isActive
|
||||
)
|
||||
@@ -219,22 +234,6 @@ class DefaultCreatorRankingAggregationRepositoryTest @Autowired constructor(
|
||||
return member
|
||||
}
|
||||
|
||||
private fun saveAudioContent(creator: Member, isActive: Boolean): AudioContent {
|
||||
val theme = AudioContentTheme(theme = "theme-${creator.nickname}", image = "theme.png")
|
||||
entityManager.persist(theme)
|
||||
val content = AudioContent(
|
||||
title = "content-${creator.nickname}",
|
||||
detail = "detail",
|
||||
languageCode = "ko",
|
||||
releaseDate = inPeriod
|
||||
)
|
||||
content.member = creator
|
||||
content.theme = theme
|
||||
content.isActive = isActive
|
||||
entityManager.persist(content)
|
||||
return content
|
||||
}
|
||||
|
||||
private fun saveUseCanCalculate(
|
||||
member: Member,
|
||||
creator: Member,
|
||||
@@ -255,49 +254,6 @@ class DefaultCreatorRankingAggregationRepositoryTest @Autowired constructor(
|
||||
updateTimestamps("use_can_calculate", calculate.id!!, createdAt, createdAt)
|
||||
}
|
||||
|
||||
private fun saveContentLike(content: AudioContent, member: Member, isActive: Boolean, createdAt: LocalDateTime) {
|
||||
val like = AudioContentLike(memberId = member.id!!)
|
||||
like.audioContent = content
|
||||
like.isActive = isActive
|
||||
entityManager.persist(like)
|
||||
entityManager.flush()
|
||||
updateTimestamps("content_like", like.id!!, createdAt, createdAt)
|
||||
}
|
||||
|
||||
private fun saveContentComment(
|
||||
content: AudioContent,
|
||||
member: Member,
|
||||
parent: AudioContentComment? = null,
|
||||
isActive: Boolean,
|
||||
createdAt: LocalDateTime
|
||||
): AudioContentComment {
|
||||
val comment = AudioContentComment(comment = "comment", languageCode = "ko", isActive = isActive)
|
||||
comment.audioContent = content
|
||||
comment.member = member
|
||||
comment.parent = parent
|
||||
entityManager.persist(comment)
|
||||
entityManager.flush()
|
||||
updateTimestamps("content_comment", comment.id!!, createdAt, createdAt)
|
||||
return comment
|
||||
}
|
||||
|
||||
private fun saveCreatorCheers(
|
||||
creator: Member,
|
||||
member: Member,
|
||||
parent: CreatorCheers? = null,
|
||||
isActive: Boolean,
|
||||
createdAt: LocalDateTime
|
||||
): CreatorCheers {
|
||||
val cheers = CreatorCheers(cheers = "cheers", languageCode = "ko", isActive = isActive)
|
||||
cheers.creator = creator
|
||||
cheers.member = member
|
||||
cheers.parent = parent
|
||||
entityManager.persist(cheers)
|
||||
entityManager.flush()
|
||||
updateTimestamps("creator_cheers", cheers.id!!, createdAt, createdAt)
|
||||
return cheers
|
||||
}
|
||||
|
||||
private fun saveFollowing(
|
||||
creator: Member,
|
||||
member: Member,
|
||||
@@ -313,6 +269,87 @@ class DefaultCreatorRankingAggregationRepositoryTest @Autowired constructor(
|
||||
updateTimestamps("creator_following", following.id!!, createdAt, updatedAt)
|
||||
}
|
||||
|
||||
private fun saveCharacter(creator: Member, name: String, isActive: Boolean = true): ChatCharacter {
|
||||
val character = ChatCharacter(
|
||||
characterUUID = "uuid-$name",
|
||||
name = name,
|
||||
description = "description",
|
||||
systemPrompt = "prompt",
|
||||
isActive = isActive
|
||||
)
|
||||
character.creatorMember = creator
|
||||
entityManager.persist(character)
|
||||
return character
|
||||
}
|
||||
|
||||
private fun saveChatRoom(sessionId: String): ChatRoom {
|
||||
val room = ChatRoom(sessionId = sessionId, title = sessionId)
|
||||
entityManager.persist(room)
|
||||
return room
|
||||
}
|
||||
|
||||
private fun saveParticipant(
|
||||
room: ChatRoom,
|
||||
participantType: ParticipantType,
|
||||
character: ChatCharacter?,
|
||||
isActive: Boolean
|
||||
): ChatParticipant {
|
||||
val participant = ChatParticipant(
|
||||
chatRoom = room,
|
||||
participantType = participantType,
|
||||
character = character,
|
||||
isActive = isActive
|
||||
)
|
||||
entityManager.persist(participant)
|
||||
return participant
|
||||
}
|
||||
|
||||
private fun saveChatMessage(room: ChatRoom, participant: ChatParticipant, isActive: Boolean, createdAt: LocalDateTime) {
|
||||
val message = ChatMessage(
|
||||
message = "message",
|
||||
chatRoom = room,
|
||||
participant = participant,
|
||||
isActive = isActive
|
||||
)
|
||||
entityManager.persist(message)
|
||||
entityManager.flush()
|
||||
updateTimestamps("chat_message", message.id!!, createdAt, createdAt)
|
||||
}
|
||||
|
||||
private fun saveLiveRoom(
|
||||
creator: Member,
|
||||
beginDateTime: LocalDateTime,
|
||||
channelName: String? = "channel-${creator.nickname}"
|
||||
): LiveRoom {
|
||||
val room = LiveRoom(
|
||||
title = "live-${creator.nickname}",
|
||||
notice = "notice",
|
||||
beginDateTime = beginDateTime,
|
||||
numberOfPeople = 10,
|
||||
isAdult = false
|
||||
)
|
||||
room.member = creator
|
||||
room.channelName = channelName
|
||||
entityManager.persist(room)
|
||||
return room
|
||||
}
|
||||
|
||||
private fun saveAudioContent(creator: Member, releaseDate: LocalDateTime): AudioContent {
|
||||
val theme = AudioContentTheme(theme = "theme-${creator.nickname}", image = "theme.png")
|
||||
entityManager.persist(theme)
|
||||
val content = AudioContent(
|
||||
title = "content-${creator.nickname}",
|
||||
detail = "detail",
|
||||
languageCode = "ko",
|
||||
releaseDate = releaseDate
|
||||
)
|
||||
content.member = creator
|
||||
content.theme = theme
|
||||
content.isActive = true
|
||||
entityManager.persist(content)
|
||||
return content
|
||||
}
|
||||
|
||||
private fun updateTimestamps(tableName: String, id: Long, createdAt: LocalDateTime, updatedAt: LocalDateTime) {
|
||||
entityManager.createNativeQuery(
|
||||
"update $tableName set created_at = :createdAt, updated_at = :updatedAt where id = :id"
|
||||
@@ -328,4 +365,10 @@ class DefaultCreatorRankingAggregationRepositoryTest @Autowired constructor(
|
||||
entityManager.flush()
|
||||
entityManager.clear()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val START_AT = LocalDateTime.of(2026, 5, 31, 15, 0)
|
||||
private val END_AT = LocalDateTime.of(2026, 6, 7, 15, 0)
|
||||
private val IN_PERIOD = LocalDateTime.of(2026, 6, 1, 0, 0)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user