feat(home-recommendation): 라이브 입장 제한 필터를 적용한다

This commit is contained in:
2026-09-17 19:14:29 +09:00
parent 123b9b1a0e
commit 542bf81b58
10 changed files with 387 additions and 23 deletions
@@ -2,7 +2,9 @@ package kr.co.vividnext.sodalive.v2.api.home.application
import kr.co.vividnext.sodalive.event.EventItem
import kr.co.vividnext.sodalive.i18n.Lang
import kr.co.vividnext.sodalive.member.Gender
import kr.co.vividnext.sodalive.member.Member
import kr.co.vividnext.sodalive.member.MemberRole
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.home.dto.recommendation.HomeActiveCreatorItem
@@ -53,7 +55,9 @@ class HomeRecommendationFacade(
lives = queryService.findLiveRecommendations(
limit = HOME_LIVE_LIMIT,
memberId = member?.id,
includeAdultLives = includeAdult
includeAdultLives = includeAdult,
isViewerCreator = member?.role == MemberRole.CREATOR,
effectiveViewerGender = member?.effectiveGender()
).map { it.toItem() },
banners = queryService.findHomeBanners(
limit = HOME_BANNER_LIMIT,
@@ -125,7 +129,9 @@ class HomeRecommendationFacade(
offset = page.toOffset(size),
limit = size + 1,
memberId = member.id,
includeAdultLives = resolveAdultVisibility(member)
includeAdultLives = resolveAdultVisibility(member),
isViewerCreator = member.role == MemberRole.CREATOR,
effectiveViewerGender = member.effectiveGender()
)
fetched.toPage(page, size) { it.toItem() }
}.onSuccess {
@@ -209,6 +215,10 @@ class HomeRecommendationFacade(
return memberContentPreferenceService.canViewAdultContent(member)
}
private fun Member.effectiveGender(): Gender {
return auth?.let { if (it.gender == 1) Gender.MALE else Gender.FEMALE } ?: gender
}
private fun Int.toOffset(size: Int): Long = this.toLong() * size
private fun <S, T> List<S>.toPage(
@@ -1,6 +1,8 @@
package kr.co.vividnext.sodalive.v2.api.home.live.application
import kr.co.vividnext.sodalive.member.Gender
import kr.co.vividnext.sodalive.member.Member
import kr.co.vividnext.sodalive.member.MemberRole
import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService
import kr.co.vividnext.sodalive.v2.api.home.live.dto.HomeOnAirLivePageResponse
import kr.co.vividnext.sodalive.v2.api.home.live.dto.HomeOnAirLiveResponse
@@ -24,7 +26,9 @@ class HomeOnAirLiveFacade(
offset = normalizedPage.toLong() * PAGE_SIZE,
limit = PAGE_SIZE + 1,
memberId = member.id,
includeAdultLives = memberContentPreferenceService.canViewAdultContent(member)
includeAdultLives = memberContentPreferenceService.canViewAdultContent(member),
isViewerCreator = member.role == MemberRole.CREATOR,
effectiveViewerGender = member.effectiveGender()
)
val items = fetched.take(PAGE_SIZE).map { it.toResponse() }
@@ -57,6 +61,10 @@ class HomeOnAirLiveFacade(
return atOffset(ZoneOffset.UTC).toInstant().toString()
}
private fun Member.effectiveGender(): Gender {
return auth?.let { if (it.gender == 1) Gender.MALE else Gender.FEMALE } ?: gender
}
companion object {
private const val PAGE_SIZE = 20
private const val MAX_PAGE = 10_000
@@ -22,7 +22,9 @@ import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.QCreatorCommun
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.i18n.Lang
import kr.co.vividnext.sodalive.live.room.GenderRestriction
import kr.co.vividnext.sodalive.live.room.QLiveRoom.liveRoom
import kr.co.vividnext.sodalive.member.Gender
import kr.co.vividnext.sodalive.member.MemberKind
import kr.co.vividnext.sodalive.member.MemberRole
import kr.co.vividnext.sodalive.member.QMember
@@ -58,7 +60,9 @@ class DefaultHomeRecommendationQueryRepository(
offset: Long,
limit: Int,
memberId: Long?,
includeAdultLives: Boolean
includeAdultLives: Boolean,
isViewerCreator: Boolean,
effectiveViewerGender: Gender?
): List<HomeLiveRecommendationRecord> {
return queryFactory
.select(
@@ -79,6 +83,8 @@ class DefaultHomeRecommendationQueryRepository(
liveRoom.channelName.isNotNull,
liveRoom.channelName.isNotEmpty,
includeAdultLiveCondition(includeAdultLives),
genderLiveCondition(memberId, effectiveViewerGender),
creatorJoinLiveCondition(memberId, isViewerCreator),
notBlockedCreatorCondition(memberId, member.id),
member.isActive.isTrue
)
@@ -1294,6 +1300,21 @@ class DefaultHomeRecommendationQueryRepository(
return if (includeAdultLives) null else liveRoom.isAdult.isFalse
}
private fun genderLiveCondition(viewerId: Long?, effectiveViewerGender: Gender?): BooleanExpression? {
if (effectiveViewerGender == null || effectiveViewerGender == Gender.NONE) return null
val genderCondition = when (effectiveViewerGender) {
Gender.MALE -> liveRoom.genderRestriction.`in`(GenderRestriction.ALL, GenderRestriction.MALE_ONLY)
Gender.FEMALE -> liveRoom.genderRestriction.`in`(GenderRestriction.ALL, GenderRestriction.FEMALE_ONLY)
Gender.NONE -> return null
}
return viewerId?.let { genderCondition.or(liveRoom.member.id.eq(it)) } ?: genderCondition
}
private fun creatorJoinLiveCondition(viewerId: Long?, isViewerCreator: Boolean): BooleanExpression? {
if (!isViewerCreator || viewerId == null) return null
return liveRoom.isAvailableJoinCreator.isTrue.or(liveRoom.member.id.eq(viewerId))
}
private fun excludeAiCharacterCondition(excludeCharacterIds: List<Long>): BooleanExpression? {
return if (excludeCharacterIds.isEmpty()) null else chatCharacter.id.notIn(excludeCharacterIds)
}
@@ -2,6 +2,7 @@ package kr.co.vividnext.sodalive.v2.recommendation.application
import kr.co.vividnext.sodalive.i18n.Lang
import kr.co.vividnext.sodalive.i18n.LangContext
import kr.co.vividnext.sodalive.member.Gender
import kr.co.vividnext.sodalive.v2.common.domain.CreatorActivityType
import kr.co.vividnext.sodalive.v2.creator.channel.community.translation.application.CreatorCommunityTranslationService
import kr.co.vividnext.sodalive.v2.recommendation.domain.RecommendationSnapshotWindowPolicy
@@ -41,9 +42,18 @@ class HomeRecommendationQueryService(
offset: Long = 0,
limit: Int = DEFAULT_LIVE_LIMIT,
memberId: Long? = null,
includeAdultLives: Boolean = false
includeAdultLives: Boolean = false,
isViewerCreator: Boolean = false,
effectiveViewerGender: Gender? = null
): List<HomeLiveRecommendationRecord> {
return queryPort.findLiveRecommendations(offset, limit, memberId, includeAdultLives)
return queryPort.findLiveRecommendations(
offset,
limit,
memberId,
includeAdultLives,
isViewerCreator,
effectiveViewerGender
)
}
fun findHomeBanners(
@@ -1,6 +1,7 @@
package kr.co.vividnext.sodalive.v2.recommendation.port.out
import kr.co.vividnext.sodalive.i18n.Lang
import kr.co.vividnext.sodalive.member.Gender
import kr.co.vividnext.sodalive.v2.common.domain.CreatorActivityType
import java.time.LocalDateTime
@@ -9,7 +10,9 @@ interface HomeRecommendationQueryPort {
offset: Long = 0,
limit: Int,
memberId: Long? = null,
includeAdultLives: Boolean = false
includeAdultLives: Boolean = false,
isViewerCreator: Boolean = false,
effectiveViewerGender: Gender? = null
): List<HomeLiveRecommendationRecord>
fun findHomeBanners(
@@ -3,6 +3,7 @@ package kr.co.vividnext.sodalive.v2.api.home
import kr.co.vividnext.sodalive.chat.character.ChatCharacter
import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.CreatorCommunity
import kr.co.vividnext.sodalive.live.room.LiveRoom
import kr.co.vividnext.sodalive.member.Gender
import kr.co.vividnext.sodalive.member.Member
import kr.co.vividnext.sodalive.member.MemberAdapter
import kr.co.vividnext.sodalive.member.MemberKind
@@ -372,7 +373,9 @@ class HomeRecommendationControllerTest @Autowired constructor(
offset = 0,
limit = 21,
memberId = member.id,
includeAdultLives = false
includeAdultLives = false,
isViewerCreator = false,
effectiveViewerGender = Gender.NONE
)
)
.thenThrow(IllegalStateException("page query failed"))
@@ -2,7 +2,10 @@ package kr.co.vividnext.sodalive.v2.api.home.application
import kr.co.vividnext.sodalive.i18n.Lang
import kr.co.vividnext.sodalive.i18n.LangContext
import kr.co.vividnext.sodalive.member.Gender
import kr.co.vividnext.sodalive.member.Member
import kr.co.vividnext.sodalive.member.MemberRole
import kr.co.vividnext.sodalive.member.auth.Auth
import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService
import kr.co.vividnext.sodalive.v2.creator.channel.community.translation.application.CreatorCommunityTranslationService
import kr.co.vividnext.sodalive.v2.recommendation.application.HomeRecommendationQueryService
@@ -86,6 +89,42 @@ class HomeRecommendationFacadeTest {
assertTrue(queryPort.bannerLang == Lang.KO)
}
@Test
@DisplayName("홈 추천은 크리에이터 여부와 인증 성별 우선 유효 성별을 라이브 조회에 전달한다")
fun shouldPassCreatorRoleAndAuthGenderToHomeLiveQuery() {
val member = createMember(id = 12L, role = MemberRole.CREATOR, gender = Gender.FEMALE, authGender = 1)
facade.getHomeRecommendations(member)
assertTrue(queryPort.liveIsViewerCreator == true)
assertTrue(queryPort.liveEffectiveViewerGender == Gender.MALE)
}
@Test
@DisplayName("홈 라이브 목록은 크리에이터 여부와 회원 성별 fallback을 라이브 조회에 전달한다")
fun shouldPassCreatorRoleAndMemberGenderToLivePageQuery() {
val member = createMember(id = 13L, role = MemberRole.CREATOR, gender = Gender.FEMALE)
facade.getLives(member, page = 0, size = 20)
assertTrue(queryPort.liveIsViewerCreator == true)
assertTrue(queryPort.liveEffectiveViewerGender == Gender.FEMALE)
}
private fun createMember(
id: Long,
role: MemberRole = MemberRole.USER,
gender: Gender = Gender.NONE,
authGender: Int? = null
): Member {
return Member(password = "password", nickname = "member$id", role = role, gender = gender).apply {
this.id = id
authGender?.let {
Auth(name = "name", birth = "19900101", uniqueCi = "ci$id", di = "di$id", gender = it).member = this
}
}
}
private fun firstAudio(price: Int, isPointAvailable: Boolean): HomeFirstAudioContentRecord {
return HomeFirstAudioContentRecord(
contentId = price.toLong() + 1,
@@ -105,13 +144,21 @@ class HomeRecommendationFacadeTest {
var firstAudioContents: List<HomeFirstAudioContentRecord> = emptyList()
var bannerIncludeAdultBanners: Boolean = false
var bannerLang: Lang? = null
var liveIsViewerCreator: Boolean? = null
var liveEffectiveViewerGender: Gender? = null
override fun findLiveRecommendations(
offset: Long,
limit: Int,
memberId: Long?,
includeAdultLives: Boolean
): List<HomeLiveRecommendationRecord> = emptyList()
includeAdultLives: Boolean,
isViewerCreator: Boolean,
effectiveViewerGender: Gender?
): List<HomeLiveRecommendationRecord> {
liveIsViewerCreator = isViewerCreator
liveEffectiveViewerGender = effectiveViewerGender
return emptyList()
}
override fun findHomeBanners(
limit: Int,
@@ -1,7 +1,9 @@
package kr.co.vividnext.sodalive.v2.api.home.live.application
import kr.co.vividnext.sodalive.member.Gender
import kr.co.vividnext.sodalive.member.Member
import kr.co.vividnext.sodalive.member.MemberRole
import kr.co.vividnext.sodalive.member.auth.Auth
import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService
import kr.co.vividnext.sodalive.v2.recommendation.application.HomeRecommendationQueryService
import kr.co.vividnext.sodalive.v2.recommendation.port.out.HomeLiveRecommendationRecord
@@ -25,7 +27,9 @@ class HomeOnAirLiveFacadeTest {
eqValue(0L),
eqValue(21),
eqValue(member.id),
eqValue(true)
eqValue(true),
eqValue(false),
eqValue(Gender.NONE)
)
val response = facade.getOnAirLives(member, page = 0)
@@ -34,7 +38,14 @@ class HomeOnAirLiveFacadeTest {
assertEquals(20, response.size)
assertEquals(true, response.hasNext)
assertEquals(20, response.items.size)
Mockito.verify(queryService).findLiveRecommendations(eqValue(0L), eqValue(21), eqValue(member.id), eqValue(true))
Mockito.verify(queryService).findLiveRecommendations(
eqValue(0L),
eqValue(21),
eqValue(member.id),
eqValue(true),
eqValue(false),
eqValue(Gender.NONE)
)
}
@Test
@@ -46,7 +57,9 @@ class HomeOnAirLiveFacadeTest {
eqValue(0L),
eqValue(21),
eqValue(member.id),
eqValue(false)
eqValue(false),
eqValue(false),
eqValue(Gender.NONE)
)
val response = facade.getOnAirLives(member, page = 0)
@@ -60,13 +73,46 @@ class HomeOnAirLiveFacadeTest {
val member = createMember(100L)
Mockito.doReturn(false).`when`(preferenceService).canViewAdultContent(member)
Mockito.doReturn(listOf(record(1L, beginDateTime = LocalDateTime.of(2026, 6, 26, 12, 30)))).`when`(queryService)
.findLiveRecommendations(eqValue(0L), eqValue(21), eqValue(member.id), eqValue(false))
.findLiveRecommendations(
eqValue(0L),
eqValue(21),
eqValue(member.id),
eqValue(false),
eqValue(false),
eqValue(Gender.NONE)
)
val response = facade.getOnAirLives(member, page = 0)
assertEquals("2026-06-26T12:30:00Z", response.items.single().beginDateTimeUtc)
}
@Test
@DisplayName("현재 진행 중인 라이브 facade는 크리에이터 여부와 인증 성별 우선 유효 성별을 전달한다")
fun shouldPassCreatorRoleAndAuthGenderToLiveRecommendationQuery() {
val member = createMember(id = 100L, role = MemberRole.CREATOR, gender = Gender.FEMALE, authGender = 1)
Mockito.doReturn(true).`when`(preferenceService).canViewAdultContent(member)
Mockito.doReturn(emptyList<HomeLiveRecommendationRecord>()).`when`(queryService).findLiveRecommendations(
eqValue(0L),
eqValue(21),
eqValue(member.id),
eqValue(true),
eqValue(true),
eqValue(Gender.MALE)
)
facade.getOnAirLives(member, page = 0)
Mockito.verify(queryService).findLiveRecommendations(
eqValue(0L),
eqValue(21),
eqValue(member.id),
eqValue(true),
eqValue(true),
eqValue(Gender.MALE)
)
}
private fun record(
id: Long,
creatorProfileImage: String? = "profile.png",
@@ -80,13 +126,24 @@ class HomeOnAirLiveFacadeTest {
beginDateTime = beginDateTime
)
private fun createMember(id: Long): Member {
private fun createMember(
id: Long,
role: MemberRole = MemberRole.USER,
gender: Gender = Gender.NONE,
authGender: Int? = null
): Member {
return Member(
email = "viewer$id@test.com",
password = "password",
nickname = "viewer$id",
role = MemberRole.USER
).apply { this.id = id }
role = role,
gender = gender
).apply {
this.id = id
authGender?.let {
Auth(name = "name", birth = "19900101", uniqueCi = "ci$id", di = "di$id", gender = it).member = this
}
}
}
private fun <T> eqValue(value: T): T {
@@ -32,7 +32,9 @@ import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.CreatorCommuni
import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.comment.CreatorCommunityComment
import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.like.CreatorCommunityLike
import kr.co.vividnext.sodalive.i18n.Lang
import kr.co.vividnext.sodalive.live.room.GenderRestriction
import kr.co.vividnext.sodalive.live.room.LiveRoom
import kr.co.vividnext.sodalive.member.Gender
import kr.co.vividnext.sodalive.member.Member
import kr.co.vividnext.sodalive.member.MemberKind
import kr.co.vividnext.sodalive.member.MemberRole
@@ -194,6 +196,194 @@ class DefaultHomeRecommendationQueryRepositoryTest @Autowired constructor(
assertEquals(listOf(visibleLive.id), lives.map { it.liveRoomId })
}
@Test
@DisplayName("라이브 추천은 다른 크리에이터에게 크리에이터 입장 제한 라이브를 제외하고 방 생성자는 유지한다")
fun shouldExcludeCreatorJoinRestrictedLiveForOtherCreatorButKeepRoomOwner() {
val baseAt = LocalDateTime.of(2026, 9, 17, 10, 0)
val viewer = saveMember("creator-viewer", MemberRole.CREATOR)
val otherCreator = saveMember("restricted-live-creator", MemberRole.CREATOR)
val availableLive = saveLiveRoom(otherCreator, baseAt, channelName = "available-live")
saveLiveRoom(
otherCreator,
baseAt.plusMinutes(1),
channelName = "creator-restricted-live",
isAvailableJoinCreator = false
)
val ownerLive = saveLiveRoom(
viewer,
baseAt.plusMinutes(2),
channelName = "owner-restricted-live",
isAvailableJoinCreator = false
)
flushAndClear()
val lives = repository.findLiveRecommendations(
limit = 10,
memberId = viewer.id,
isViewerCreator = true,
effectiveViewerGender = Gender.NONE
)
assertEquals(listOf(ownerLive.id, availableLive.id), lives.map { it.liveRoomId })
}
@Test
@DisplayName("라이브 추천은 일반 사용자에게 크리에이터 입장 제한 라이브를 노출한다")
fun shouldKeepCreatorJoinRestrictedLiveVisibleForUserViewer() {
val viewer = saveMember("user-viewer", MemberRole.USER)
val creator = saveMember("creator-restricted-live-creator", MemberRole.CREATOR)
val restrictedLive = saveLiveRoom(
creator,
LocalDateTime.of(2026, 9, 17, 10, 0),
channelName = "creator-restricted-live",
isAvailableJoinCreator = false
)
flushAndClear()
val lives = repository.findLiveRecommendations(
limit = 10,
memberId = viewer.id,
isViewerCreator = false,
effectiveViewerGender = Gender.NONE
)
assertEquals(listOf(restrictedLive.id), lives.map { it.liveRoomId })
}
@Test
@DisplayName("라이브 추천은 남성에게 남성 전용과 전체 라이브를 노출하고 방 생성자는 성별 제한에서 예외 처리한다")
fun shouldFilterFemaleOnlyLiveForMaleViewerButKeepOwnerLive() {
val baseAt = LocalDateTime.of(2026, 9, 17, 10, 0)
val viewer = saveMember("male-viewer", MemberRole.CREATOR)
val creator = saveMember("gender-live-creator", MemberRole.CREATOR)
val maleOnlyLive = saveLiveRoom(
creator,
baseAt,
channelName = "male-only-live",
genderRestriction = GenderRestriction.MALE_ONLY
)
val allLive = saveLiveRoom(creator, baseAt.plusMinutes(1), channelName = "all-live")
saveLiveRoom(
creator,
baseAt.plusMinutes(2),
channelName = "female-only-live",
genderRestriction = GenderRestriction.FEMALE_ONLY
)
val ownerLive = saveLiveRoom(
viewer,
baseAt.plusMinutes(3),
channelName = "owner-female-only-live",
genderRestriction = GenderRestriction.FEMALE_ONLY
)
flushAndClear()
val lives = repository.findLiveRecommendations(
limit = 10,
memberId = viewer.id,
isViewerCreator = true,
effectiveViewerGender = Gender.MALE
)
assertEquals(listOf(ownerLive.id, allLive.id, maleOnlyLive.id), lives.map { it.liveRoomId })
}
@Test
@DisplayName("라이브 추천은 여성 방 생성자에게 남성 전용인 자신의 라이브를 노출한다")
fun shouldKeepMaleOnlyLiveVisibleForFemaleRoomOwner() {
val baseAt = LocalDateTime.of(2026, 9, 17, 10, 0)
val viewer = saveMember("female-owner", MemberRole.CREATOR)
val creator = saveMember("female-owner-filter-creator", MemberRole.CREATOR)
val femaleOnlyLive = saveLiveRoom(
creator,
baseAt.plusMinutes(1),
channelName = "female-only-live",
genderRestriction = GenderRestriction.FEMALE_ONLY
)
val ownerLive = saveLiveRoom(
viewer,
baseAt.plusMinutes(2),
channelName = "owner-male-only-live",
genderRestriction = GenderRestriction.MALE_ONLY
)
flushAndClear()
val lives = repository.findLiveRecommendations(
limit = 10,
memberId = viewer.id,
isViewerCreator = true,
effectiveViewerGender = Gender.FEMALE
)
assertEquals(listOf(ownerLive.id, femaleOnlyLive.id), lives.map { it.liveRoomId })
}
@Test
@DisplayName("라이브 추천은 여성에게 여성 전용과 전체 라이브만 노출한다")
fun shouldFilterMaleOnlyLiveForFemaleViewer() {
val baseAt = LocalDateTime.of(2026, 9, 17, 10, 0)
val viewer = saveMember("female-viewer", MemberRole.USER)
val creator = saveMember("female-filter-creator", MemberRole.CREATOR)
val femaleOnlyLive = saveLiveRoom(
creator,
baseAt.plusMinutes(2),
channelName = "female-only-live",
genderRestriction = GenderRestriction.FEMALE_ONLY
)
val allLive = saveLiveRoom(creator, baseAt.plusMinutes(1), channelName = "all-live")
saveLiveRoom(
creator,
baseAt,
channelName = "male-only-live",
genderRestriction = GenderRestriction.MALE_ONLY
)
flushAndClear()
val lives = repository.findLiveRecommendations(
limit = 10,
memberId = viewer.id,
effectiveViewerGender = Gender.FEMALE
)
assertEquals(listOf(femaleOnlyLive.id, allLive.id), lives.map { it.liveRoomId })
}
@Test
@DisplayName("라이브 추천은 성별 미설정 또는 유효 성별이 없으면 성별 제한을 적용하지 않는다")
fun shouldNotApplyGenderRestrictionForNoneOrNullGender() {
val baseAt = LocalDateTime.of(2026, 9, 17, 10, 0)
val viewer = saveMember("unrestricted-viewer", MemberRole.USER)
val creator = saveMember("unrestricted-creator", MemberRole.CREATOR)
val maleOnlyLive = saveLiveRoom(
creator,
baseAt,
channelName = "male-only-live",
genderRestriction = GenderRestriction.MALE_ONLY
)
val femaleOnlyLive = saveLiveRoom(
creator,
baseAt.plusMinutes(1),
channelName = "female-only-live",
genderRestriction = GenderRestriction.FEMALE_ONLY
)
val allLive = saveLiveRoom(creator, baseAt.plusMinutes(2), channelName = "all-live")
flushAndClear()
val noneGenderLives = repository.findLiveRecommendations(
limit = 10,
memberId = viewer.id,
effectiveViewerGender = Gender.NONE
)
val nullGenderLives = repository.findLiveRecommendations(
limit = 10,
memberId = viewer.id,
effectiveViewerGender = null
)
val expectedIds = listOf(allLive.id, femaleOnlyLive.id, maleOnlyLive.id)
assertEquals(expectedIds, noneGenderLives.map { it.liveRoomId })
assertEquals(expectedIds, nullGenderLives.map { it.liveRoomId })
}
@Test
@DisplayName("홈 배너는 활성 배너를 orders 오름차순 최대 20개로 조회하고 동일 orders는 랜덤 tie-breaker를 적용한다")
fun shouldFindActiveHomeBannersWithOrderLimitAndRandomTieBreaker() {
@@ -2856,7 +3046,9 @@ class DefaultHomeRecommendationQueryRepositoryTest @Autowired constructor(
isAdult: Boolean = false,
title: String = "live-${creator.nickname}-$beginDateTime",
price: Int = 0,
isActive: Boolean = true
isActive: Boolean = true,
isAvailableJoinCreator: Boolean = true,
genderRestriction: GenderRestriction = GenderRestriction.ALL
): LiveRoom {
val room = LiveRoom(
title = title,
@@ -2864,7 +3056,9 @@ class DefaultHomeRecommendationQueryRepositoryTest @Autowired constructor(
beginDateTime = beginDateTime,
numberOfPeople = 0,
isAdult = isAdult,
price = price
price = price,
isAvailableJoinCreator = isAvailableJoinCreator,
genderRestriction = genderRestriction
)
room.member = creator
room.channelName = channelName
@@ -2,6 +2,7 @@ package kr.co.vividnext.sodalive.v2.recommendation.application
import kr.co.vividnext.sodalive.i18n.Lang
import kr.co.vividnext.sodalive.i18n.LangContext
import kr.co.vividnext.sodalive.member.Gender
import kr.co.vividnext.sodalive.v2.common.domain.CreatorActivityType
import kr.co.vividnext.sodalive.v2.creator.channel.community.translation.application.CreatorCommunityDisplayContent
import kr.co.vividnext.sodalive.v2.creator.channel.community.translation.application.CreatorCommunityTranslationService
@@ -86,19 +87,23 @@ class HomeRecommendationQueryServiceTest {
}
@Test
@DisplayName("라이브 추천 조회는 paging과 성인 노출 여부를 조회 포트에 그대로 위임한다")
fun shouldDelegateLiveRecommendationQueryWithPagingAndAdultFlag() {
@DisplayName("라이브 추천 조회는 paging, 성인 노출, 크리에이터 여부와 유효 성별을 조회 포트에 그대로 위임한다")
fun shouldDelegateLiveRecommendationQueryWithViewerContext() {
val recommendations = service.findLiveRecommendations(
offset = 40,
limit = 21,
memberId = 100L,
includeAdultLives = true
includeAdultLives = true,
isViewerCreator = true,
effectiveViewerGender = Gender.MALE
)
assertEquals(40, port.liveOffset)
assertEquals(21, port.liveLimit)
assertEquals(100L, port.liveMemberId)
assertEquals(true, port.liveIncludeAdultLives)
assertEquals(true, port.liveIsViewerCreator)
assertEquals(Gender.MALE, port.liveEffectiveViewerGender)
assertEquals(port.liveRecommendations, recommendations)
}
@@ -1092,6 +1097,8 @@ class HomeRecommendationQueryServiceTest {
var liveOffset: Long? = null
var liveMemberId: Long? = null
var liveIncludeAdultLives: Boolean? = null
var liveIsViewerCreator: Boolean? = null
var liveEffectiveViewerGender: Gender? = null
var bannerLimit: Int? = null
var bannerMemberId: Long? = null
var bannerIncludeAdultBanners: Boolean? = null
@@ -1187,12 +1194,16 @@ class HomeRecommendationQueryServiceTest {
offset: Long,
limit: Int,
memberId: Long?,
includeAdultLives: Boolean
includeAdultLives: Boolean,
isViewerCreator: Boolean,
effectiveViewerGender: Gender?
): List<HomeLiveRecommendationRecord> {
liveOffset = offset
liveLimit = limit
liveMemberId = memberId
liveIncludeAdultLives = includeAdultLives
liveIsViewerCreator = isViewerCreator
liveEffectiveViewerGender = effectiveViewerGender
return liveRecommendations
}