test(home): 크리에이터 랭킹 공개 응답 스키마를 검증한다
This commit is contained in:
@@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user