test #426
@@ -0,0 +1,72 @@
|
||||
package kr.co.vividnext.sodalive.v2.api.content.overview.application
|
||||
|
||||
import kr.co.vividnext.sodalive.member.Member
|
||||
import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService
|
||||
import kr.co.vividnext.sodalive.v2.api.content.overview.dto.ContentOverviewItemResponse
|
||||
import kr.co.vividnext.sodalive.v2.api.content.overview.dto.ContentOverviewPageResponse
|
||||
import kr.co.vividnext.sodalive.v2.api.content.overview.dto.ContentOverviewType
|
||||
import kr.co.vividnext.sodalive.v2.common.domain.toCdnUrl
|
||||
import kr.co.vividnext.sodalive.v2.content.recommendation.application.AudioRecommendationQueryService
|
||||
import kr.co.vividnext.sodalive.v2.recommendation.application.HomeRecommendationQueryService
|
||||
import org.springframework.beans.factory.annotation.Value
|
||||
import org.springframework.stereotype.Component
|
||||
import java.time.LocalDateTime
|
||||
|
||||
@Component
|
||||
class ContentOverviewFacade(
|
||||
private val audioRecommendationQueryService: AudioRecommendationQueryService,
|
||||
private val homeRecommendationQueryService: HomeRecommendationQueryService,
|
||||
private val memberContentPreferenceService: MemberContentPreferenceService,
|
||||
@Value("\${cloud.aws.cloud-front.host}")
|
||||
private val cloudFrontHost: String,
|
||||
private val queryPolicy: ContentOverviewQueryPolicy = ContentOverviewQueryPolicy()
|
||||
) {
|
||||
fun getContents(type: String?, page: Int?, size: Int?, member: Member): ContentOverviewPageResponse {
|
||||
val resolvedType = queryPolicy.resolveType(type)
|
||||
val resolvedPage = queryPolicy.createPage(page, size)
|
||||
|
||||
return when (resolvedType) {
|
||||
ContentOverviewType.NEW_AND_HOT_AUDIO -> getNewAndHotContents(member, resolvedPage)
|
||||
ContentOverviewType.FIRST_AUDIO_CONTENT -> getFirstAudioContents(member, resolvedPage)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getNewAndHotContents(member: Member, page: ContentOverviewPage): ContentOverviewPageResponse {
|
||||
val fetched = audioRecommendationQueryService.findNewAndHotAudios(
|
||||
member = member,
|
||||
offset = page.offset,
|
||||
limit = page.size + 1
|
||||
)
|
||||
return ContentOverviewPageResponse(
|
||||
type = ContentOverviewType.NEW_AND_HOT_AUDIO,
|
||||
items = queryPolicy.pageItems(fetched, page).map { ContentOverviewItemResponse.fromNewAndHot(it) },
|
||||
page = page.page,
|
||||
size = page.size,
|
||||
hasNext = queryPolicy.hasNext(fetched, page)
|
||||
)
|
||||
}
|
||||
|
||||
private fun getFirstAudioContents(member: Member, page: ContentOverviewPage): ContentOverviewPageResponse {
|
||||
val fetched = homeRecommendationQueryService.findFirstAudioContents(
|
||||
now = LocalDateTime.now(),
|
||||
offset = page.offset,
|
||||
limit = page.size + 1,
|
||||
memberId = member.id,
|
||||
includeAdultContents = memberContentPreferenceService.canViewAdultContent(member)
|
||||
)
|
||||
return ContentOverviewPageResponse(
|
||||
type = ContentOverviewType.FIRST_AUDIO_CONTENT,
|
||||
items = queryPolicy.pageItems(fetched, page).map {
|
||||
ContentOverviewItemResponse.fromFirstAudioContent(
|
||||
audio = it,
|
||||
coverImage = it.coverImage.toCdnUrl(cloudFrontHost),
|
||||
isAdult = it.isAdult,
|
||||
isOriginalSeries = it.isOriginalSeries
|
||||
)
|
||||
},
|
||||
page = page.page,
|
||||
size = page.size,
|
||||
hasNext = queryPolicy.hasNext(fetched, page)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
package kr.co.vividnext.sodalive.v2.api.content.overview.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.api.content.overview.dto.ContentOverviewType
|
||||
import kr.co.vividnext.sodalive.v2.content.recommendation.application.AudioRecommendationQueryService
|
||||
import kr.co.vividnext.sodalive.v2.content.recommendation.domain.AudioCard
|
||||
import kr.co.vividnext.sodalive.v2.recommendation.application.HomeRecommendationQueryService
|
||||
import kr.co.vividnext.sodalive.v2.recommendation.port.out.HomeFirstAudioContentRecord
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.DisplayName
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.mockito.Mockito
|
||||
import java.time.LocalDateTime
|
||||
|
||||
class ContentOverviewFacadeTest {
|
||||
private val audioRecommendationQueryService = Mockito.mock(AudioRecommendationQueryService::class.java)
|
||||
private val homeRecommendationQueryService = Mockito.mock(HomeRecommendationQueryService::class.java)
|
||||
private val memberContentPreferenceService = Mockito.mock(MemberContentPreferenceService::class.java)
|
||||
private val facade = ContentOverviewFacade(
|
||||
audioRecommendationQueryService = audioRecommendationQueryService,
|
||||
homeRecommendationQueryService = homeRecommendationQueryService,
|
||||
memberContentPreferenceService = memberContentPreferenceService,
|
||||
cloudFrontHost = "https://cdn.test",
|
||||
queryPolicy = ContentOverviewQueryPolicy()
|
||||
)
|
||||
|
||||
@Test
|
||||
@DisplayName("New & Hot 전체보기는 size + 1 조회 결과를 공통 페이지 응답으로 변환한다")
|
||||
fun shouldReturnNewAndHotPage() {
|
||||
val member = member(id = 10L)
|
||||
Mockito.doReturn((1L..21L).map { audioCard(it) }).`when`(audioRecommendationQueryService)
|
||||
.findNewAndHotAudios(member, offset = 0L, limit = 21)
|
||||
|
||||
val response = facade.getContents("NEW_AND_HOT_AUDIO", page = 0, size = 20, member = member)
|
||||
|
||||
assertEquals(ContentOverviewType.NEW_AND_HOT_AUDIO, response.type)
|
||||
assertEquals((1L..20L).toList(), response.items.map { it.contentId })
|
||||
assertEquals("https://cdn.test/audio1.png", response.items[0].coverImage)
|
||||
assertEquals(0, response.page)
|
||||
assertEquals(20, response.size)
|
||||
assertEquals(true, response.hasNext)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("첫 번째 오디오 콘텐츠 전체보기는 adult visibility와 offset을 반영해 조회한다")
|
||||
fun shouldReturnFirstAudioContentPage() {
|
||||
val member = member(id = 10L)
|
||||
Mockito.doReturn(true).`when`(memberContentPreferenceService).canViewAdultContent(member)
|
||||
Mockito.doReturn(listOf(firstAudio(1L), firstAudio(2L))).`when`(homeRecommendationQueryService)
|
||||
.findFirstAudioContents(
|
||||
anyLocalDateTime(),
|
||||
eqValue(20L),
|
||||
eqValue(21),
|
||||
eqValue(member.id),
|
||||
eqValue(true)
|
||||
)
|
||||
|
||||
val response = facade.getContents("FIRST_AUDIO_CONTENT", page = 1, size = 20, member = member)
|
||||
|
||||
assertEquals(ContentOverviewType.FIRST_AUDIO_CONTENT, response.type)
|
||||
assertEquals(listOf(1L, 2L), response.items.map { it.contentId })
|
||||
assertEquals("https://cdn.test/cover/audio1.png", response.items[0].coverImage)
|
||||
assertEquals(true, response.items[0].isFirstContent)
|
||||
assertEquals(false, response.hasNext)
|
||||
}
|
||||
|
||||
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 anyLocalDateTime(): LocalDateTime {
|
||||
return Mockito.any(LocalDateTime::class.java) ?: LocalDateTime.of(2026, 6, 27, 0, 0)
|
||||
}
|
||||
|
||||
private fun <T> eqValue(value: T): T {
|
||||
return Mockito.eq(value) ?: value
|
||||
}
|
||||
|
||||
private fun firstAudio(id: Long): HomeFirstAudioContentRecord {
|
||||
return HomeFirstAudioContentRecord(
|
||||
contentId = id,
|
||||
creatorId = id + 100,
|
||||
creatorNickname = "creator$id",
|
||||
creatorProfileImage = null,
|
||||
title = "first audio$id",
|
||||
price = id.toInt(),
|
||||
coverImage = "cover/audio$id.png",
|
||||
isPointAvailable = true,
|
||||
isAdult = false,
|
||||
isOriginalSeries = false
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user