test #433

Merged
klaus merged 41 commits from test into main 2026-07-10 07:20:07 +00:00
Showing only changes of commit 61d5178eba - Show all commits

View File

@@ -7,6 +7,7 @@ import kr.co.vividnext.sodalive.member.MemberRole
import kr.co.vividnext.sodalive.member.block.BlockMember import kr.co.vividnext.sodalive.member.block.BlockMember
import kr.co.vividnext.sodalive.support.EmbeddedRedisInitializer import kr.co.vividnext.sodalive.support.EmbeddedRedisInitializer
import kr.co.vividnext.sodalive.v2.ranking.adapter.out.persistence.CreatorRankingSnapshot import kr.co.vividnext.sodalive.v2.ranking.adapter.out.persistence.CreatorRankingSnapshot
import kr.co.vividnext.sodalive.v2.ranking.domain.CreatorRankingScoreSpec
import kr.co.vividnext.sodalive.v2.ranking.domain.CreatorRankingType import kr.co.vividnext.sodalive.v2.ranking.domain.CreatorRankingType
import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
@@ -23,7 +24,13 @@ import org.springframework.transaction.annotation.Transactional
import java.time.LocalDateTime import java.time.LocalDateTime
import javax.persistence.EntityManager import javax.persistence.EntityManager
@SpringBootTest(properties = ["cloud.aws.cloud-front.host=https://cdn.test"]) @SpringBootTest(
properties = [
"cloud.aws.cloud-front.host=https://cdn.test",
"spring.datasource.url=jdbc:h2:mem:creator-ranking-controller;" +
"MODE=MySQL;DATABASE_TO_UPPER=false;NON_KEYWORDS=VALUE;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE"
]
)
@AutoConfigureMockMvc @AutoConfigureMockMvc
@Transactional @Transactional
@ContextConfiguration(initializers = [EmbeddedRedisInitializer::class]) @ContextConfiguration(initializers = [EmbeddedRedisInitializer::class])
@@ -33,13 +40,20 @@ class CreatorRankingControllerTest @Autowired constructor(
private val entityManager: EntityManager private val entityManager: EntityManager
) { ) {
@Test @Test
@DisplayName("크리에이터 랭킹 조회는 허용된 응답 필드만 반환하고 점수와 기간은 노출하지 않는") @DisplayName("크리에이터 랭킹 API는 공개 응답 스키마만 노출하고 내부 스냅샷 필드를 숨긴")
fun shouldReturnCreatorRankingSchemaWithoutScoreAndPeriodFields() { fun shouldReturnOnlyPublicCreatorRankingSchemaThroughEndpoint() {
val creatorOne = saveMember("creator-one", MemberRole.CREATOR)
val creatorTwo = saveMember("creator-two", MemberRole.CREATOR)
saveSnapshot(creatorId = creatorOne.id!!, nickname = "creator-one", profileImageUrl = "profile-one.png", rankNo = 2)
saveSnapshot(creatorId = creatorTwo.id!!, nickname = "creator-two", profileImageUrl = "profile-two.png", rankNo = 1)
saveSnapshot( saveSnapshot(
creatorId = 1L, creatorId = creatorOne.id!!,
nickname = "creator-one", nickname = "creator-one",
profileImageUrl = "profile-one.png", profileImageUrl = "profile-one.png",
finalScore = 100.0 rankNo = 1,
aggregationStartAtUtc = PREVIOUS_START,
aggregationEndAtUtc = PREVIOUS_END,
visibleFromAtUtc = PREVIOUS_VISIBLE
) )
entityManager.flush() entityManager.flush()
entityManager.clear() entityManager.clear()
@@ -47,23 +61,33 @@ class CreatorRankingControllerTest @Autowired constructor(
mockMvc.perform(get("/api/v2/home/rankings/creators")) mockMvc.perform(get("/api/v2/home/rankings/creators"))
.andExpect(status().isOk) .andExpect(status().isOk)
.andExpect(jsonPath("$.success").value(true)) .andExpect(jsonPath("$.success").value(true))
.andExpect(jsonPath("$.data.showRankChange").value(false)) .andExpect(jsonPath("$.data.showRankChange").value(true))
.andExpect(jsonPath("$.data.items[0].rank").value(1)) .andExpect(jsonPath("$.data.items[0].rank").value(1))
.andExpect(jsonPath("$.data.items[0].rankChange").doesNotExist()) .andExpect(jsonPath("$.data.items[0].rankChange").doesNotExist())
.andExpect(jsonPath("$.data.items[0].isNew").value(false)) .andExpect(jsonPath("$.data.items[0].isNew").value(true))
.andExpect(jsonPath("$.data.items[0].creatorId").value(1L)) .andExpect(jsonPath("$.data.items[0].creatorId").value(creatorTwo.id))
.andExpect(jsonPath("$.data.items[0].nickname").value("creator-one")) .andExpect(jsonPath("$.data.items[0].nickname").value("creator-two"))
.andExpect(jsonPath("$.data.items[0].profileImageUrl").value("https://cdn.test/profile-one.png")) .andExpect(jsonPath("$.data.items[0].profileImageUrl").value("https://cdn.test/profile-two.png"))
.andExpect(jsonPath("$.data.items[1].rank").value(2))
.andExpect(jsonPath("$.data.items[1].rankChange").value(-1))
.andExpect(jsonPath("$.data.items[1].isNew").value(false))
.andExpect(jsonPath("$.data.items[1].creatorId").value(creatorOne.id))
.andExpect(jsonPath("$.data.items[1].nickname").value("creator-one"))
.andExpect(jsonPath("$.data.items[1].profileImageUrl").value("https://cdn.test/profile-one.png"))
.andExpect(jsonPath("$.data.items[0].finalScore").doesNotExist()) .andExpect(jsonPath("$.data.items[0].finalScore").doesNotExist())
.andExpect(jsonPath("$.data.items[0].aggregationStartAtUtc").doesNotExist()) .andExpect(jsonPath("$.data.items[0].rankNo").doesNotExist())
.andExpect(jsonPath("$.data.items[0].aggregationEndAtUtc").doesNotExist()) .andExpect(jsonPath("$.data.items[0].scorePolicyVersion").doesNotExist())
.andExpect(jsonPath("$.data.aggregationStartAtUtc").doesNotExist()) .andExpect(jsonPath("$.data.items[0].scoreDetailJson").doesNotExist())
.andExpect(jsonPath("$.data.aggregationEndAtUtc").doesNotExist()) .andExpect(jsonPath("$.data.fallback").doesNotExist())
.andExpect(jsonPath("$.data.finalScore").doesNotExist())
.andExpect(jsonPath("$.data.rankNo").doesNotExist())
.andExpect(jsonPath("$.data.scorePolicyVersion").doesNotExist())
.andExpect(jsonPath("$.data.scoreDetailJson").doesNotExist())
} }
@Test @Test
@DisplayName("크리에이터 랭킹 조회는 비회원도 호출 가능하고 빈 랭킹을 성공 응답으로 반환한") @DisplayName("크리에이터 랭킹 API는 비회원도 빈 랭킹을 성공 응답으로 조회할 수 있")
fun shouldReturnEmptyCreatorRankingsForAnonymous() { fun shouldReturnEmptySuccessForAnonymous() {
mockMvc.perform(get("/api/v2/home/rankings/creators")) mockMvc.perform(get("/api/v2/home/rankings/creators"))
.andExpect(status().isOk) .andExpect(status().isOk)
.andExpect(jsonPath("$.success").value(true)) .andExpect(jsonPath("$.success").value(true))
@@ -73,7 +97,7 @@ class CreatorRankingControllerTest @Autowired constructor(
} }
@Test @Test
@DisplayName("크리에이터 랭킹 조회는 인증 회원 id를 전달해 차단 크리에이터 정보를 마스킹한다") @DisplayName("크리에이터 랭킹 API는 인증 회원과 차단 관계인 크리에이터 정보를 마스킹한다")
fun shouldMaskBlockedCreatorForAuthenticatedMember() { fun shouldMaskBlockedCreatorForAuthenticatedMember() {
val viewer = saveMember("ranking-viewer", MemberRole.USER) val viewer = saveMember("ranking-viewer", MemberRole.USER)
val blockedCreator = saveMember("blocked-creator", MemberRole.CREATOR) val blockedCreator = saveMember("blocked-creator", MemberRole.CREATOR)
@@ -81,7 +105,7 @@ class CreatorRankingControllerTest @Autowired constructor(
creatorId = blockedCreator.id!!, creatorId = blockedCreator.id!!,
nickname = blockedCreator.nickname, nickname = blockedCreator.nickname,
profileImageUrl = "blocked-profile.png", profileImageUrl = "blocked-profile.png",
finalScore = 100.0 rankNo = 1
) )
saveBlock(member = viewer, blockedMember = blockedCreator) saveBlock(member = viewer, blockedMember = blockedCreator)
entityManager.flush() entityManager.flush()
@@ -97,6 +121,11 @@ class CreatorRankingControllerTest @Autowired constructor(
.andExpect(jsonPath("$.data.items[0].creatorId").value(0L)) .andExpect(jsonPath("$.data.items[0].creatorId").value(0L))
.andExpect(jsonPath("$.data.items[0].nickname").value("")) .andExpect(jsonPath("$.data.items[0].nickname").value(""))
.andExpect(jsonPath("$.data.items[0].profileImageUrl").value("https://cdn.test/profile/default-profile.png")) .andExpect(jsonPath("$.data.items[0].profileImageUrl").value("https://cdn.test/profile/default-profile.png"))
.andExpect(jsonPath("$.data.items[0].finalScore").doesNotExist())
.andExpect(jsonPath("$.data.items[0].rankNo").doesNotExist())
.andExpect(jsonPath("$.data.items[0].scorePolicyVersion").doesNotExist())
.andExpect(jsonPath("$.data.items[0].scoreDetailJson").doesNotExist())
.andExpect(jsonPath("$.data.fallback").doesNotExist())
} }
private fun saveMember(seed: String, role: MemberRole): Member { private fun saveMember(seed: String, role: MemberRole): Member {
@@ -123,32 +152,34 @@ class CreatorRankingControllerTest @Autowired constructor(
creatorId: Long, creatorId: Long,
nickname: String, nickname: String,
profileImageUrl: String?, profileImageUrl: String?,
finalScore: Double rankNo: Int,
aggregationStartAtUtc: LocalDateTime = START,
aggregationEndAtUtc: LocalDateTime = END,
visibleFromAtUtc: LocalDateTime = VISIBLE
) { ) {
entityManager.persist( entityManager.persist(
CreatorRankingSnapshot( CreatorRankingSnapshot(
rankingType = CreatorRankingType.WEEKLY, rankingType = CreatorRankingType.WEEKLY,
aggregationStartAtUtc = LocalDateTime.of(2026, 5, 31, 15, 0, 0), aggregationStartAtUtc = aggregationStartAtUtc,
aggregationEndAtUtc = LocalDateTime.of(2026, 6, 7, 15, 0, 0), aggregationEndAtUtc = aggregationEndAtUtc,
visibleFromAtUtc = LocalDateTime.of(2026, 6, 8, 0, 0, 0), visibleFromAtUtc = visibleFromAtUtc,
creatorId = creatorId, creatorId = creatorId,
nickname = nickname, nickname = nickname,
profileImageUrl = profileImageUrl, profileImageUrl = profileImageUrl,
finalScore = finalScore, rankNo = rankNo,
contentLiveScore = 0.0, finalScore = (100 - rankNo).toDouble(),
engagementScore = 0.0, scorePolicyVersion = CreatorRankingScoreSpec.SCORE_POLICY_VERSION,
supportScore = 0.0, scoreDetailJson = "{}"
fanLoyaltyScore = 0.0,
liveCanAmount = 0,
contentPurchaseCanAmount = 0,
contentLikeCount = 0,
contentCommentCount = 0,
channelDonationCanAmount = 0,
channelDonationCount = 0,
fanTalkCount = 0,
finalFollowerCount = 0,
followIncrease = 0
) )
) )
} }
companion object {
private val PREVIOUS_START = LocalDateTime.of(2026, 5, 24, 15, 0)
private val PREVIOUS_END = LocalDateTime.of(2026, 5, 31, 15, 0)
private val PREVIOUS_VISIBLE = LocalDateTime.of(2026, 6, 1, 0, 0)
private val START = LocalDateTime.of(2026, 5, 31, 15, 0)
private val END = LocalDateTime.of(2026, 6, 7, 15, 0)
private val VISIBLE = LocalDateTime.of(2026, 6, 8, 0, 0)
}
} }