fix(recommend): 장르 추천에서 요청자를 제외한다
This commit is contained in:
@@ -933,6 +933,7 @@ class DefaultHomeRecommendationQueryRepository(
|
|||||||
and (:includeAdultGenres = true or c.is_adult = false)
|
and (:includeAdultGenres = true or c.is_adult = false)
|
||||||
and m.is_active = true
|
and m.is_active = true
|
||||||
and m.role = 'CREATOR'
|
and m.role = 'CREATOR'
|
||||||
|
and (:memberId is null or m.id <> :memberId)
|
||||||
and not exists (
|
and not exists (
|
||||||
select 1
|
select 1
|
||||||
from creator_following cf
|
from creator_following cf
|
||||||
@@ -1009,6 +1010,7 @@ class DefaultHomeRecommendationQueryRepository(
|
|||||||
and (:includeAdultGenres = true or c.is_adult = false)
|
and (:includeAdultGenres = true or c.is_adult = false)
|
||||||
and m.is_active = true
|
and m.is_active = true
|
||||||
and m.role = 'CREATOR'
|
and m.role = 'CREATOR'
|
||||||
|
and (:memberId is null or m.id <> :memberId)
|
||||||
and not exists (
|
and not exists (
|
||||||
select 1
|
select 1
|
||||||
from creator_following cf
|
from creator_following cf
|
||||||
@@ -1052,6 +1054,7 @@ class DefaultHomeRecommendationQueryRepository(
|
|||||||
and (:includeAdultGenres = true or c.is_adult = false)
|
and (:includeAdultGenres = true or c.is_adult = false)
|
||||||
and m.is_active = true
|
and m.is_active = true
|
||||||
and m.role = 'CREATOR'
|
and m.role = 'CREATOR'
|
||||||
|
and (:memberId is null or m.id <> :memberId)
|
||||||
and not exists (
|
and not exists (
|
||||||
select 1
|
select 1
|
||||||
from creator_following cf
|
from creator_following cf
|
||||||
|
|||||||
@@ -1427,6 +1427,108 @@ class DefaultHomeRecommendationQueryRepositoryTest @Autowired constructor(
|
|||||||
assertEquals(listOf(visibleCreator.id), recommendations.single().creators.map { it.creatorId })
|
assertEquals(listOf(visibleCreator.id), recommendations.single().creators.map { it.creatorId })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("장르 기반 크리에이터 추천은 요청자 본인만 있는 장르를 후보에서 제외한다")
|
||||||
|
fun shouldExcludeRequesterOnlyGenreFromGenreCreatorRecommendations() {
|
||||||
|
val viewer = saveMember("self-only-viewer", MemberRole.CREATOR)
|
||||||
|
val fallbackCreator = saveMember("self-only-fallback", MemberRole.CREATOR)
|
||||||
|
val selfTheme = saveTheme("self-only-theme")
|
||||||
|
val fallbackTheme = saveTheme("self-only-fallback-theme")
|
||||||
|
val selfContent = saveAudioContent(
|
||||||
|
viewer,
|
||||||
|
LocalDateTime.of(2026, 5, 30, 10, 0),
|
||||||
|
isActive = true,
|
||||||
|
theme = selfTheme
|
||||||
|
)
|
||||||
|
saveAudioContent(
|
||||||
|
fallbackCreator,
|
||||||
|
LocalDateTime.of(2026, 5, 30, 11, 0),
|
||||||
|
isActive = true,
|
||||||
|
theme = fallbackTheme
|
||||||
|
)
|
||||||
|
entityManager.persist(
|
||||||
|
CreatorContentViewHistory(
|
||||||
|
memberId = viewer.id!!,
|
||||||
|
contentId = selfContent.id!!,
|
||||||
|
genreId = 999L,
|
||||||
|
viewedAt = LocalDateTime.of(2026, 5, 31, 10, 0)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
flushAndClear()
|
||||||
|
|
||||||
|
val recommendations = repository.findGenreCreatorRecommendations(
|
||||||
|
memberId = viewer.id!!,
|
||||||
|
includeAdultGenres = false,
|
||||||
|
genreLimit = 1,
|
||||||
|
creatorLimit = 8
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals(listOf(fallbackTheme.id), recommendations.map { it.genreId })
|
||||||
|
assertEquals(listOf(fallbackCreator.id), recommendations.single().creators.map { it.creatorId })
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("장르 기반 크리에이터 추천은 요청자 본인을 제외한 뒤 대체 크리에이터로 8명을 채운다")
|
||||||
|
fun shouldBackfillCreatorAfterExcludingRequesterFromGenreCreatorRecommendations() {
|
||||||
|
val viewer = saveMember("self-backfill-viewer", MemberRole.CREATOR)
|
||||||
|
val theme = saveTheme("self-backfill-theme")
|
||||||
|
saveAudioContent(viewer, LocalDateTime.of(2026, 5, 30, 10, 0), isActive = true, theme = theme)
|
||||||
|
val expectedCreators = (0..8).map { index ->
|
||||||
|
saveMember("self-backfill-creator-$index", MemberRole.CREATOR).also { creator ->
|
||||||
|
saveAudioContent(
|
||||||
|
creator,
|
||||||
|
LocalDateTime.of(2026, 5, 30, 11, 0).plusMinutes(index.toLong()),
|
||||||
|
isActive = true,
|
||||||
|
theme = theme
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
flushAndClear()
|
||||||
|
|
||||||
|
val recommendations = repository.findGenreCreatorRecommendations(
|
||||||
|
memberId = viewer.id!!,
|
||||||
|
includeAdultGenres = false,
|
||||||
|
genreLimit = 1,
|
||||||
|
creatorLimit = 8
|
||||||
|
)
|
||||||
|
val creatorIds = recommendations.single().creators.map { it.creatorId }
|
||||||
|
|
||||||
|
assertEquals(8, creatorIds.size)
|
||||||
|
assertEquals(false, creatorIds.contains(viewer.id))
|
||||||
|
assertEquals(true, creatorIds.all { it in expectedCreators.map { creator -> creator.id } })
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("장르 기반 크리에이터 추천은 요청자 본인 제외 후 대체가 없으면 가능한 크리에이터만 응답한다")
|
||||||
|
fun shouldReturnAvailableCreatorsAfterExcludingRequesterFromGenreCreatorRecommendations() {
|
||||||
|
val viewer = saveMember("self-partial-viewer", MemberRole.CREATOR)
|
||||||
|
val theme = saveTheme("self-partial-theme")
|
||||||
|
saveAudioContent(viewer, LocalDateTime.of(2026, 5, 30, 10, 0), isActive = true, theme = theme)
|
||||||
|
val expectedCreators = (0 until 7).map { index ->
|
||||||
|
saveMember("self-partial-creator-$index", MemberRole.CREATOR).also { creator ->
|
||||||
|
saveAudioContent(
|
||||||
|
creator,
|
||||||
|
LocalDateTime.of(2026, 5, 30, 11, 0).plusMinutes(index.toLong()),
|
||||||
|
isActive = true,
|
||||||
|
theme = theme
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
flushAndClear()
|
||||||
|
|
||||||
|
val recommendations = repository.findGenreCreatorRecommendations(
|
||||||
|
memberId = viewer.id!!,
|
||||||
|
includeAdultGenres = false,
|
||||||
|
genreLimit = 1,
|
||||||
|
creatorLimit = 8
|
||||||
|
)
|
||||||
|
val creatorIds = recommendations.single().creators.map { it.creatorId }
|
||||||
|
|
||||||
|
assertEquals(7, creatorIds.size)
|
||||||
|
assertEquals(false, creatorIds.contains(viewer.id))
|
||||||
|
assertEquals(expectedCreators.map { it.id }.toSet(), creatorIds.toSet())
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@DisplayName("장르 기반 크리에이터 추천은 series_genre가 아니라 content_theme 기준으로 그룹을 만든다")
|
@DisplayName("장르 기반 크리에이터 추천은 series_genre가 아니라 content_theme 기준으로 그룹을 만든다")
|
||||||
fun shouldGroupGenreCreatorRecommendationsByContentThemeNotSeriesGenre() {
|
fun shouldGroupGenreCreatorRecommendationsByContentThemeNotSeriesGenre() {
|
||||||
|
|||||||
@@ -526,6 +526,35 @@ class HomeRecommendationQueryServiceTest {
|
|||||||
assertEquals((101L..108L).toList(), recommendations[1].creators.map { it.creatorId })
|
assertEquals((101L..108L).toList(), recommendations[1].creators.map { it.creatorId })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("장르 기반 크리에이터 추천은 장르의 추천 가능 크리에이터가 8명 미만이면 가능한 만큼 응답한다")
|
||||||
|
fun shouldReturnAvailableCreatorsWhenGenreCreatorCountIsUnderLimit() {
|
||||||
|
val availableCreators = (1L..7L).map { creatorId ->
|
||||||
|
HomeGenreCreatorRecommendationRecord(
|
||||||
|
creatorId = creatorId,
|
||||||
|
creatorNickname = "available-$creatorId",
|
||||||
|
creatorProfileImage = null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
port.genreCreatorRecommendations = listOf(
|
||||||
|
HomeGenreCreatorRecommendationGroup(
|
||||||
|
genreId = 1L,
|
||||||
|
genreName = "partial-theme",
|
||||||
|
creators = availableCreators
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
val recommendations = service.findGenreCreatorRecommendations(
|
||||||
|
memberId = 100L,
|
||||||
|
includeAdultGenres = false,
|
||||||
|
genreLimit = 5,
|
||||||
|
creatorLimit = 8
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals(listOf(1L), recommendations.map { it.genreId })
|
||||||
|
assertEquals((1L..7L).toList(), recommendations.single().creators.map { it.creatorId })
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@DisplayName("장르 기반 크리에이터 추천은 조회 이력 장르는 중복 제거 후 8명 미만이어도 유지한다")
|
@DisplayName("장르 기반 크리에이터 추천은 조회 이력 장르는 중복 제거 후 8명 미만이어도 유지한다")
|
||||||
fun shouldKeepViewedThemeWhenCreatorDeduplicationMakesThemeUnderfilled() {
|
fun shouldKeepViewedThemeWhenCreatorDeduplicationMakesThemeUnderfilled() {
|
||||||
|
|||||||
Reference in New Issue
Block a user