feat(home): 인기 커뮤니티 집계 쿼리를 변경한다
This commit is contained in:
@@ -648,9 +648,10 @@ class DefaultHomeRecommendationQueryRepository(
|
|||||||
|
|
||||||
override fun findPopularCommunitySnapshots(
|
override fun findPopularCommunitySnapshots(
|
||||||
windowStart: LocalDateTime,
|
windowStart: LocalDateTime,
|
||||||
snapshotAt: LocalDateTime,
|
windowEndExclusive: LocalDateTime,
|
||||||
limit: Int
|
limit: Int
|
||||||
): List<RecommendationSnapshotRecord> {
|
): List<RecommendationSnapshotRecord> {
|
||||||
|
val snapshotAt = windowEndExclusive.minusSeconds(1)
|
||||||
val sql = """
|
val sql = """
|
||||||
with creator_debut as (
|
with creator_debut as (
|
||||||
select debut_events.creator_id as creator_id, min(debut_events.debut_at) as debut_at
|
select debut_events.creator_id as creator_id, min(debut_events.debut_at) as debut_at
|
||||||
@@ -675,7 +676,7 @@ class DefaultHomeRecommendationQueryRepository(
|
|||||||
from creator_community_like ccl
|
from creator_community_like ccl
|
||||||
where ccl.is_active = true
|
where ccl.is_active = true
|
||||||
and ccl.created_at >= :windowStart
|
and ccl.created_at >= :windowStart
|
||||||
and ccl.created_at <= :snapshotAt
|
and ccl.created_at < :windowEndExclusive
|
||||||
group by ccl.creator_community_id
|
group by ccl.creator_community_id
|
||||||
),
|
),
|
||||||
comment_stats as (
|
comment_stats as (
|
||||||
@@ -683,7 +684,7 @@ class DefaultHomeRecommendationQueryRepository(
|
|||||||
from creator_community_comment ccc
|
from creator_community_comment ccc
|
||||||
where ccc.is_active = true
|
where ccc.is_active = true
|
||||||
and ccc.created_at >= :windowStart
|
and ccc.created_at >= :windowStart
|
||||||
and ccc.created_at <= :snapshotAt
|
and ccc.created_at < :windowEndExclusive
|
||||||
group by ccc.creator_community_id
|
group by ccc.creator_community_id
|
||||||
),
|
),
|
||||||
follower_stats as (
|
follower_stats as (
|
||||||
@@ -697,9 +698,9 @@ class DefaultHomeRecommendationQueryRepository(
|
|||||||
(case when cc.is_comment_available = true then coalesce(cs.comment_count, 0) else 0 end) * ${RecommendationScoreSpec.COMMUNITY_COMMENT_WEIGHT} +
|
(case when cc.is_comment_available = true then coalesce(cs.comment_count, 0) else 0 end) * ${RecommendationScoreSpec.COMMUNITY_COMMENT_WEIGHT} +
|
||||||
coalesce(fs.follower_count, 0) * ${RecommendationScoreSpec.COMMUNITY_FOLLOWER_WEIGHT}) *
|
coalesce(fs.follower_count, 0) * ${RecommendationScoreSpec.COMMUNITY_FOLLOWER_WEIGHT}) *
|
||||||
case
|
case
|
||||||
when cd.debut_at >= :boost10Start then ${RecommendationScoreSpec.NEW_BOOST_10_DAYS}
|
when cd.debut_at >= :boost10Start then ${RecommendationScoreSpec.COMMUNITY_NEW_BOOST_10_DAYS}
|
||||||
when cd.debut_at >= :boost20Start then ${RecommendationScoreSpec.NEW_BOOST_20_DAYS}
|
when cd.debut_at >= :boost20Start then ${RecommendationScoreSpec.COMMUNITY_NEW_BOOST_20_DAYS}
|
||||||
when cd.debut_at >= :boost30Start then ${RecommendationScoreSpec.NEW_BOOST_30_DAYS}
|
when cd.debut_at >= :boost30Start then ${RecommendationScoreSpec.COMMUNITY_NEW_BOOST_30_DAYS}
|
||||||
else ${RecommendationScoreSpec.DEFAULT_NEW_BOOST}
|
else ${RecommendationScoreSpec.DEFAULT_NEW_BOOST}
|
||||||
end) as score,
|
end) as score,
|
||||||
rand() as random_tie_breaker
|
rand() as random_tie_breaker
|
||||||
@@ -711,15 +712,27 @@ class DefaultHomeRecommendationQueryRepository(
|
|||||||
left join follower_stats fs on fs.creator_id = cc.member_id
|
left join follower_stats fs on fs.creator_id = cc.member_id
|
||||||
where cc.is_active = true
|
where cc.is_active = true
|
||||||
and m.is_active = true
|
and m.is_active = true
|
||||||
and cc.price = 0
|
and cc.price <= 0
|
||||||
and cc.is_fixed = false
|
and cc.is_fixed = false
|
||||||
and cc.created_at <= :snapshotAt
|
and cc.created_at < :windowEndExclusive
|
||||||
and cd.debut_at is not null
|
and cd.debut_at is not null
|
||||||
|
and (
|
||||||
|
coalesce(ls.like_count, 0) > 0
|
||||||
|
or (cc.is_comment_available = true and coalesce(cs.comment_count, 0) > 0)
|
||||||
|
or coalesce(fs.follower_count, 0) > 0
|
||||||
|
)
|
||||||
order by score desc, random_tie_breaker asc
|
order by score desc, random_tie_breaker asc
|
||||||
limit :limit
|
limit :limit
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
|
|
||||||
return executeSnapshotQuery(sql, RecommendedSectionType.POPULAR_COMMUNITY, windowStart, snapshotAt, limit)
|
return executeSnapshotQuery(
|
||||||
|
sql,
|
||||||
|
RecommendedSectionType.POPULAR_COMMUNITY,
|
||||||
|
windowStart,
|
||||||
|
snapshotAt,
|
||||||
|
limit,
|
||||||
|
windowEndExclusive
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun findAiCharacterRecommendationDetails(
|
override fun findAiCharacterRecommendationDetails(
|
||||||
@@ -832,6 +845,7 @@ class DefaultHomeRecommendationQueryRepository(
|
|||||||
creatorCommunity.isActive.isTrue,
|
creatorCommunity.isActive.isTrue,
|
||||||
member.isActive.isTrue,
|
member.isActive.isTrue,
|
||||||
creatorCommunity.isFixed.isFalse,
|
creatorCommunity.isFixed.isFalse,
|
||||||
|
creatorCommunity.price.loe(0),
|
||||||
includeAdultCommunityCondition(includeAdultCommunities),
|
includeAdultCommunityCondition(includeAdultCommunities),
|
||||||
notBlockedCreatorCondition(memberId, member.id),
|
notBlockedCreatorCondition(memberId, member.id),
|
||||||
creatorCommunity.id.`in`(communityIds)
|
creatorCommunity.id.`in`(communityIds)
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ interface HomeRecommendationQueryPort {
|
|||||||
|
|
||||||
fun findPopularCommunitySnapshots(
|
fun findPopularCommunitySnapshots(
|
||||||
windowStart: LocalDateTime,
|
windowStart: LocalDateTime,
|
||||||
snapshotAt: LocalDateTime,
|
windowEndExclusive: LocalDateTime,
|
||||||
limit: Int
|
limit: Int
|
||||||
): List<RecommendationSnapshotRecord>
|
): List<RecommendationSnapshotRecord>
|
||||||
|
|
||||||
|
|||||||
@@ -940,6 +940,7 @@ class DefaultHomeRecommendationQueryRepositoryTest @Autowired constructor(
|
|||||||
fun shouldFindPopularCommunitySnapshotsWithDistinctCounts() {
|
fun shouldFindPopularCommunitySnapshotsWithDistinctCounts() {
|
||||||
val windowStart = LocalDateTime.of(2026, 5, 23, 0, 0)
|
val windowStart = LocalDateTime.of(2026, 5, 23, 0, 0)
|
||||||
val snapshotAt = LocalDateTime.of(2026, 5, 29, 23, 59, 59)
|
val snapshotAt = LocalDateTime.of(2026, 5, 29, 23, 59, 59)
|
||||||
|
val windowEndExclusive = snapshotAt.plusSeconds(1)
|
||||||
val creator = saveMember("community-creator", MemberRole.CREATOR)
|
val creator = saveMember("community-creator", MemberRole.CREATOR)
|
||||||
val member1 = saveMember("community-member-1", MemberRole.USER)
|
val member1 = saveMember("community-member-1", MemberRole.USER)
|
||||||
val member2 = saveMember("community-member-2", MemberRole.USER)
|
val member2 = saveMember("community-member-2", MemberRole.USER)
|
||||||
@@ -970,20 +971,20 @@ class DefaultHomeRecommendationQueryRepositoryTest @Autowired constructor(
|
|||||||
updateCreatedAt("Member", creator.id!!, LocalDateTime.of(2026, 4, 1, 12, 0))
|
updateCreatedAt("Member", creator.id!!, LocalDateTime.of(2026, 4, 1, 12, 0))
|
||||||
flushAndClear()
|
flushAndClear()
|
||||||
|
|
||||||
val snapshots = repository.findPopularCommunitySnapshots(windowStart, snapshotAt, limit = 10)
|
val snapshots = repository.findPopularCommunitySnapshots(windowStart, windowEndExclusive, limit = 10)
|
||||||
.associateBy { it.targetId }
|
.associateBy { it.targetId }
|
||||||
|
|
||||||
val expectedPostScore = scorePolicy.calculateCommunityScore(
|
val expectedPostScore = scorePolicy.calculateCommunityScore(
|
||||||
likeCount = 2,
|
likeCount = 2,
|
||||||
commentCount = 2,
|
commentCount = 2,
|
||||||
followerCount = 2,
|
followerCount = 2,
|
||||||
newBoost = scorePolicy.calculateCreatorNewBoost(LocalDateTime.of(2026, 4, 1, 12, 0), snapshotAt)
|
newBoost = scorePolicy.calculateCommunityCreatorNewBoost(LocalDateTime.of(2026, 4, 1, 12, 0), snapshotAt)
|
||||||
)
|
)
|
||||||
val expectedCommentDisabledScore = scorePolicy.calculateCommunityScore(
|
val expectedCommentDisabledScore = scorePolicy.calculateCommunityScore(
|
||||||
likeCount = 0,
|
likeCount = 0,
|
||||||
commentCount = 0,
|
commentCount = 0,
|
||||||
followerCount = 2,
|
followerCount = 2,
|
||||||
newBoost = scorePolicy.calculateCreatorNewBoost(LocalDateTime.of(2026, 4, 1, 12, 0), snapshotAt)
|
newBoost = scorePolicy.calculateCommunityCreatorNewBoost(LocalDateTime.of(2026, 4, 1, 12, 0), snapshotAt)
|
||||||
)
|
)
|
||||||
assertEquals(expectedPostScore, snapshots[post.id]!!.score, 0.0001)
|
assertEquals(expectedPostScore, snapshots[post.id]!!.score, 0.0001)
|
||||||
assertEquals(expectedCommentDisabledScore, snapshots[commentDisabledPost.id]!!.score, 0.0001)
|
assertEquals(expectedCommentDisabledScore, snapshots[commentDisabledPost.id]!!.score, 0.0001)
|
||||||
@@ -994,6 +995,7 @@ class DefaultHomeRecommendationQueryRepositoryTest @Autowired constructor(
|
|||||||
fun shouldIncludeAdultCommunitiesInPopularCommunitySnapshots() {
|
fun shouldIncludeAdultCommunitiesInPopularCommunitySnapshots() {
|
||||||
val windowStart = LocalDateTime.of(2026, 5, 23, 0, 0)
|
val windowStart = LocalDateTime.of(2026, 5, 23, 0, 0)
|
||||||
val snapshotAt = LocalDateTime.of(2026, 5, 29, 23, 59, 59)
|
val snapshotAt = LocalDateTime.of(2026, 5, 29, 23, 59, 59)
|
||||||
|
val windowEndExclusive = snapshotAt.plusSeconds(1)
|
||||||
val creator = saveMember("adult-community-creator", MemberRole.CREATOR)
|
val creator = saveMember("adult-community-creator", MemberRole.CREATOR)
|
||||||
val member = saveMember("adult-community-member", MemberRole.USER)
|
val member = saveMember("adult-community-member", MemberRole.USER)
|
||||||
saveLiveRoom(creator, LocalDateTime.of(2026, 5, 10, 12, 0), channelName = "adult-community-live")
|
saveLiveRoom(creator, LocalDateTime.of(2026, 5, 10, 12, 0), channelName = "adult-community-live")
|
||||||
@@ -1007,7 +1009,7 @@ class DefaultHomeRecommendationQueryRepositoryTest @Autowired constructor(
|
|||||||
updateCreatedAt("CreatorCommunityLike", adultLike.id!!, windowStart.plusDays(1))
|
updateCreatedAt("CreatorCommunityLike", adultLike.id!!, windowStart.plusDays(1))
|
||||||
flushAndClear()
|
flushAndClear()
|
||||||
|
|
||||||
val snapshots = repository.findPopularCommunitySnapshots(windowStart, snapshotAt, limit = 10)
|
val snapshots = repository.findPopularCommunitySnapshots(windowStart, windowEndExclusive, limit = 10)
|
||||||
|
|
||||||
assertEquals(setOf(normalPost.id, adultPost.id), snapshots.map { it.targetId }.toSet())
|
assertEquals(setOf(normalPost.id, adultPost.id), snapshots.map { it.targetId }.toSet())
|
||||||
}
|
}
|
||||||
@@ -1017,6 +1019,7 @@ class DefaultHomeRecommendationQueryRepositoryTest @Autowired constructor(
|
|||||||
fun shouldFindPopularCommunitySnapshotsWithDbScoreOrderAndLimit() {
|
fun shouldFindPopularCommunitySnapshotsWithDbScoreOrderAndLimit() {
|
||||||
val windowStart = LocalDateTime.of(2026, 5, 23, 0, 0)
|
val windowStart = LocalDateTime.of(2026, 5, 23, 0, 0)
|
||||||
val snapshotAt = LocalDateTime.of(2026, 5, 29, 23, 59, 59)
|
val snapshotAt = LocalDateTime.of(2026, 5, 29, 23, 59, 59)
|
||||||
|
val windowEndExclusive = snapshotAt.plusSeconds(1)
|
||||||
val liker = saveMember("community-score-liker", MemberRole.USER)
|
val liker = saveMember("community-score-liker", MemberRole.USER)
|
||||||
val oldCreator = saveMember("old-community-score-creator", MemberRole.CREATOR)
|
val oldCreator = saveMember("old-community-score-creator", MemberRole.CREATOR)
|
||||||
val newCreator = saveMember("new-community-score-creator", MemberRole.CREATOR)
|
val newCreator = saveMember("new-community-score-creator", MemberRole.CREATOR)
|
||||||
@@ -1032,13 +1035,13 @@ class DefaultHomeRecommendationQueryRepositoryTest @Autowired constructor(
|
|||||||
updateCreatedAt("CreatorCommunityLike", newLike.id!!, windowStart.plusDays(1))
|
updateCreatedAt("CreatorCommunityLike", newLike.id!!, windowStart.plusDays(1))
|
||||||
flushAndClear()
|
flushAndClear()
|
||||||
|
|
||||||
val snapshots = repository.findPopularCommunitySnapshots(windowStart, snapshotAt, limit = 1)
|
val snapshots = repository.findPopularCommunitySnapshots(windowStart, windowEndExclusive, limit = 1)
|
||||||
|
|
||||||
val expectedScore = scorePolicy.calculateCommunityScore(
|
val expectedScore = scorePolicy.calculateCommunityScore(
|
||||||
likeCount = 1,
|
likeCount = 1,
|
||||||
commentCount = 0,
|
commentCount = 0,
|
||||||
followerCount = 0,
|
followerCount = 0,
|
||||||
newBoost = scorePolicy.calculateCreatorNewBoost(LocalDateTime.of(2026, 5, 20, 0, 0), snapshotAt)
|
newBoost = scorePolicy.calculateCommunityCreatorNewBoost(LocalDateTime.of(2026, 5, 20, 0, 0), snapshotAt)
|
||||||
)
|
)
|
||||||
assertEquals(1, snapshots.size)
|
assertEquals(1, snapshots.size)
|
||||||
assertEquals(RecommendedSectionType.POPULAR_COMMUNITY, snapshots.single().sectionType)
|
assertEquals(RecommendedSectionType.POPULAR_COMMUNITY, snapshots.single().sectionType)
|
||||||
@@ -1046,6 +1049,56 @@ class DefaultHomeRecommendationQueryRepositoryTest @Autowired constructor(
|
|||||||
assertEquals(expectedScore, snapshots.single().score, 0.0001)
|
assertEquals(expectedScore, snapshots.single().score, 0.0001)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("인기 커뮤니티 스냅샷은 좋아요 댓글 팔로워가 모두 0인 게시글을 제외한다")
|
||||||
|
fun shouldExcludeZeroMetricPopularCommunitySnapshots() {
|
||||||
|
val windowStart = LocalDateTime.of(2026, 5, 23, 0, 0)
|
||||||
|
val snapshotAt = LocalDateTime.of(2026, 5, 29, 23, 59, 59)
|
||||||
|
val windowEndExclusive = snapshotAt.plusSeconds(1)
|
||||||
|
val creator = saveMember("zero-community-creator", MemberRole.CREATOR)
|
||||||
|
val liker = saveMember("zero-community-liker", MemberRole.USER)
|
||||||
|
saveLiveRoom(creator, LocalDateTime.of(2026, 5, 10, 12, 0), channelName = "zero-community-live")
|
||||||
|
val zeroPost = saveCommunity(creator, isCommentAvailable = true)
|
||||||
|
val scoredPost = saveCommunity(creator, isCommentAvailable = true)
|
||||||
|
val like = saveCommunityLike(liker, scoredPost, isActive = true)
|
||||||
|
updateCreatedAt("CreatorCommunity", zeroPost.id!!, windowStart.plusDays(1))
|
||||||
|
updateCreatedAt("CreatorCommunity", scoredPost.id!!, windowStart.plusDays(1))
|
||||||
|
updateCreatedAt("CreatorCommunityLike", like.id!!, windowStart.plusDays(1))
|
||||||
|
flushAndClear()
|
||||||
|
|
||||||
|
val snapshots = repository.findPopularCommunitySnapshots(windowStart, windowEndExclusive, limit = 10)
|
||||||
|
|
||||||
|
assertEquals(listOf(scoredPost.id), snapshots.map { it.targetId })
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("인기 커뮤니티 스냅샷 후보는 점수가 있어도 유료와 고정 게시글을 제외한다")
|
||||||
|
fun shouldExcludePaidAndFixedPostsFromPopularCommunitySnapshots() {
|
||||||
|
val windowStart = LocalDateTime.of(2026, 5, 23, 0, 0)
|
||||||
|
val snapshotAt = LocalDateTime.of(2026, 5, 29, 23, 59, 59)
|
||||||
|
val windowEndExclusive = snapshotAt.plusSeconds(1)
|
||||||
|
val creator = saveMember("paid-fixed-community-creator", MemberRole.CREATOR)
|
||||||
|
val liker = saveMember("paid-fixed-community-liker", MemberRole.USER)
|
||||||
|
saveLiveRoom(creator, LocalDateTime.of(2026, 5, 10, 12, 0), channelName = "paid-fixed-community-live")
|
||||||
|
val eligiblePost = saveCommunity(creator, isCommentAvailable = true)
|
||||||
|
val paidPost = saveCommunity(creator, isCommentAvailable = true, price = 10)
|
||||||
|
val fixedPost = saveCommunity(creator, isCommentAvailable = true, isFixed = true)
|
||||||
|
val eligibleLike = saveCommunityLike(liker, eligiblePost, isActive = true)
|
||||||
|
val paidLike = saveCommunityLike(liker, paidPost, isActive = true)
|
||||||
|
val fixedLike = saveCommunityLike(liker, fixedPost, isActive = true)
|
||||||
|
listOf(eligiblePost, paidPost, fixedPost).forEach { post ->
|
||||||
|
updateCreatedAt("CreatorCommunity", post.id!!, windowStart.plusDays(1))
|
||||||
|
}
|
||||||
|
listOf(eligibleLike, paidLike, fixedLike).forEach { like ->
|
||||||
|
updateCreatedAt("CreatorCommunityLike", like.id!!, windowStart.plusDays(1))
|
||||||
|
}
|
||||||
|
flushAndClear()
|
||||||
|
|
||||||
|
val snapshots = repository.findPopularCommunitySnapshots(windowStart, windowEndExclusive, limit = 10)
|
||||||
|
|
||||||
|
assertEquals(listOf(eligiblePost.id), snapshots.map { it.targetId })
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@DisplayName("실제 데뷔일이 없는 크리에이터는 최근 응원과 인기 커뮤니티 스냅샷에서 제외한다")
|
@DisplayName("실제 데뷔일이 없는 크리에이터는 최근 응원과 인기 커뮤니티 스냅샷에서 제외한다")
|
||||||
fun shouldExcludeCreatorSnapshotsWithoutActualDebutAt() {
|
fun shouldExcludeCreatorSnapshotsWithoutActualDebutAt() {
|
||||||
@@ -1070,7 +1123,7 @@ class DefaultHomeRecommendationQueryRepositoryTest @Autowired constructor(
|
|||||||
flushAndClear()
|
flushAndClear()
|
||||||
|
|
||||||
val cheerSnapshots = repository.findCheerCreatorSnapshots(windowStart, windowEndExclusive, limit = 10)
|
val cheerSnapshots = repository.findCheerCreatorSnapshots(windowStart, windowEndExclusive, limit = 10)
|
||||||
val communitySnapshots = repository.findPopularCommunitySnapshots(windowStart, snapshotAt, limit = 10)
|
val communitySnapshots = repository.findPopularCommunitySnapshots(windowStart, windowEndExclusive, limit = 10)
|
||||||
|
|
||||||
assertEquals(emptyList<Any>(), cheerSnapshots)
|
assertEquals(emptyList<Any>(), cheerSnapshots)
|
||||||
assertEquals(emptyList<Any>(), communitySnapshots)
|
assertEquals(emptyList<Any>(), communitySnapshots)
|
||||||
@@ -1106,7 +1159,7 @@ class DefaultHomeRecommendationQueryRepositoryTest @Autowired constructor(
|
|||||||
flushAndClear()
|
flushAndClear()
|
||||||
|
|
||||||
val cheerSnapshot = repository.findCheerCreatorSnapshots(windowStart, windowEndExclusive, limit = 10).single()
|
val cheerSnapshot = repository.findCheerCreatorSnapshots(windowStart, windowEndExclusive, limit = 10).single()
|
||||||
val communitySnapshot = repository.findPopularCommunitySnapshots(windowStart, snapshotAt, limit = 10).single()
|
val communitySnapshot = repository.findPopularCommunitySnapshots(windowStart, windowEndExclusive, limit = 10).single()
|
||||||
|
|
||||||
val expectedCheerScore = scorePolicy.calculateCheerScore(
|
val expectedCheerScore = scorePolicy.calculateCheerScore(
|
||||||
donationAmount = 100,
|
donationAmount = 100,
|
||||||
@@ -1118,7 +1171,7 @@ class DefaultHomeRecommendationQueryRepositoryTest @Autowired constructor(
|
|||||||
likeCount = 1,
|
likeCount = 1,
|
||||||
commentCount = 0,
|
commentCount = 0,
|
||||||
followerCount = 0,
|
followerCount = 0,
|
||||||
newBoost = scorePolicy.calculateCreatorNewBoost(firstLiveAt, snapshotAt)
|
newBoost = scorePolicy.calculateCommunityCreatorNewBoost(firstLiveAt, snapshotAt)
|
||||||
)
|
)
|
||||||
assertEquals(expectedCheerScore, cheerSnapshot.score, 0.0001)
|
assertEquals(expectedCheerScore, cheerSnapshot.score, 0.0001)
|
||||||
assertEquals(snapshotAt, cheerSnapshot.snapshotAt)
|
assertEquals(snapshotAt, cheerSnapshot.snapshotAt)
|
||||||
@@ -1153,7 +1206,7 @@ class DefaultHomeRecommendationQueryRepositoryTest @Autowired constructor(
|
|||||||
flushAndClear()
|
flushAndClear()
|
||||||
|
|
||||||
val cheerSnapshot = repository.findCheerCreatorSnapshots(windowStart, windowEndExclusive, limit = 10).single()
|
val cheerSnapshot = repository.findCheerCreatorSnapshots(windowStart, windowEndExclusive, limit = 10).single()
|
||||||
val communitySnapshot = repository.findPopularCommunitySnapshots(windowStart, snapshotAt, limit = 10).single()
|
val communitySnapshot = repository.findPopularCommunitySnapshots(windowStart, windowEndExclusive, limit = 10).single()
|
||||||
|
|
||||||
val expectedCheerScore = scorePolicy.calculateCheerScore(
|
val expectedCheerScore = scorePolicy.calculateCheerScore(
|
||||||
donationAmount = 100,
|
donationAmount = 100,
|
||||||
@@ -1165,7 +1218,7 @@ class DefaultHomeRecommendationQueryRepositoryTest @Autowired constructor(
|
|||||||
likeCount = 1,
|
likeCount = 1,
|
||||||
commentCount = 0,
|
commentCount = 0,
|
||||||
followerCount = 0,
|
followerCount = 0,
|
||||||
newBoost = scorePolicy.calculateCreatorNewBoost(contentDebutAt, snapshotAt)
|
newBoost = scorePolicy.calculateCommunityCreatorNewBoost(contentDebutAt, snapshotAt)
|
||||||
)
|
)
|
||||||
assertEquals(expectedCheerScore, cheerSnapshot.score, 0.0001)
|
assertEquals(expectedCheerScore, cheerSnapshot.score, 0.0001)
|
||||||
assertEquals(snapshotAt, cheerSnapshot.snapshotAt)
|
assertEquals(snapshotAt, cheerSnapshot.snapshotAt)
|
||||||
@@ -1200,6 +1253,30 @@ class DefaultHomeRecommendationQueryRepositoryTest @Autowired constructor(
|
|||||||
assertEquals(listOf(newHighScoreCreator.id, newLowScoreCreator.id), creators.map { it.creatorId })
|
assertEquals(listOf(newHighScoreCreator.id, newLowScoreCreator.id), creators.map { it.creatorId })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("최근 데뷔 크리에이터는 인기 커뮤니티 전용 부스트가 아니라 기존 신규 부스트를 유지한다")
|
||||||
|
fun shouldKeepOriginalNewBoostForRecentDebutCreators() {
|
||||||
|
val now = LocalDateTime.of(2026, 5, 31, 10, 0)
|
||||||
|
val boostedCreator = saveMember("original-boost-debut", MemberRole.CREATOR)
|
||||||
|
val baseCreator = saveMember("base-boost-debut", MemberRole.CREATOR)
|
||||||
|
val fan = saveMember("original-boost-fan", MemberRole.USER)
|
||||||
|
val boostedContent = saveAudioContent(boostedCreator, now.minusDays(5), isActive = true)
|
||||||
|
val baseContent = saveAudioContent(baseCreator, now.minusDays(20), isActive = true)
|
||||||
|
val boostedFollowing = saveFollowing(fan, boostedCreator, isActive = true)
|
||||||
|
val baseLike1 = saveAudioContentLike(fan, baseContent, isActive = true)
|
||||||
|
val baseLike2 = saveAudioContentLike(fan, baseContent, isActive = true)
|
||||||
|
updateCreatedAt("AudioContent", boostedContent.id!!, now.minusDays(5))
|
||||||
|
updateCreatedAt("AudioContent", baseContent.id!!, now.minusDays(20))
|
||||||
|
updateCreatedAt("CreatorFollowing", boostedFollowing.id!!, now.minusDays(1))
|
||||||
|
updateCreatedAt("AudioContentLike", baseLike1.id!!, now.minusDays(1))
|
||||||
|
updateCreatedAt("AudioContentLike", baseLike2.id!!, now.minusDays(1))
|
||||||
|
flushAndClear()
|
||||||
|
|
||||||
|
val creators = repository.findRecentDebutCreators(now, limit = 2)
|
||||||
|
|
||||||
|
assertEquals(listOf(boostedCreator.id, baseCreator.id), creators.map { it.creatorId })
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@DisplayName("최근 데뷔 크리에이터 전체보기 조회는 offset/limit과 성인 콘텐츠 제외를 DB에서 적용한다")
|
@DisplayName("최근 데뷔 크리에이터 전체보기 조회는 offset/limit과 성인 콘텐츠 제외를 DB에서 적용한다")
|
||||||
fun shouldFindPagedRecentDebutCreatorsWithAdultFilter() {
|
fun shouldFindPagedRecentDebutCreatorsWithAdultFilter() {
|
||||||
@@ -1604,19 +1681,16 @@ class DefaultHomeRecommendationQueryRepositoryTest @Autowired constructor(
|
|||||||
)
|
)
|
||||||
|
|
||||||
val detailById = details.associateBy { it.communityId }
|
val detailById = details.associateBy { it.communityId }
|
||||||
assertEquals(setOf(eligible.id, paid.id), detailById.keys)
|
assertEquals(setOf(eligible.id), detailById.keys)
|
||||||
assertEquals("content", detailById[eligible.id]!!.content)
|
assertEquals("content", detailById[eligible.id]!!.content)
|
||||||
assertEquals("community/detail-image.png", detailById[eligible.id]!!.imagePath)
|
assertEquals("community/detail-image.png", detailById[eligible.id]!!.imagePath)
|
||||||
assertEquals("community/detail-audio.mp3", detailById[eligible.id]!!.audioPath)
|
assertEquals("community/detail-audio.mp3", detailById[eligible.id]!!.audioPath)
|
||||||
assertEquals(0, detailById[eligible.id]!!.price)
|
assertEquals(0, detailById[eligible.id]!!.price)
|
||||||
assertEquals(10, detailById[paid.id]!!.price)
|
|
||||||
assertEquals(LocalDateTime.of(2026, 5, 29, 1, 0), detailById[eligible.id]!!.createdAt)
|
assertEquals(LocalDateTime.of(2026, 5, 29, 1, 0), detailById[eligible.id]!!.createdAt)
|
||||||
assertEquals(2L, detailById[eligible.id]!!.likeCount)
|
assertEquals(2L, detailById[eligible.id]!!.likeCount)
|
||||||
assertEquals(1L, detailById[eligible.id]!!.commentCount)
|
assertEquals(1L, detailById[eligible.id]!!.commentCount)
|
||||||
assertEquals(false, detailById[eligible.id]!!.existOrdered)
|
assertEquals(false, detailById[eligible.id]!!.existOrdered)
|
||||||
assertEquals(true, detailById[paid.id]!!.existOrdered)
|
|
||||||
assertEquals(true, detailById[eligible.id]!!.isLiked)
|
assertEquals(true, detailById[eligible.id]!!.isLiked)
|
||||||
assertEquals(false, detailById[paid.id]!!.isLiked)
|
|
||||||
assertEquals(creator.id, detailById[eligible.id]!!.creatorId)
|
assertEquals(creator.id, detailById[eligible.id]!!.creatorId)
|
||||||
assertEquals("community-detail-creator", detailById[eligible.id]!!.creatorNickname)
|
assertEquals("community-detail-creator", detailById[eligible.id]!!.creatorNickname)
|
||||||
}
|
}
|
||||||
@@ -1625,16 +1699,16 @@ class DefaultHomeRecommendationQueryRepositoryTest @Autowired constructor(
|
|||||||
@DisplayName("인기 커뮤니티 상세는 비회원에게 구매 여부를 false로 반환한다")
|
@DisplayName("인기 커뮤니티 상세는 비회원에게 구매 여부를 false로 반환한다")
|
||||||
fun shouldReturnFalseOrderStatusForAnonymousPopularCommunityDetails() {
|
fun shouldReturnFalseOrderStatusForAnonymousPopularCommunityDetails() {
|
||||||
val creator = saveMember("anonymous-community-creator", MemberRole.CREATOR)
|
val creator = saveMember("anonymous-community-creator", MemberRole.CREATOR)
|
||||||
val paid = saveCommunity(creator, isCommentAvailable = true, price = 10)
|
val free = saveCommunity(creator, isCommentAvailable = true, price = 0)
|
||||||
flushAndClear()
|
flushAndClear()
|
||||||
|
|
||||||
val details = repository.findPopularCommunityRecommendationDetails(
|
val details = repository.findPopularCommunityRecommendationDetails(
|
||||||
listOf(paid.id!!),
|
listOf(free.id!!),
|
||||||
memberId = null,
|
memberId = null,
|
||||||
includeAdultCommunities = false
|
includeAdultCommunities = false
|
||||||
)
|
)
|
||||||
|
|
||||||
assertEquals(listOf(paid.id), details.map { it.communityId })
|
assertEquals(listOf(free.id), details.map { it.communityId })
|
||||||
assertEquals(listOf(false), details.map { it.existOrdered })
|
assertEquals(listOf(false), details.map { it.existOrdered })
|
||||||
assertEquals(listOf(false), details.map { it.isLiked })
|
assertEquals(listOf(false), details.map { it.isLiked })
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user