feat(content-ranking): 랭킹 조회 언어를 전달한다
This commit is contained in:
+63
@@ -1,5 +1,7 @@
|
||||
package kr.co.vividnext.sodalive.v2.api.content.ranking.adapter.`in`.web
|
||||
|
||||
import kr.co.vividnext.sodalive.content.translation.ContentTranslation
|
||||
import kr.co.vividnext.sodalive.content.translation.ContentTranslationPayload
|
||||
import kr.co.vividnext.sodalive.member.Member
|
||||
import kr.co.vividnext.sodalive.member.MemberAdapter
|
||||
import kr.co.vividnext.sodalive.member.MemberRole
|
||||
@@ -29,6 +31,25 @@ class AudioRankingControllerTest @Autowired constructor(
|
||||
private val mockMvc: MockMvc,
|
||||
private val entityManager: EntityManager
|
||||
) {
|
||||
@Test
|
||||
@DisplayName("오디오 랭킹 조회는 같은 스냅샷에서 en-ja-en 제목 번역을 격리한다")
|
||||
fun shouldIsolateRequestedLocaleTitlesWithoutChangingRankingResponse() {
|
||||
saveSnapshot(contentId = 1L, rank = 1, aggregationStartAtUtc = PREVIOUS_START_AT, aggregationEndAtUtc = PREVIOUS_END_AT)
|
||||
saveSnapshot(contentId = 2L, rank = 2, aggregationStartAtUtc = PREVIOUS_START_AT, aggregationEndAtUtc = PREVIOUS_END_AT)
|
||||
saveSnapshot(contentId = 2L, rank = 1, aggregationStartAtUtc = LATEST_START_AT, aggregationEndAtUtc = LATEST_END_AT)
|
||||
saveSnapshot(contentId = 3L, rank = 2, aggregationStartAtUtc = LATEST_START_AT, aggregationEndAtUtc = LATEST_END_AT)
|
||||
saveContentTranslation(2L, "en", "en-title-2")
|
||||
saveContentTranslation(2L, "ja", "ja-title-2")
|
||||
saveContentTranslation(3L, "en", "en-title-3")
|
||||
saveContentTranslation(3L, "ja", "ja-title-3")
|
||||
entityManager.flush()
|
||||
entityManager.clear()
|
||||
|
||||
expectRisingRanking("en", "en-title-2", "en-title-3")
|
||||
expectRisingRanking("ja", "ja-title-2", "ja-title-3")
|
||||
expectRisingRanking("en", "en-title-2", "en-title-3")
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("오디오 랭킹 조회는 비회원에게 200 OK와 기본 WEEKLY_POPULAR 랭킹을 반환한다")
|
||||
fun shouldReturnWeeklyPopularRankingsForAnonymousByDefault() {
|
||||
@@ -113,6 +134,48 @@ class AudioRankingControllerTest @Autowired constructor(
|
||||
)
|
||||
}
|
||||
|
||||
private fun saveContentTranslation(contentId: Long, locale: String, title: String) {
|
||||
val translation = ContentTranslation(
|
||||
contentId = contentId,
|
||||
locale = locale,
|
||||
renderedPayload = ContentTranslationPayload(title = title, detail = "", tags = "")
|
||||
)
|
||||
entityManager.persist(translation)
|
||||
entityManager.flush()
|
||||
val payload = "{\"title\":\"$title\",\"detail\":\"\",\"tags\":\"\"}"
|
||||
entityManager.createNativeQuery(
|
||||
"update content_translation set rendered_payload = '$payload' format json where id = :id"
|
||||
)
|
||||
.setParameter("id", translation.id)
|
||||
.executeUpdate()
|
||||
}
|
||||
|
||||
private fun expectRisingRanking(locale: String, firstTitle: String, secondTitle: String) {
|
||||
mockMvc.perform(
|
||||
get("/api/v2/audio/rankings")
|
||||
.param("type", "RISING")
|
||||
.header("Accept-Language", locale)
|
||||
)
|
||||
.andExpect(status().isOk)
|
||||
.andExpect(jsonPath("$.success").value(true))
|
||||
.andExpect(jsonPath("$.data.showRankChange").value(true))
|
||||
.andExpect(jsonPath("$.data.type").value("RISING"))
|
||||
.andExpect(jsonPath("$.data.items[0].contentId").value(2L))
|
||||
.andExpect(jsonPath("$.data.items[0].title").value(firstTitle))
|
||||
.andExpect(jsonPath("$.data.items[0].creatorNickname").value("creator-2"))
|
||||
.andExpect(jsonPath("$.data.items[0].rank").value(1))
|
||||
.andExpect(jsonPath("$.data.items[0].rankChange").value(1))
|
||||
.andExpect(jsonPath("$.data.items[0].isNew").value(false))
|
||||
.andExpect(jsonPath("$.data.items[0].coverImageUrl").value("https://cdn.test/cover-2.png"))
|
||||
.andExpect(jsonPath("$.data.items[1].contentId").value(3L))
|
||||
.andExpect(jsonPath("$.data.items[1].title").value(secondTitle))
|
||||
.andExpect(jsonPath("$.data.items[1].creatorNickname").value("creator-3"))
|
||||
.andExpect(jsonPath("$.data.items[1].rank").value(2))
|
||||
.andExpect(jsonPath("$.data.items[1].rankChange").doesNotExist())
|
||||
.andExpect(jsonPath("$.data.items[1].isNew").value(true))
|
||||
.andExpect(jsonPath("$.data.items[1].coverImageUrl").value("https://cdn.test/cover-3.png"))
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val PREVIOUS_START_AT = LocalDateTime.of(2026, 5, 25, 15, 0)
|
||||
private val PREVIOUS_END_AT = LocalDateTime.of(2026, 6, 1, 15, 0)
|
||||
|
||||
+51
-4
@@ -1,5 +1,7 @@
|
||||
package kr.co.vividnext.sodalive.v2.content.ranking.application
|
||||
|
||||
import kr.co.vividnext.sodalive.i18n.Lang
|
||||
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||
import kr.co.vividnext.sodalive.member.Member
|
||||
import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService
|
||||
import kr.co.vividnext.sodalive.v2.content.ranking.domain.AudioRankingType
|
||||
@@ -9,6 +11,7 @@ import kr.co.vividnext.sodalive.v2.content.ranking.port.out.AudioRankingSnapshot
|
||||
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.junit.jupiter.api.extension.ExtendWith
|
||||
import org.mockito.Mockito
|
||||
@@ -21,6 +24,41 @@ import java.time.ZonedDateTime
|
||||
|
||||
@ExtendWith(OutputCaptureExtension::class)
|
||||
class AudioRankingQueryServiceTest {
|
||||
@Test
|
||||
@DisplayName("랭킹은 요청 locale의 제목만 바꾸고 순위 응답은 유지한다")
|
||||
fun shouldUseRequestedLocaleTitlesWithoutChangingRankingValues() {
|
||||
val snapshotPort = FakeAudioRankingQuerySnapshotPort()
|
||||
val originalLatestSnapshots = listOf(snapshot(contentId = 2L, rank = 1), snapshot(contentId = 1L, rank = 2))
|
||||
snapshotPort.latestSnapshots = originalLatestSnapshots
|
||||
snapshotPort.previousSnapshots = listOf(snapshot(contentId = 1L, rank = 1), snapshot(contentId = 2L, rank = 2))
|
||||
snapshotPort.latestSnapshotsByLocale = mapOf(
|
||||
"en" to originalLatestSnapshots.map { it.copy(title = "en-title-${it.contentId}") },
|
||||
"ja" to originalLatestSnapshots.map { it.copy(title = "ja-title-${it.contentId}") }
|
||||
)
|
||||
val langContext = LangContext().apply { setLang(Lang.EN) }
|
||||
val service = service(snapshotPort, langContext = langContext)
|
||||
|
||||
val english = service.getRankings(AudioRankingType.REVENUE, member = null)
|
||||
langContext.setLang(Lang.JA)
|
||||
val japanese = service.getRankings(AudioRankingType.REVENUE, member = null)
|
||||
langContext.setLang(Lang.EN)
|
||||
val englishAgain = service.getRankings(AudioRankingType.REVENUE, member = null)
|
||||
|
||||
assertEquals(listOf("en-title-2", "en-title-1"), english.items.map { it.title })
|
||||
assertEquals(listOf("ja-title-2", "ja-title-1"), japanese.items.map { it.title })
|
||||
assertEquals(listOf("en-title-2", "en-title-1"), englishAgain.items.map { it.title })
|
||||
assertEquals(
|
||||
english.copy(items = english.items.map { it.copy(title = "") }),
|
||||
japanese.copy(items = japanese.items.map { it.copy(title = "") })
|
||||
)
|
||||
assertEquals(
|
||||
english.copy(items = english.items.map { it.copy(title = "") }),
|
||||
englishAgain.copy(items = englishAgain.items.map { it.copy(title = "") })
|
||||
)
|
||||
assertEquals(listOf("en", "ja", "en"), snapshotPort.latestLocales)
|
||||
assertEquals(listOf("en", "ja", "en"), snapshotPort.previousLocales)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun shouldNotWrapGetRankingsInTransactionSoFallbackRequeryUsesFreshSnapshot() {
|
||||
val method = AudioRankingQueryService::class.java.getDeclaredMethod(
|
||||
@@ -223,7 +261,8 @@ class AudioRankingQueryServiceTest {
|
||||
snapshotPort: FakeAudioRankingQuerySnapshotPort,
|
||||
adultMember: Member? = null,
|
||||
blockPort: AudioRankingBlockPort = FakeAudioRankingBlockPort(),
|
||||
jobService: AudioRankingSnapshotJobService = Mockito.mock(AudioRankingSnapshotJobService::class.java)
|
||||
jobService: AudioRankingSnapshotJobService = Mockito.mock(AudioRankingSnapshotJobService::class.java),
|
||||
langContext: LangContext = LangContext()
|
||||
): AudioRankingQueryService {
|
||||
val memberContentPreferenceService = Mockito.mock(MemberContentPreferenceService::class.java)
|
||||
if (adultMember != null) {
|
||||
@@ -231,6 +270,7 @@ class AudioRankingQueryServiceTest {
|
||||
}
|
||||
return AudioRankingQueryService(
|
||||
snapshotPort = snapshotPort,
|
||||
langContext = langContext,
|
||||
memberContentPreferenceService = memberContentPreferenceService,
|
||||
blockPort = blockPort,
|
||||
jobService = jobService,
|
||||
@@ -275,25 +315,32 @@ private class FakeAudioRankingQuerySnapshotPort : AudioRankingSnapshotPort {
|
||||
var nowUtc: LocalDateTime? = null
|
||||
var currentAggregationStartAtUtc: LocalDateTime? = null
|
||||
var latestCallCount: Int = 0
|
||||
var latestLocales: MutableList<String> = mutableListOf()
|
||||
var previousLocales: MutableList<String> = mutableListOf()
|
||||
var latestSnapshotsByLocale: Map<String, List<AudioRankingSnapshotRecord>> = emptyMap()
|
||||
|
||||
override fun findLatestVisibleSnapshots(
|
||||
rankingType: AudioRankingType,
|
||||
nowUtc: LocalDateTime
|
||||
nowUtc: LocalDateTime,
|
||||
locale: String
|
||||
): List<AudioRankingSnapshotRecord> {
|
||||
this.nowUtc = nowUtc
|
||||
latestLocales.add(locale)
|
||||
latestCallCount += 1
|
||||
if (latestSnapshotsByCall.isNotEmpty()) {
|
||||
return latestSnapshotsByCall.getOrElse(latestCallCount - 1) { latestSnapshotsByCall.last() }
|
||||
}
|
||||
return latestSnapshots
|
||||
return latestSnapshotsByLocale[locale] ?: latestSnapshots
|
||||
}
|
||||
|
||||
override fun findPreviousVisibleSnapshots(
|
||||
rankingType: AudioRankingType,
|
||||
currentAggregationStartAtUtc: LocalDateTime,
|
||||
nowUtc: LocalDateTime
|
||||
nowUtc: LocalDateTime,
|
||||
locale: String
|
||||
): List<AudioRankingSnapshotRecord> {
|
||||
this.currentAggregationStartAtUtc = currentAggregationStartAtUtc
|
||||
previousLocales.add(locale)
|
||||
return previousSnapshots
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user