feat(audio-recommendation): 추천 콘텐츠 제목 번역을 적용한다
This commit is contained in:
+149
-8
@@ -10,6 +10,9 @@ import kr.co.vividnext.sodalive.content.main.banner.AudioContentBanner
|
||||
import kr.co.vividnext.sodalive.content.main.banner.AudioContentBannerType
|
||||
import kr.co.vividnext.sodalive.content.main.tab.AudioContentMainTab
|
||||
import kr.co.vividnext.sodalive.content.theme.AudioContentTheme
|
||||
import kr.co.vividnext.sodalive.content.translation.ContentTranslation
|
||||
import kr.co.vividnext.sodalive.content.translation.ContentTranslationPayload
|
||||
import kr.co.vividnext.sodalive.content.translation.ContentTranslationRepository
|
||||
import kr.co.vividnext.sodalive.creator.admin.content.series.Series
|
||||
import kr.co.vividnext.sodalive.creator.admin.content.series.SeriesContent
|
||||
import kr.co.vividnext.sodalive.i18n.Lang
|
||||
@@ -38,9 +41,15 @@ import javax.persistence.EntityManager
|
||||
@Import(QueryDslConfig::class)
|
||||
class DefaultAudioRecommendationQueryRepositoryTest @Autowired constructor(
|
||||
private val entityManager: EntityManager,
|
||||
queryFactory: JPAQueryFactory
|
||||
queryFactory: JPAQueryFactory,
|
||||
contentTranslationRepository: ContentTranslationRepository
|
||||
) {
|
||||
private val repository = DefaultAudioRecommendationQueryRepository(queryFactory, entityManager, "https://cdn.test")
|
||||
private val repository = DefaultAudioRecommendationQueryRepository(
|
||||
queryFactory,
|
||||
entityManager,
|
||||
contentTranslationRepository,
|
||||
"https://cdn.test"
|
||||
)
|
||||
|
||||
@Test
|
||||
@DisplayName("배너는 콘텐츠 추천 탭과 성인 조회 가능 여부를 적용한다")
|
||||
@@ -210,9 +219,27 @@ class DefaultAudioRecommendationQueryRepositoryTest @Autowired constructor(
|
||||
}
|
||||
flushAndClear()
|
||||
|
||||
val latestAudios = repository.findLatestAudios(12, viewer.id, canViewAdultContent = false, now = now)
|
||||
val freeAudios = repository.findFreeAudios(10, viewer.id, canViewAdultContent = false, now = now)
|
||||
val pointAudios = repository.findPointAudios(10, viewer.id, canViewAdultContent = false, now = now)
|
||||
val latestAudios = repository.findLatestAudios(
|
||||
12,
|
||||
viewer.id,
|
||||
canViewAdultContent = false,
|
||||
now = now,
|
||||
locale = Lang.KO.code
|
||||
)
|
||||
val freeAudios = repository.findFreeAudios(
|
||||
10,
|
||||
viewer.id,
|
||||
canViewAdultContent = false,
|
||||
now = now,
|
||||
locale = Lang.KO.code
|
||||
)
|
||||
val pointAudios = repository.findPointAudios(
|
||||
10,
|
||||
viewer.id,
|
||||
canViewAdultContent = false,
|
||||
now = now,
|
||||
locale = Lang.KO.code
|
||||
)
|
||||
|
||||
assertEquals(12, latestAudios.size)
|
||||
assertEquals(listOf(latest.id, paidPoint.id), latestAudios.take(2).map { it.audioContentId })
|
||||
@@ -297,7 +324,8 @@ class DefaultAudioRecommendationQueryRepositoryTest @Autowired constructor(
|
||||
val commented = repository.findCommentedAudiosByIds(
|
||||
contentIds = listOf(first.id!!, second.id!!),
|
||||
memberId = viewer.id,
|
||||
canViewAdultContent = false
|
||||
canViewAdultContent = false,
|
||||
locale = Lang.KO.code
|
||||
)
|
||||
|
||||
assertEquals(listOf(first.id), snapshots.map { it.targetId })
|
||||
@@ -308,6 +336,101 @@ class DefaultAudioRecommendationQueryRepositoryTest @Autowired constructor(
|
||||
assertEquals("https://cdn.test/commented.png", commented.first().imageUrl)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("오디오 카드 공통 QueryDSL 경로는 빈 문자열, 공백, 누락, 다른 언어 번역을 원문으로 대체한다")
|
||||
fun shouldTranslateAudioCardsByRequestedLocaleWithFallbackAndInputOrder() {
|
||||
val now = LocalDateTime.of(2026, 6, 23, 12, 0)
|
||||
val creator = saveMember("translated-card-creator", MemberRole.CREATOR)
|
||||
val theme = saveTheme()
|
||||
val translated = saveAudio(creator, theme, "translated-source", now.minusDays(1))
|
||||
val empty = saveAudio(creator, theme, "empty-source", now.minusDays(2))
|
||||
val whitespace = saveAudio(creator, theme, "whitespace-source", now.minusDays(3))
|
||||
val otherLocaleOnly = saveAudio(creator, theme, "other-locale-source", now.minusDays(4))
|
||||
val missing = saveAudio(creator, theme, "missing-source", now.minusDays(5))
|
||||
saveContentTranslation(translated, "en", "translated-en")
|
||||
saveContentTranslation(translated, "ja", "translated-ja")
|
||||
saveContentTranslation(empty, "en", "")
|
||||
saveContentTranslation(whitespace, "en", " ")
|
||||
saveContentTranslation(otherLocaleOnly, "ja", "other-locale-ja")
|
||||
flushAndClear()
|
||||
|
||||
val contentIds = listOf(missing.id!!, empty.id!!, whitespace.id!!, otherLocaleOnly.id!!, translated.id!!)
|
||||
val englishCards = repository.findAudioCardsByIds(contentIds, null, false, now, Lang.EN.code)
|
||||
val japaneseCards = repository.findAudioCardsByIds(contentIds, null, false, now, Lang.JA.code)
|
||||
|
||||
assertEquals(contentIds, englishCards.map { it.audioContentId })
|
||||
assertEquals(
|
||||
listOf("missing-source", "empty-source", "whitespace-source", "other-locale-source", "translated-en"),
|
||||
englishCards.map { it.title }
|
||||
)
|
||||
assertEquals(contentIds, japaneseCards.map { it.audioContentId })
|
||||
assertEquals(
|
||||
listOf("missing-source", "empty-source", "whitespace-source", "other-locale-ja", "translated-ja"),
|
||||
japaneseCards.map { it.title }
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("최근 댓글 많은 오디오 native query는 locale별 제목만 바꾸고 fallback과 댓글 필드를 보존한다")
|
||||
fun shouldTranslateCommentedAudiosByRequestedLocaleWithFallbackAndPreservedFields() {
|
||||
val now = LocalDateTime.now().minusHours(1)
|
||||
val creator = saveMember("translated-commented-creator", MemberRole.CREATOR)
|
||||
val writer = saveMember("translated-commented-writer", MemberRole.USER)
|
||||
val theme = saveTheme()
|
||||
val translated = saveAudio(creator, theme, "commented-translated-source", now.minusMinutes(1))
|
||||
val empty = saveAudio(creator, theme, "commented-empty-source", now.minusMinutes(2))
|
||||
val whitespace = saveAudio(creator, theme, "commented-whitespace-source", now.minusMinutes(3))
|
||||
val otherLocaleOnly = saveAudio(creator, theme, "commented-other-locale-source", now.minusMinutes(4))
|
||||
val missing = saveAudio(creator, theme, "commented-missing-source", now.minusMinutes(5))
|
||||
saveComment(translated, writer, "translated-comment", now)
|
||||
saveComment(empty, writer, "empty-comment", now)
|
||||
saveComment(whitespace, writer, "whitespace-comment", now)
|
||||
saveComment(otherLocaleOnly, writer, "other-locale-comment", now)
|
||||
saveComment(missing, writer, "missing-comment", now)
|
||||
saveContentTranslation(translated, "en", "commented-translated-en")
|
||||
saveContentTranslation(translated, "ja", "commented-translated-ja")
|
||||
saveContentTranslation(empty, "en", "")
|
||||
saveContentTranslation(whitespace, "en", " ")
|
||||
saveContentTranslation(otherLocaleOnly, "ja", "commented-other-locale-ja")
|
||||
flushAndClear()
|
||||
|
||||
val contentIds = listOf(missing.id!!, empty.id!!, whitespace.id!!, otherLocaleOnly.id!!, translated.id!!)
|
||||
val englishAudios = repository.findCommentedAudiosByIds(contentIds, null, false, Lang.EN.code)
|
||||
val japaneseAudios = repository.findCommentedAudiosByIds(contentIds, null, false, Lang.JA.code)
|
||||
|
||||
assertEquals(contentIds, englishAudios.map { it.audioContentId })
|
||||
assertEquals(contentIds, japaneseAudios.map { it.audioContentId })
|
||||
assertEquals(
|
||||
listOf(
|
||||
"commented-missing-source",
|
||||
"commented-empty-source",
|
||||
"commented-whitespace-source",
|
||||
"commented-other-locale-source",
|
||||
"commented-translated-en"
|
||||
),
|
||||
englishAudios.map { it.title }
|
||||
)
|
||||
assertEquals(
|
||||
listOf(
|
||||
"commented-missing-source",
|
||||
"commented-empty-source",
|
||||
"commented-whitespace-source",
|
||||
"commented-other-locale-ja",
|
||||
"commented-translated-ja"
|
||||
),
|
||||
japaneseAudios.map { it.title }
|
||||
)
|
||||
assertEquals(
|
||||
listOf("missing-comment", "empty-comment", "whitespace-comment", "other-locale-comment", "translated-comment"),
|
||||
englishAudios.map { it.latestComment }
|
||||
)
|
||||
assertEquals(englishAudios.map { it.latestComment }, japaneseAudios.map { it.latestComment })
|
||||
assertEquals(
|
||||
englishAudios.map { it.latestCommentWriterProfileImageUrl },
|
||||
japaneseAudios.map { it.latestCommentWriterProfileImageUrl }
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("최근 댓글 많은 오디오 스냅샷은 같은 크리에이터 후보 중 최고 점수 1개만 반환한다")
|
||||
fun shouldKeepOnlyHighestScoredMostCommentedSnapshotPerCreator() {
|
||||
@@ -356,7 +479,8 @@ class DefaultAudioRecommendationQueryRepositoryTest @Autowired constructor(
|
||||
val commented = repository.findCommentedAudiosByIds(
|
||||
contentIds = listOf(audio.id!!),
|
||||
memberId = viewer.id,
|
||||
canViewAdultContent = false
|
||||
canViewAdultContent = false,
|
||||
locale = Lang.KO.code
|
||||
)
|
||||
|
||||
assertEquals(1, commented.size)
|
||||
@@ -381,7 +505,8 @@ class DefaultAudioRecommendationQueryRepositoryTest @Autowired constructor(
|
||||
val commented = repository.findCommentedAudiosByIds(
|
||||
contentIds = listOf(audio.id!!),
|
||||
memberId = viewer.id,
|
||||
canViewAdultContent = false
|
||||
canViewAdultContent = false,
|
||||
locale = Lang.KO.code
|
||||
)
|
||||
|
||||
assertEquals(1, commented.size)
|
||||
@@ -507,6 +632,22 @@ class DefaultAudioRecommendationQueryRepositoryTest @Autowired constructor(
|
||||
return audio
|
||||
}
|
||||
|
||||
private fun saveContentTranslation(content: AudioContent, locale: String, title: String) {
|
||||
val translation = ContentTranslation(
|
||||
contentId = content.id!!,
|
||||
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 saveView(audio: AudioContent, viewedAt: LocalDateTime) {
|
||||
entityManager.persist(
|
||||
CreatorContentViewHistory(memberId = 1L, contentId = audio.id!!, genreId = 1L, viewedAt = viewedAt)
|
||||
|
||||
Reference in New Issue
Block a user