feat(recommendation): 추천 배너 언어 필터를 적용한다

This commit is contained in:
2026-08-19 17:10:05 +09:00
parent b1be87b46f
commit 1731b3a8c1
15 changed files with 154 additions and 18 deletions

View File

@@ -5,8 +5,8 @@ import kr.co.vividnext.sodalive.member.Member
private val FORCED_KR_MEMBER_IDS = setOf(16L, 17L, 17958L, 44144L) private val FORCED_KR_MEMBER_IDS = setOf(16L, 17L, 17958L, 44144L)
private val FORCED_JP_MEMBER_IDS = setOf(2L, 29721L, 32050L, 37543L, 40850L) private val FORCED_JP_MEMBER_IDS = setOf(2L, 29721L, 32050L, 37543L, 40850L)
fun resolveCountryCodeWithForcedMapping(member: Member, requestCountryCode: String?): String { fun resolveCountryCodeWithForcedMapping(member: Member?, requestCountryCode: String?): String {
val memberId = member.id val memberId = member?.id
if (memberId != null && FORCED_KR_MEMBER_IDS.contains(memberId)) { if (memberId != null && FORCED_KR_MEMBER_IDS.contains(memberId)) {
return "KR" return "KR"
} }

View File

@@ -157,8 +157,8 @@ class MemberContentPreferenceService(
return getStoredPreference(member).isAdult return getStoredPreference(member).isAdult
} }
fun resolveCountryCode(member: Member): String { fun resolveCountryCode(member: Member?): String {
requireMemberId(member) if (member != null) requireMemberId(member)
return resolveCountryCodeWithForcedMapping(member, countryContext.countryCode) return resolveCountryCodeWithForcedMapping(member, countryContext.countryCode)
} }

View File

@@ -1,6 +1,7 @@
package kr.co.vividnext.sodalive.v2.api.home.application package kr.co.vividnext.sodalive.v2.api.home.application
import kr.co.vividnext.sodalive.event.EventItem import kr.co.vividnext.sodalive.event.EventItem
import kr.co.vividnext.sodalive.i18n.Lang
import kr.co.vividnext.sodalive.member.Member import kr.co.vividnext.sodalive.member.Member
import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService
import kr.co.vividnext.sodalive.v2.api.common.dto.RecommendationBannerResponse import kr.co.vividnext.sodalive.v2.api.common.dto.RecommendationBannerResponse
@@ -46,6 +47,7 @@ class HomeRecommendationFacade(
return runCatching { return runCatching {
val now = LocalDateTime.now() val now = LocalDateTime.now()
val includeAdult = resolveAdultVisibility(member) val includeAdult = resolveAdultVisibility(member)
val bannerLang = if (memberContentPreferenceService.resolveCountryCode(member) == "JP") Lang.JA else Lang.KO
HomeRecommendationResponse( HomeRecommendationResponse(
lives = queryService.findLiveRecommendations( lives = queryService.findLiveRecommendations(
@@ -56,7 +58,8 @@ class HomeRecommendationFacade(
banners = queryService.findHomeBanners( banners = queryService.findHomeBanners(
limit = HOME_BANNER_LIMIT, limit = HOME_BANNER_LIMIT,
memberId = member?.id, memberId = member?.id,
includeAdultBanners = includeAdult includeAdultBanners = includeAdult,
lang = bannerLang
) )
.map { RecommendationBannerResponse.from(it.toBanner()) }, .map { RecommendationBannerResponse.from(it.toBanner()) },
recentlyActiveCreators = queryService.findRecentlyActiveCreators( recentlyActiveCreators = queryService.findRecentlyActiveCreators(

View File

@@ -15,6 +15,7 @@ import kr.co.vividnext.sodalive.creator.admin.content.series.QSeries.series
import kr.co.vividnext.sodalive.creator.admin.content.series.QSeriesContent.seriesContent import kr.co.vividnext.sodalive.creator.admin.content.series.QSeriesContent.seriesContent
import kr.co.vividnext.sodalive.event.EventItem import kr.co.vividnext.sodalive.event.EventItem
import kr.co.vividnext.sodalive.event.QEvent.event import kr.co.vividnext.sodalive.event.QEvent.event
import kr.co.vividnext.sodalive.i18n.Lang
import kr.co.vividnext.sodalive.member.QMember import kr.co.vividnext.sodalive.member.QMember
import kr.co.vividnext.sodalive.member.QMember.member import kr.co.vividnext.sodalive.member.QMember.member
import kr.co.vividnext.sodalive.member.block.QBlockMember import kr.co.vividnext.sodalive.member.block.QBlockMember
@@ -40,7 +41,12 @@ class DefaultAudioRecommendationQueryRepository(
@Value("\${cloud.aws.cloud-front.host}") @Value("\${cloud.aws.cloud-front.host}")
private val cloudFrontHost: String private val cloudFrontHost: String
) : AudioRecommendationQueryRepository { ) : AudioRecommendationQueryRepository {
override fun findBanners(limit: Int, memberId: Long?, canViewAdultContent: Boolean): List<RecommendationBanner> { override fun findBanners(
limit: Int,
memberId: Long?,
canViewAdultContent: Boolean,
lang: Lang
): List<RecommendationBanner> {
val bannerCreator = QMember("audioRecommendationBannerCreator") val bannerCreator = QMember("audioRecommendationBannerCreator")
val seriesOwner = QMember("audioRecommendationSeriesOwner") val seriesOwner = QMember("audioRecommendationSeriesOwner")
val randomTieBreaker = Expressions.numberTemplate(Double::class.java, "function('rand')") val randomTieBreaker = Expressions.numberTemplate(Double::class.java, "function('rand')")
@@ -64,6 +70,7 @@ class DefaultAudioRecommendationQueryRepository(
.where( .where(
audioContentBanner.isActive.isTrue, audioContentBanner.isActive.isTrue,
audioContentBanner.tab.id.eq(2L), audioContentBanner.tab.id.eq(2L),
audioContentBanner.lang.eq(lang),
adultBannerCondition(canViewAdultContent), adultBannerCondition(canViewAdultContent),
activeBannerTargetCondition(memberId, bannerCreator, seriesOwner) activeBannerTargetCondition(memberId, bannerCreator, seriesOwner)
) )

View File

@@ -1,5 +1,6 @@
package kr.co.vividnext.sodalive.v2.content.recommendation.application package kr.co.vividnext.sodalive.v2.content.recommendation.application
import kr.co.vividnext.sodalive.i18n.Lang
import kr.co.vividnext.sodalive.member.Member import kr.co.vividnext.sodalive.member.Member
import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService
import kr.co.vividnext.sodalive.v2.content.recommendation.domain.AudioCard import kr.co.vividnext.sodalive.v2.content.recommendation.domain.AudioCard
@@ -24,6 +25,7 @@ class AudioRecommendationQueryService(
val canViewAdultContent = canViewAdultContent(member) val canViewAdultContent = canViewAdultContent(member)
val visibility = if (canViewAdultContent) AudioRecommendationVisibility.ALL else AudioRecommendationVisibility.SAFE val visibility = if (canViewAdultContent) AudioRecommendationVisibility.ALL else AudioRecommendationVisibility.SAFE
val memberId = member?.id val memberId = member?.id
val bannerLang = if (memberContentPreferenceService.resolveCountryCode(member) == "JP") Lang.JA else Lang.KO
val newAndHotSectionType = newAndHotSectionType(visibility) val newAndHotSectionType = newAndHotSectionType(visibility)
val mostCommentedSectionType = mostCommentedSectionType(visibility) val mostCommentedSectionType = mostCommentedSectionType(visibility)
val recommendedAudioSectionType = recommendedAudioSectionType(visibility) val recommendedAudioSectionType = recommendedAudioSectionType(visibility)
@@ -44,7 +46,7 @@ class AudioRecommendationQueryService(
) )
return AudioRecommendations( return AudioRecommendations(
banners = queryPort.findBanners(BANNER_LIMIT, memberId, canViewAdultContent), banners = queryPort.findBanners(BANNER_LIMIT, memberId, canViewAdultContent, bannerLang),
originalSeries = queryPort.findOriginalSeries(ORIGINAL_SERIES_LIMIT, memberId, canViewAdultContent, now), originalSeries = queryPort.findOriginalSeries(ORIGINAL_SERIES_LIMIT, memberId, canViewAdultContent, now),
latestAudios = queryPort.findLatestAudios(LATEST_AUDIO_LIMIT, memberId, canViewAdultContent, now), latestAudios = queryPort.findLatestAudios(LATEST_AUDIO_LIMIT, memberId, canViewAdultContent, now),
newAndHotAudios = queryPort.findAudioCardsByIds( newAndHotAudios = queryPort.findAudioCardsByIds(

View File

@@ -1,5 +1,6 @@
package kr.co.vividnext.sodalive.v2.content.recommendation.port.out package kr.co.vividnext.sodalive.v2.content.recommendation.port.out
import kr.co.vividnext.sodalive.i18n.Lang
import kr.co.vividnext.sodalive.v2.common.domain.RecommendationBanner import kr.co.vividnext.sodalive.v2.common.domain.RecommendationBanner
import kr.co.vividnext.sodalive.v2.content.recommendation.domain.AudioCard import kr.co.vividnext.sodalive.v2.content.recommendation.domain.AudioCard
import kr.co.vividnext.sodalive.v2.content.recommendation.domain.AudioRecommendationVisibility import kr.co.vividnext.sodalive.v2.content.recommendation.domain.AudioRecommendationVisibility
@@ -9,7 +10,12 @@ import kr.co.vividnext.sodalive.v2.recommendation.port.out.RecommendationSnapsho
import java.time.LocalDateTime import java.time.LocalDateTime
interface AudioRecommendationQueryPort { interface AudioRecommendationQueryPort {
fun findBanners(limit: Int, memberId: Long?, canViewAdultContent: Boolean): List<RecommendationBanner> fun findBanners(
limit: Int,
memberId: Long?,
canViewAdultContent: Boolean,
lang: Lang = Lang.KO
): List<RecommendationBanner>
fun findOriginalSeries(limit: Int, memberId: Long?, canViewAdultContent: Boolean, now: LocalDateTime): List<OriginalSeries> fun findOriginalSeries(limit: Int, memberId: Long?, canViewAdultContent: Boolean, now: LocalDateTime): List<OriginalSeries>
fun findLatestAudios(limit: Int, memberId: Long?, canViewAdultContent: Boolean, now: LocalDateTime): List<AudioCard> fun findLatestAudios(limit: Int, memberId: Long?, canViewAdultContent: Boolean, now: LocalDateTime): List<AudioCard>
fun findFreeAudios(limit: Int, memberId: Long?, canViewAdultContent: Boolean, now: LocalDateTime): List<AudioCard> fun findFreeAudios(limit: Int, memberId: Long?, canViewAdultContent: Boolean, now: LocalDateTime): List<AudioCard>

View File

@@ -20,6 +20,7 @@ import kr.co.vividnext.sodalive.event.QEvent.event
import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.QCreatorCommunity.creatorCommunity import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.QCreatorCommunity.creatorCommunity
import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.comment.QCreatorCommunityComment.creatorCommunityComment import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.comment.QCreatorCommunityComment.creatorCommunityComment
import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.like.QCreatorCommunityLike.creatorCommunityLike import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.like.QCreatorCommunityLike.creatorCommunityLike
import kr.co.vividnext.sodalive.i18n.Lang
import kr.co.vividnext.sodalive.live.room.QLiveRoom.liveRoom import kr.co.vividnext.sodalive.live.room.QLiveRoom.liveRoom
import kr.co.vividnext.sodalive.member.MemberKind import kr.co.vividnext.sodalive.member.MemberKind
import kr.co.vividnext.sodalive.member.MemberRole import kr.co.vividnext.sodalive.member.MemberRole
@@ -88,7 +89,8 @@ class DefaultHomeRecommendationQueryRepository(
override fun findHomeBanners( override fun findHomeBanners(
limit: Int, limit: Int,
memberId: Long?, memberId: Long?,
includeAdultBanners: Boolean includeAdultBanners: Boolean,
lang: Lang
): List<HomeBannerRecommendationRecord> { ): List<HomeBannerRecommendationRecord> {
val bannerCreator = QMember("bannerCreator") val bannerCreator = QMember("bannerCreator")
val seriesOwner = QMember("seriesOwner") val seriesOwner = QMember("seriesOwner")
@@ -116,6 +118,7 @@ class DefaultHomeRecommendationQueryRepository(
.where( .where(
audioContentBanner.isActive.isTrue, audioContentBanner.isActive.isTrue,
audioContentBanner.tab.isNull, audioContentBanner.tab.isNull,
audioContentBanner.lang.eq(lang),
includeAdultBannerCondition(includeAdultBanners), includeAdultBannerCondition(includeAdultBanners),
activeBannerTargetCondition(memberId, bannerCreator, seriesOwner) activeBannerTargetCondition(memberId, bannerCreator, seriesOwner)
) )

View File

@@ -1,5 +1,6 @@
package kr.co.vividnext.sodalive.v2.recommendation.application package kr.co.vividnext.sodalive.v2.recommendation.application
import kr.co.vividnext.sodalive.i18n.Lang
import kr.co.vividnext.sodalive.v2.common.domain.CreatorActivityType import kr.co.vividnext.sodalive.v2.common.domain.CreatorActivityType
import kr.co.vividnext.sodalive.v2.recommendation.domain.RecommendationSnapshotWindowPolicy import kr.co.vividnext.sodalive.v2.recommendation.domain.RecommendationSnapshotWindowPolicy
import kr.co.vividnext.sodalive.v2.recommendation.domain.RecommendedSectionType import kr.co.vividnext.sodalive.v2.recommendation.domain.RecommendedSectionType
@@ -44,9 +45,10 @@ class HomeRecommendationQueryService(
fun findHomeBanners( fun findHomeBanners(
limit: Int = DEFAULT_BANNER_LIMIT, limit: Int = DEFAULT_BANNER_LIMIT,
memberId: Long? = null, memberId: Long? = null,
includeAdultBanners: Boolean = false includeAdultBanners: Boolean = false,
lang: Lang = Lang.KO
): List<HomeBannerRecommendationRecord> { ): List<HomeBannerRecommendationRecord> {
return queryPort.findHomeBanners(limit, memberId, includeAdultBanners) return queryPort.findHomeBanners(limit, memberId, includeAdultBanners, lang)
} }
fun findRecentlyActiveCreators( fun findRecentlyActiveCreators(

View File

@@ -1,5 +1,6 @@
package kr.co.vividnext.sodalive.v2.recommendation.port.out package kr.co.vividnext.sodalive.v2.recommendation.port.out
import kr.co.vividnext.sodalive.i18n.Lang
import kr.co.vividnext.sodalive.v2.common.domain.CreatorActivityType import kr.co.vividnext.sodalive.v2.common.domain.CreatorActivityType
import java.time.LocalDateTime import java.time.LocalDateTime
@@ -14,7 +15,8 @@ interface HomeRecommendationQueryPort {
fun findHomeBanners( fun findHomeBanners(
limit: Int, limit: Int,
memberId: Long? = null, memberId: Long? = null,
includeAdultBanners: Boolean = false includeAdultBanners: Boolean = false,
lang: Lang = Lang.KO
): List<HomeBannerRecommendationRecord> ): List<HomeBannerRecommendationRecord>
fun findRecentlyActiveCreators( fun findRecentlyActiveCreators(

View File

@@ -207,6 +207,22 @@ class MemberContentPreferenceIntegrationTest @Autowired constructor(
assertEquals("KR", service.resolveCountryCode(member)) assertEquals("KR", service.resolveCountryCode(member))
} }
@Test
@DisplayName("비로그인 사용자의 국가 코드는 접속 국가 헤더를 정규화하고 없으면 KR로 계산된다")
fun shouldResolveCountryCodeForAnonymousUser() {
countryContext.setCountryCode("JP")
assertEquals("JP", service.resolveCountryCode(null))
countryContext.setCountryCode(" jp ")
assertEquals("JP", service.resolveCountryCode(null))
countryContext.setCountryCode(null)
assertEquals("KR", service.resolveCountryCode(null))
countryContext.setCountryCode(" ")
assertEquals("KR", service.resolveCountryCode(null))
}
private fun saveMember(seed: String): Member { private fun saveMember(seed: String): Member {
return memberRepository.saveAndFlush( return memberRepository.saveAndFlush(
Member( Member(

View File

@@ -1,5 +1,6 @@
package kr.co.vividnext.sodalive.v2.api.home.application package kr.co.vividnext.sodalive.v2.api.home.application
import kr.co.vividnext.sodalive.i18n.Lang
import kr.co.vividnext.sodalive.member.Member import kr.co.vividnext.sodalive.member.Member
import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService
import kr.co.vividnext.sodalive.v2.recommendation.application.HomeRecommendationQueryService import kr.co.vividnext.sodalive.v2.recommendation.application.HomeRecommendationQueryService
@@ -56,6 +57,27 @@ class HomeRecommendationFacadeTest {
assertTrue(queryPort.bannerIncludeAdultBanners) assertTrue(queryPort.bannerIncludeAdultBanners)
} }
@Test
@DisplayName("홈 추천은 JP 국가 판정이면 JA 배너 조회를 요청한다")
fun shouldPassJaToHomeBannerQueryForJapan() {
val member = Member(password = "password", nickname = "jp-member").apply { id = 11L }
Mockito.doReturn("JP").`when`(preferenceService).resolveCountryCode(member)
facade.getHomeRecommendations(member)
assertTrue(queryPort.bannerLang == Lang.JA)
}
@Test
@DisplayName("홈 추천은 JP 이외 국가 판정이면 KO 배너 조회를 요청한다")
fun shouldPassKoToHomeBannerQueryForNonJapan() {
Mockito.doReturn("KR").`when`(preferenceService).resolveCountryCode(null)
facade.getHomeRecommendations(null)
assertTrue(queryPort.bannerLang == Lang.KO)
}
private fun firstAudio(price: Int, isPointAvailable: Boolean): HomeFirstAudioContentRecord { private fun firstAudio(price: Int, isPointAvailable: Boolean): HomeFirstAudioContentRecord {
return HomeFirstAudioContentRecord( return HomeFirstAudioContentRecord(
contentId = price.toLong() + 1, contentId = price.toLong() + 1,
@@ -74,6 +96,7 @@ class HomeRecommendationFacadeTest {
private class FakeHomeRecommendationQueryPort : HomeRecommendationQueryPort { private class FakeHomeRecommendationQueryPort : HomeRecommendationQueryPort {
var firstAudioContents: List<HomeFirstAudioContentRecord> = emptyList() var firstAudioContents: List<HomeFirstAudioContentRecord> = emptyList()
var bannerIncludeAdultBanners: Boolean = false var bannerIncludeAdultBanners: Boolean = false
var bannerLang: Lang? = null
override fun findLiveRecommendations( override fun findLiveRecommendations(
offset: Long, offset: Long,
@@ -85,9 +108,11 @@ class HomeRecommendationFacadeTest {
override fun findHomeBanners( override fun findHomeBanners(
limit: Int, limit: Int,
memberId: Long?, memberId: Long?,
includeAdultBanners: Boolean includeAdultBanners: Boolean,
lang: Lang
): List<HomeBannerRecommendationRecord> { ): List<HomeBannerRecommendationRecord> {
bannerIncludeAdultBanners = includeAdultBanners bannerIncludeAdultBanners = includeAdultBanners
bannerLang = lang
return emptyList() return emptyList()
} }

View File

@@ -12,6 +12,7 @@ import kr.co.vividnext.sodalive.content.main.tab.AudioContentMainTab
import kr.co.vividnext.sodalive.content.theme.AudioContentTheme import kr.co.vividnext.sodalive.content.theme.AudioContentTheme
import kr.co.vividnext.sodalive.creator.admin.content.series.Series 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.creator.admin.content.series.SeriesContent
import kr.co.vividnext.sodalive.i18n.Lang
import kr.co.vividnext.sodalive.member.Member import kr.co.vividnext.sodalive.member.Member
import kr.co.vividnext.sodalive.member.MemberRole import kr.co.vividnext.sodalive.member.MemberRole
import kr.co.vividnext.sodalive.member.block.BlockMember import kr.co.vividnext.sodalive.member.block.BlockMember
@@ -96,6 +97,38 @@ class DefaultAudioRecommendationQueryRepositoryTest @Autowired constructor(
assertEquals(visibleCreator.id, safeBanners.first().creatorId) assertEquals(visibleCreator.id, safeBanners.first().creatorId)
} }
@Test
@DisplayName("콘텐츠 추천 배너는 언어 필터를 limit보다 먼저 적용한다")
fun shouldFilterBannersByLangBeforeLimit() {
val recommendationTab = saveContentRecommendationTab()
val koBanner = saveBanner(
"ko-audio-banner.png",
AudioContentBannerType.LINK,
1,
link = "https://ko.test",
tab = recommendationTab,
lang = Lang.KO
)
val jaBanner = saveBanner(
"ja-audio-banner.png",
AudioContentBannerType.LINK,
2,
link = "https://ja.test",
tab = recommendationTab,
lang = Lang.JA
)
flushAndClear()
assertEquals(
listOf("https://cdn.test/${koBanner.thumbnailImage}"),
repository.findBanners(1, null, false, Lang.KO).map { it.imageUrl }
)
assertEquals(
listOf("https://cdn.test/${jaBanner.thumbnailImage}"),
repository.findBanners(1, null, false, Lang.JA).map { it.imageUrl }
)
}
@Test @Test
@DisplayName("오리지널 시리즈는 활성 원본 시리즈를 최신순으로 반환하고 성인/차단 조건을 적용한다") @DisplayName("오리지널 시리즈는 활성 원본 시리즈를 최신순으로 반환하고 성인/차단 조건을 적용한다")
fun shouldFindOriginalSeriesWithVisibilityConditions() { fun shouldFindOriginalSeriesWithVisibilityConditions() {
@@ -404,11 +437,13 @@ class DefaultAudioRecommendationQueryRepositoryTest @Autowired constructor(
isAdult: Boolean = false, isAdult: Boolean = false,
creator: Member? = null, creator: Member? = null,
link: String? = null, link: String? = null,
tab: AudioContentMainTab? = null tab: AudioContentMainTab? = null,
lang: Lang = Lang.KO
): AudioContentBanner { ): AudioContentBanner {
val banner = AudioContentBanner( val banner = AudioContentBanner(
thumbnailImage = thumbnailImage, thumbnailImage = thumbnailImage,
type = type, type = type,
lang = lang,
isAdult = isAdult, isAdult = isAdult,
isActive = isActive, isActive = isActive,
orders = orders orders = orders

View File

@@ -1,5 +1,6 @@
package kr.co.vividnext.sodalive.v2.content.recommendation.application package kr.co.vividnext.sodalive.v2.content.recommendation.application
import kr.co.vividnext.sodalive.i18n.Lang
import kr.co.vividnext.sodalive.member.Member import kr.co.vividnext.sodalive.member.Member
import kr.co.vividnext.sodalive.member.MemberRole import kr.co.vividnext.sodalive.member.MemberRole
import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService
@@ -77,7 +78,7 @@ class AudioRecommendationQueryServiceTest {
0, 0,
AudioRecommendationQueryService.RECOMMENDED_AUDIO_LIMIT AudioRecommendationQueryService.RECOMMENDED_AUDIO_LIMIT
) )
Mockito.verify(queryPort).findBanners(AudioRecommendationQueryService.BANNER_LIMIT, null, false) Mockito.verify(queryPort).findBanners(AudioRecommendationQueryService.BANNER_LIMIT, null, false, Lang.KO)
Mockito.verify(queryPort).findLatestAudios( Mockito.verify(queryPort).findLatestAudios(
eqValue(AudioRecommendationQueryService.LATEST_AUDIO_LIMIT), eqValue(AudioRecommendationQueryService.LATEST_AUDIO_LIMIT),
Mockito.isNull(), Mockito.isNull(),
@@ -140,6 +141,7 @@ class AudioRecommendationQueryServiceTest {
fun shouldUseStoredPreferenceForMemberAdultVisibility() { fun shouldUseStoredPreferenceForMemberAdultVisibility() {
val member = member(id = 10L) val member = member(id = 10L)
Mockito.doReturn(true).`when`(preferenceService).canViewAdultContent(member) Mockito.doReturn(true).`when`(preferenceService).canViewAdultContent(member)
Mockito.doReturn("JP").`when`(preferenceService).resolveCountryCode(member)
stubFallback( stubFallback(
RecommendedSectionType.NEW_AND_HOT_AUDIO_ALL, RecommendedSectionType.NEW_AND_HOT_AUDIO_ALL,
0, 0,
@@ -150,6 +152,7 @@ class AudioRecommendationQueryServiceTest {
service.getRecommendations(member) service.getRecommendations(member)
Mockito.verify(preferenceService).canViewAdultContent(member) Mockito.verify(preferenceService).canViewAdultContent(member)
Mockito.verify(queryPort).findBanners(AudioRecommendationQueryService.BANNER_LIMIT, member.id, true, Lang.JA)
Mockito.verify(preferenceService, Mockito.never()).initializeDefaultPreference(member) Mockito.verify(preferenceService, Mockito.never()).initializeDefaultPreference(member)
verifyFallback( verifyFallback(
RecommendedSectionType.NEW_AND_HOT_AUDIO_ALL, RecommendedSectionType.NEW_AND_HOT_AUDIO_ALL,

View File

@@ -262,6 +262,23 @@ class DefaultHomeRecommendationQueryRepositoryTest @Autowired constructor(
assertEquals(listOf(homeBanner.thumbnailImage), banners.map { it.thumbnailImage }) assertEquals(listOf(homeBanner.thumbnailImage), banners.map { it.thumbnailImage })
} }
@Test
@DisplayName("홈 배너는 언어 필터를 limit보다 먼저 적용한다")
fun shouldFilterHomeBannersByLangBeforeLimit() {
val koBanner = saveBanner("ko-home-banner.png", AudioContentBannerType.LINK, 1, true, lang = Lang.KO)
val jaBanner = saveBanner("ja-home-banner.png", AudioContentBannerType.LINK, 2, true, lang = Lang.JA)
flushAndClear()
assertEquals(
listOf(koBanner.thumbnailImage),
repository.findHomeBanners(limit = 1, lang = Lang.KO).map { it.thumbnailImage }
)
assertEquals(
listOf(jaBanner.thumbnailImage),
repository.findHomeBanners(limit = 1, lang = Lang.JA).map { it.thumbnailImage }
)
}
@Test @Test
@DisplayName("홈 배너는 성인 콘텐츠 조회가 불가능하면 비성인 배너만 반환한다") @DisplayName("홈 배너는 성인 콘텐츠 조회가 불가능하면 비성인 배너만 반환한다")
fun shouldExcludeAdultHomeBannersWhenAdultContentIsNotVisible() { fun shouldExcludeAdultHomeBannersWhenAdultContentIsNotVisible() {
@@ -2693,12 +2710,13 @@ class DefaultHomeRecommendationQueryRepositoryTest @Autowired constructor(
event: Event? = null, event: Event? = null,
series: Series? = null, series: Series? = null,
tab: AudioContentMainTab? = null, tab: AudioContentMainTab? = null,
link: String? = null link: String? = null,
lang: Lang = Lang.KO
): AudioContentBanner { ): AudioContentBanner {
val banner = AudioContentBanner( val banner = AudioContentBanner(
thumbnailImage = thumbnailImage, thumbnailImage = thumbnailImage,
type = type, type = type,
lang = Lang.KO, lang = lang,
isAdult = isAdult, isAdult = isAdult,
isActive = isActive, isActive = isActive,
orders = orders orders = orders

View File

@@ -1,5 +1,6 @@
package kr.co.vividnext.sodalive.v2.recommendation.application package kr.co.vividnext.sodalive.v2.recommendation.application
import kr.co.vividnext.sodalive.i18n.Lang
import kr.co.vividnext.sodalive.v2.common.domain.CreatorActivityType import kr.co.vividnext.sodalive.v2.common.domain.CreatorActivityType
import kr.co.vividnext.sodalive.v2.recommendation.domain.RecommendedSectionType import kr.co.vividnext.sodalive.v2.recommendation.domain.RecommendedSectionType
import kr.co.vividnext.sodalive.v2.recommendation.port.out.HomeAiCharacterRecommendationRecord import kr.co.vividnext.sodalive.v2.recommendation.port.out.HomeAiCharacterRecommendationRecord
@@ -93,6 +94,16 @@ class HomeRecommendationQueryServiceTest {
assertEquals(20, port.bannerLimit) assertEquals(20, port.bannerLimit)
assertEquals(100L, port.bannerMemberId) assertEquals(100L, port.bannerMemberId)
assertEquals(true, port.bannerIncludeAdultBanners) assertEquals(true, port.bannerIncludeAdultBanners)
assertEquals(Lang.KO, port.bannerLang)
assertEquals(port.banners, banners)
}
@Test
@DisplayName("홈 배너 추천은 언어 조건을 조회 포트에 위임한다")
fun shouldDelegateHomeBannerLang() {
val banners = service.findHomeBanners(lang = Lang.JA)
assertEquals(Lang.JA, port.bannerLang)
assertEquals(port.banners, banners) assertEquals(port.banners, banners)
} }
@@ -966,6 +977,7 @@ class HomeRecommendationQueryServiceTest {
var bannerLimit: Int? = null var bannerLimit: Int? = null
var bannerMemberId: Long? = null var bannerMemberId: Long? = null
var bannerIncludeAdultBanners: Boolean? = null var bannerIncludeAdultBanners: Boolean? = null
var bannerLang: Lang? = null
var activeCreatorLimit: Int? = null var activeCreatorLimit: Int? = null
var activeCreatorMemberId: Long? = null var activeCreatorMemberId: Long? = null
var activeCreatorIncludeAdultActivities: Boolean? = null var activeCreatorIncludeAdultActivities: Boolean? = null
@@ -1068,11 +1080,13 @@ class HomeRecommendationQueryServiceTest {
override fun findHomeBanners( override fun findHomeBanners(
limit: Int, limit: Int,
memberId: Long?, memberId: Long?,
includeAdultBanners: Boolean includeAdultBanners: Boolean,
lang: Lang
): List<HomeBannerRecommendationRecord> { ): List<HomeBannerRecommendationRecord> {
bannerLimit = limit bannerLimit = limit
bannerMemberId = memberId bannerMemberId = memberId
bannerIncludeAdultBanners = includeAdultBanners bannerIncludeAdultBanners = includeAdultBanners
bannerLang = lang
return banners return banners
} }