fix(recommendation): 추천 탭 배너 조회 조건을 보정한다

This commit is contained in:
2026-08-05 21:16:55 +09:00
parent 8538880d81
commit 0193b77700
9 changed files with 140 additions and 22 deletions
@@ -1,5 +1,6 @@
package kr.co.vividnext.sodalive.v2.api.home.application
import kr.co.vividnext.sodalive.member.Member
import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService
import kr.co.vividnext.sodalive.v2.recommendation.application.HomeRecommendationQueryService
import kr.co.vividnext.sodalive.v2.recommendation.domain.RecommendedSectionType
@@ -44,6 +45,17 @@ class HomeRecommendationFacadeTest {
assertFalse(response.firstAudioContents[2].isPointAvailable)
}
@Test
@DisplayName("홈 추천은 회원의 성인 콘텐츠 조회 가능 여부를 배너 조회에 전달한다")
fun shouldPassAdultVisibilityToHomeBannerQuery() {
val member = Member(password = "password", nickname = "member").apply { id = 10L }
Mockito.doReturn(true).`when`(preferenceService).canViewAdultContent(member)
facade.getHomeRecommendations(member)
assertTrue(queryPort.bannerIncludeAdultBanners)
}
private fun firstAudio(price: Int, isPointAvailable: Boolean): HomeFirstAudioContentRecord {
return HomeFirstAudioContentRecord(
contentId = price.toLong() + 1,
@@ -61,6 +73,7 @@ class HomeRecommendationFacadeTest {
private class FakeHomeRecommendationQueryPort : HomeRecommendationQueryPort {
var firstAudioContents: List<HomeFirstAudioContentRecord> = emptyList()
var bannerIncludeAdultBanners: Boolean = false
override fun findLiveRecommendations(
offset: Long,
@@ -69,7 +82,14 @@ class HomeRecommendationFacadeTest {
includeAdultLives: Boolean
): List<HomeLiveRecommendationRecord> = emptyList()
override fun findHomeBanners(limit: Int, memberId: Long?): List<HomeBannerRecommendationRecord> = emptyList()
override fun findHomeBanners(
limit: Int,
memberId: Long?,
includeAdultBanners: Boolean
): List<HomeBannerRecommendationRecord> {
bannerIncludeAdultBanners = includeAdultBanners
return emptyList()
}
override fun findRecentlyActiveCreators(
limit: Int,
@@ -8,6 +8,7 @@ import kr.co.vividnext.sodalive.content.comment.AudioContentComment
import kr.co.vividnext.sodalive.content.like.AudioContentLike
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.creator.admin.content.series.Series
import kr.co.vividnext.sodalive.creator.admin.content.series.SeriesContent
@@ -41,25 +42,58 @@ class DefaultAudioRecommendationQueryRepositoryTest @Autowired constructor(
private val repository = DefaultAudioRecommendationQueryRepository(queryFactory, entityManager, "https://cdn.test")
@Test
@DisplayName("배너는 홈 추천 배너와 같은 활성/탭/차단 정책과 CDN URL을 적용한다")
fun shouldFindBannersWithHomeBannerPolicy() {
@DisplayName("배너는 콘텐츠 추천 탭과 성인 조회 가능 여부를 적용한다")
fun shouldFindBannersForContentRecommendationTabWithAdultVisibility() {
val viewer = saveMember("viewer", MemberRole.USER)
val visibleCreator = saveMember("visible-creator", MemberRole.CREATOR)
val blockedCreator = saveMember("blocked-creator", MemberRole.CREATOR)
val visibleBanner = saveBanner("visible.png", AudioContentBannerType.CREATOR, 1, creator = visibleCreator)
val adultBanner = saveBanner("adult.png", AudioContentBannerType.LINK, 2, isAdult = true, link = "https://adult.test")
saveBanner("inactive.png", AudioContentBannerType.LINK, 2, isActive = false, link = "https://inactive.test")
saveBanner("blocked.png", AudioContentBannerType.CREATOR, 3, creator = blockedCreator)
val recommendationTab = saveContentRecommendationTab()
val visibleBanner = saveBanner(
"visible.png",
AudioContentBannerType.CREATOR,
1,
creator = visibleCreator,
tab = recommendationTab
)
val adultBanner = saveBanner(
"adult.png",
AudioContentBannerType.LINK,
2,
isAdult = true,
link = "https://adult.test",
tab = recommendationTab
)
saveBanner(
"inactive.png",
AudioContentBannerType.LINK,
3,
isActive = false,
link = "https://inactive.test",
tab = recommendationTab
)
saveBanner(
"blocked.png",
AudioContentBannerType.CREATOR,
4,
creator = blockedCreator,
tab = recommendationTab
)
saveBanner("home.png", AudioContentBannerType.LINK, 1, link = "https://home.test")
saveBlock(viewer, blockedCreator)
flushAndClear()
val banners = repository.findBanners(limit = 20, memberId = viewer.id, canViewAdultContent = false)
val safeBanners = repository.findBanners(limit = 20, memberId = viewer.id, canViewAdultContent = false)
val allBanners = repository.findBanners(limit = 20, memberId = viewer.id, canViewAdultContent = true)
assertEquals(
listOf("https://cdn.test/${visibleBanner.thumbnailImage}", "https://cdn.test/${adultBanner.thumbnailImage}"),
banners.map { it.imageUrl }
listOf("https://cdn.test/${visibleBanner.thumbnailImage}"),
safeBanners.map { it.imageUrl }
)
assertEquals(visibleCreator.id, banners.first().creatorId)
assertEquals(
listOf("https://cdn.test/${visibleBanner.thumbnailImage}", "https://cdn.test/${adultBanner.thumbnailImage}"),
allBanners.map { it.imageUrl }
)
assertEquals(visibleCreator.id, safeBanners.first().creatorId)
}
@Test
@@ -369,7 +403,8 @@ class DefaultAudioRecommendationQueryRepositoryTest @Autowired constructor(
isActive: Boolean = true,
isAdult: Boolean = false,
creator: Member? = null,
link: String? = null
link: String? = null,
tab: AudioContentMainTab? = null
): AudioContentBanner {
val banner = AudioContentBanner(
thumbnailImage = thumbnailImage,
@@ -380,10 +415,18 @@ class DefaultAudioRecommendationQueryRepositoryTest @Autowired constructor(
)
banner.creator = creator
banner.link = link
banner.tab = tab
entityManager.persist(banner)
return banner
}
private fun saveContentRecommendationTab(): AudioContentMainTab {
entityManager.createNativeQuery(
"insert into audio_content_main_tab (id, title, is_active) values (2, '추천', true)"
).executeUpdate()
return entityManager.getReference(AudioContentMainTab::class.java, 2L)
}
private fun saveSeries(
title: String,
creator: Member,
@@ -262,6 +262,36 @@ class DefaultHomeRecommendationQueryRepositoryTest @Autowired constructor(
assertEquals(listOf(homeBanner.thumbnailImage), banners.map { it.thumbnailImage })
}
@Test
@DisplayName("홈 배너는 성인 콘텐츠 조회가 불가능하면 비성인 배너만 반환한다")
fun shouldExcludeAdultHomeBannersWhenAdultContentIsNotVisible() {
val safeBanner = saveBanner(
"safe-home-banner.png",
AudioContentBannerType.LINK,
orders = 1,
isActive = true,
link = "https://safe-home-banner.test"
)
val adultBanner = saveBanner(
"adult-home-banner.png",
AudioContentBannerType.LINK,
orders = 2,
isActive = true,
isAdult = true,
link = "https://adult-home-banner.test"
)
flushAndClear()
val safeBanners = repository.findHomeBanners(limit = 20)
val allBanners = repository.findHomeBanners(limit = 20, includeAdultBanners = true)
assertEquals(listOf(safeBanner.thumbnailImage), safeBanners.map { it.thumbnailImage })
assertEquals(
listOf(safeBanner.thumbnailImage, adultBanner.thumbnailImage),
allBanners.map { it.thumbnailImage }
)
}
@Test
@DisplayName("홈 배너는 비활성 대상 엔티티를 제외하고 LINK는 배너 자체 활성 상태만으로 조회한다")
fun shouldExcludeHomeBannersWithInactiveTargetsExceptLink() {
@@ -2658,6 +2688,7 @@ class DefaultHomeRecommendationQueryRepositoryTest @Autowired constructor(
type: AudioContentBannerType,
orders: Int,
isActive: Boolean,
isAdult: Boolean = false,
creator: Member? = null,
event: Event? = null,
series: Series? = null,
@@ -2668,7 +2699,7 @@ class DefaultHomeRecommendationQueryRepositoryTest @Autowired constructor(
thumbnailImage = thumbnailImage,
type = type,
lang = Lang.KO,
isAdult = false,
isAdult = isAdult,
isActive = isActive,
orders = orders
)
@@ -86,12 +86,13 @@ class HomeRecommendationQueryServiceTest {
}
@Test
@DisplayName("홈 배너 추천은 기본 20개를 활성 배너 조회 포트에 위임한다")
@DisplayName("홈 배너 추천은 기본 20개와 성인 노출 여부를 조회 포트에 위임한다")
fun shouldFindHomeBannersWithDefaultLimit() {
val banners = service.findHomeBanners(memberId = 100L)
val banners = service.findHomeBanners(memberId = 100L, includeAdultBanners = true)
assertEquals(20, port.bannerLimit)
assertEquals(100L, port.bannerMemberId)
assertEquals(true, port.bannerIncludeAdultBanners)
assertEquals(port.banners, banners)
}
@@ -964,6 +965,7 @@ class HomeRecommendationQueryServiceTest {
var liveIncludeAdultLives: Boolean? = null
var bannerLimit: Int? = null
var bannerMemberId: Long? = null
var bannerIncludeAdultBanners: Boolean? = null
var activeCreatorLimit: Int? = null
var activeCreatorMemberId: Long? = null
var activeCreatorIncludeAdultActivities: Boolean? = null
@@ -1063,9 +1065,14 @@ class HomeRecommendationQueryServiceTest {
return liveRecommendations
}
override fun findHomeBanners(limit: Int, memberId: Long?): List<HomeBannerRecommendationRecord> {
override fun findHomeBanners(
limit: Int,
memberId: Long?,
includeAdultBanners: Boolean
): List<HomeBannerRecommendationRecord> {
bannerLimit = limit
bannerMemberId = memberId
bannerIncludeAdultBanners = includeAdultBanners
return banners
}