feat(recommendation): New & Hot 전체보기 조회를 추가한다

This commit is contained in:
2026-06-27 05:49:27 +09:00
parent 6ab8d65207
commit 581c5fd441
2 changed files with 105 additions and 15 deletions
@@ -1,6 +1,9 @@
package kr.co.vividnext.sodalive.v2.content.recommendation.application
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.content.recommendation.domain.AudioCard
import kr.co.vividnext.sodalive.v2.content.recommendation.domain.AudioRecommendationVisibility
import kr.co.vividnext.sodalive.v2.content.recommendation.port.out.AudioRecommendationQueryPort
import kr.co.vividnext.sodalive.v2.recommendation.domain.RecommendedSectionType
@@ -51,7 +54,7 @@ class AudioRecommendationQueryServiceTest {
.findLatestSnapshots(
RecommendedSectionType.NEW_AND_HOT_AUDIO_SAFE,
0,
AudioRecommendationQueryService.NEW_AND_HOT_AUDIO_LIMIT
AudioRecommendationQueryService.NEW_AND_HOT_HOME_LIMIT
)
Mockito.doReturn(emptyList<RecommendationSnapshotRecord>())
.`when`(snapshotPort)
@@ -100,7 +103,7 @@ class AudioRecommendationQueryServiceTest {
.findLatestSnapshots(
RecommendedSectionType.NEW_AND_HOT_AUDIO_SAFE,
0,
AudioRecommendationQueryService.NEW_AND_HOT_AUDIO_LIMIT
AudioRecommendationQueryService.NEW_AND_HOT_HOME_LIMIT
)
Mockito.doReturn(emptyList<RecommendationSnapshotRecord>())
.`when`(snapshotPort)
@@ -127,19 +130,14 @@ class AudioRecommendationQueryServiceTest {
@Test
@DisplayName("인증 회원 성인 정책은 조회용 저장 preference를 사용한다")
fun shouldUseStoredPreferenceForMemberAdultVisibility() {
val member = kr.co.vividnext.sodalive.member.Member(
email = "adult@test.com",
password = "password",
nickname = "adult",
role = kr.co.vividnext.sodalive.member.MemberRole.USER
)
val member = member(id = 10L)
Mockito.doReturn(true).`when`(preferenceService).canViewAdultContent(member)
Mockito.doReturn(listOf(snapshot(RecommendedSectionType.NEW_AND_HOT_AUDIO_ALL, 10L)))
.`when`(snapshotPort)
.findLatestSnapshots(
RecommendedSectionType.NEW_AND_HOT_AUDIO_ALL,
0,
AudioRecommendationQueryService.NEW_AND_HOT_AUDIO_LIMIT
AudioRecommendationQueryService.NEW_AND_HOT_HOME_LIMIT
)
service.getRecommendations(member)
@@ -149,10 +147,52 @@ class AudioRecommendationQueryServiceTest {
Mockito.verify(snapshotPort).findLatestSnapshots(
RecommendedSectionType.NEW_AND_HOT_AUDIO_ALL,
0,
AudioRecommendationQueryService.NEW_AND_HOT_AUDIO_LIMIT
AudioRecommendationQueryService.NEW_AND_HOT_HOME_LIMIT
)
}
@Test
@DisplayName("추천 탭 첫 화면은 New & Hot 스냅샷을 12개만 조회한다")
fun shouldKeepNewAndHotHomeLimitAtTwelve() {
val member = member(id = 10L)
Mockito.doReturn(true).`when`(preferenceService).canViewAdultContent(member)
Mockito.doReturn(listOf(snapshot(RecommendedSectionType.NEW_AND_HOT_AUDIO_ALL, 10L))).`when`(snapshotPort)
.findLatestSnapshots(
RecommendedSectionType.NEW_AND_HOT_AUDIO_ALL,
0,
AudioRecommendationQueryService.NEW_AND_HOT_HOME_LIMIT
)
service.getRecommendations(member)
Mockito.verify(snapshotPort).findLatestSnapshots(
RecommendedSectionType.NEW_AND_HOT_AUDIO_ALL,
0,
AudioRecommendationQueryService.NEW_AND_HOT_HOME_LIMIT
)
}
@Test
@DisplayName("New & Hot 전체보기는 스냅샷 offset과 limit으로 오디오 카드를 조회한다")
fun shouldFindNewAndHotAudiosWithOffsetAndLimit() {
val member = member(id = 10L)
val snapshots = listOf(
snapshot(RecommendedSectionType.NEW_AND_HOT_AUDIO_ALL, 3L),
snapshot(RecommendedSectionType.NEW_AND_HOT_AUDIO_ALL, 4L),
snapshot(RecommendedSectionType.NEW_AND_HOT_AUDIO_ALL, 5L)
)
Mockito.doReturn(true).`when`(preferenceService).canViewAdultContent(member)
Mockito.doReturn(snapshots).`when`(snapshotPort)
.findLatestSnapshots(RecommendedSectionType.NEW_AND_HOT_AUDIO_ALL, 20L, 21)
Mockito.doReturn(listOf(audioCard(3L), audioCard(4L), audioCard(5L))).`when`(queryPort)
.findAudioCardsByIds(eqValue(listOf(3L, 4L, 5L)), eqValue(member.id), eqValue(true), anyLocalDateTime())
val result = service.findNewAndHotAudios(member, offset = 20L, limit = 21)
assertEquals(listOf(3L, 4L, 5L), result.map { it.audioContentId })
Mockito.verify(snapshotPort).findLatestSnapshots(RecommendedSectionType.NEW_AND_HOT_AUDIO_ALL, 20L, 21)
}
@Test
@DisplayName("visibility와 섹션 조합은 신규 RecommendedSectionType으로 매핑된다")
fun shouldMapVisibilityToAudioSectionTypes() {
@@ -186,6 +226,32 @@ class AudioRecommendationQueryServiceTest {
return Mockito.any(LocalDateTime::class.java) ?: LocalDateTime.now()
}
private fun member(id: Long): Member {
return Member(
email = "viewer$id@test.com",
password = "password",
nickname = "viewer$id",
role = MemberRole.USER
).apply {
this.id = id
}
}
private fun audioCard(id: Long): AudioCard {
return AudioCard(
audioContentId = id,
title = "audio$id",
duration = "00:01",
imageUrl = "https://cdn.test/audio$id.png",
price = id.toInt(),
isAdult = false,
isPointAvailable = true,
isFirstContent = true,
isOriginalSeries = false,
creatorNickname = "creator$id"
)
}
private fun snapshot(sectionType: RecommendedSectionType, targetId: Long): RecommendationSnapshotRecord {
return RecommendationSnapshotRecord(
sectionType = sectionType,