feat(creator-channel): 오디오 탭 응답 변환을 추가한다
This commit is contained in:
@@ -0,0 +1,36 @@
|
|||||||
|
package kr.co.vividnext.sodalive.v2.api.creator.channel.audio.application
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
|
import kr.co.vividnext.sodalive.v2.api.creator.channel.audio.dto.CreatorChannelAudioTabResponse
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.audio.application.CreatorChannelAudioQueryService
|
||||||
|
import org.springframework.stereotype.Service
|
||||||
|
import org.springframework.transaction.annotation.Transactional
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
class CreatorChannelAudioFacade(
|
||||||
|
private val creatorChannelAudioQueryService: CreatorChannelAudioQueryService
|
||||||
|
) {
|
||||||
|
fun getAudioTab(
|
||||||
|
creatorId: Long,
|
||||||
|
viewer: Member,
|
||||||
|
sort: String?,
|
||||||
|
themeId: Long?,
|
||||||
|
page: Int?,
|
||||||
|
size: Int?,
|
||||||
|
now: LocalDateTime = LocalDateTime.now()
|
||||||
|
): CreatorChannelAudioTabResponse {
|
||||||
|
return CreatorChannelAudioTabResponse.from(
|
||||||
|
creatorChannelAudioQueryService.getAudioTab(
|
||||||
|
creatorId = creatorId,
|
||||||
|
viewer = viewer,
|
||||||
|
sort = sort,
|
||||||
|
themeId = themeId,
|
||||||
|
page = page,
|
||||||
|
size = size,
|
||||||
|
now = now
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package kr.co.vividnext.sodalive.v2.api.creator.channel.audio.dto
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
|
import kr.co.vividnext.sodalive.v2.api.creator.channel.common.dto.CreatorChannelAudioContentResponse
|
||||||
|
import kr.co.vividnext.sodalive.v2.common.domain.ContentSort
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.audio.domain.CreatorChannelAudioTab
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.audio.domain.CreatorChannelAudioTheme
|
||||||
|
|
||||||
|
data class CreatorChannelAudioTabResponse(
|
||||||
|
val audioContentCount: Int,
|
||||||
|
val paidAudioContentCount: Int,
|
||||||
|
val purchasedAudioContentCount: Int,
|
||||||
|
val purchasedAudioContentRate: Double,
|
||||||
|
val themes: List<CreatorChannelAudioThemeResponse>,
|
||||||
|
val audioContents: List<CreatorChannelAudioContentResponse>,
|
||||||
|
val sort: ContentSort,
|
||||||
|
val themeId: Long?,
|
||||||
|
val page: Int,
|
||||||
|
val size: Int,
|
||||||
|
@JsonProperty("hasNext")
|
||||||
|
val hasNext: Boolean
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
fun from(tab: CreatorChannelAudioTab): CreatorChannelAudioTabResponse {
|
||||||
|
return CreatorChannelAudioTabResponse(
|
||||||
|
audioContentCount = tab.audioContentCount,
|
||||||
|
paidAudioContentCount = tab.paidAudioContentCount,
|
||||||
|
purchasedAudioContentCount = tab.purchasedAudioContentCount,
|
||||||
|
purchasedAudioContentRate = tab.purchasedAudioContentRate,
|
||||||
|
themes = tab.themes.map(CreatorChannelAudioThemeResponse::from),
|
||||||
|
audioContents = tab.audioContents.map(CreatorChannelAudioContentResponse::from),
|
||||||
|
sort = tab.sort,
|
||||||
|
themeId = tab.themeId,
|
||||||
|
page = tab.page.page,
|
||||||
|
size = tab.page.size,
|
||||||
|
hasNext = tab.hasNext
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class CreatorChannelAudioThemeResponse(
|
||||||
|
val themeId: Long,
|
||||||
|
val themeName: String
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
fun from(theme: CreatorChannelAudioTheme): CreatorChannelAudioThemeResponse {
|
||||||
|
return CreatorChannelAudioThemeResponse(
|
||||||
|
themeId = theme.themeId,
|
||||||
|
themeName = theme.themeName
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
package kr.co.vividnext.sodalive.v2.api.creator.channel.audio.application
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
|
import com.fasterxml.jackson.module.kotlin.KotlinModule
|
||||||
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
|
import kr.co.vividnext.sodalive.member.MemberRole
|
||||||
|
import kr.co.vividnext.sodalive.v2.common.domain.ContentSort
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.audio.application.CreatorChannelAudioQueryService
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.audio.domain.CreatorChannelAudioTab
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.audio.domain.CreatorChannelAudioTheme
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.common.domain.CreatorChannelAudioContent
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.live.domain.CreatorChannelPage
|
||||||
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
import org.junit.jupiter.api.Assertions.assertFalse
|
||||||
|
import org.junit.jupiter.api.Assertions.assertTrue
|
||||||
|
import org.junit.jupiter.api.DisplayName
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.mockito.Mockito
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
|
||||||
|
class CreatorChannelAudioFacadeTest {
|
||||||
|
@Test
|
||||||
|
@DisplayName("오디오 탭 facade는 query service 결과를 공개 응답 DTO로 변환한다")
|
||||||
|
fun shouldMapAudioTabQueryResultToPublicResponse() {
|
||||||
|
val service = Mockito.mock(CreatorChannelAudioQueryService::class.java)
|
||||||
|
val facade = CreatorChannelAudioFacade(service)
|
||||||
|
val viewer = createMember(id = 10L)
|
||||||
|
val now = LocalDateTime.of(2026, 6, 19, 10, 0)
|
||||||
|
Mockito.doReturn(createTab()).`when`(service).getAudioTab(
|
||||||
|
creatorId = 1L,
|
||||||
|
viewer = viewer,
|
||||||
|
sort = "OWNED",
|
||||||
|
themeId = 10L,
|
||||||
|
page = 0,
|
||||||
|
size = 20,
|
||||||
|
now = now
|
||||||
|
)
|
||||||
|
|
||||||
|
val response = facade.getAudioTab(
|
||||||
|
creatorId = 1L,
|
||||||
|
viewer = viewer,
|
||||||
|
sort = "OWNED",
|
||||||
|
themeId = 10L,
|
||||||
|
page = 0,
|
||||||
|
size = 20,
|
||||||
|
now = now
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals(3, response.audioContentCount)
|
||||||
|
assertEquals(2, response.paidAudioContentCount)
|
||||||
|
assertEquals(1, response.purchasedAudioContentCount)
|
||||||
|
assertEquals(50.0, response.purchasedAudioContentRate)
|
||||||
|
assertEquals(10L, response.themes.first().themeId)
|
||||||
|
assertEquals("theme", response.themes.first().themeName)
|
||||||
|
assertEquals(201L, response.audioContents.first().audioContentId)
|
||||||
|
assertTrue(response.audioContents.first().isOwned)
|
||||||
|
assertFalse(response.audioContents.first().isRented)
|
||||||
|
assertEquals(ContentSort.OWNED, response.sort)
|
||||||
|
assertEquals(10L, response.themeId)
|
||||||
|
assertEquals(0, response.page)
|
||||||
|
assertEquals(20, response.size)
|
||||||
|
assertTrue(response.hasNext)
|
||||||
|
|
||||||
|
val json = ObjectMapper().registerModule(KotlinModule.Builder().build()).readTree(
|
||||||
|
ObjectMapper().registerModule(KotlinModule.Builder().build()).writeValueAsString(response)
|
||||||
|
)
|
||||||
|
assertTrue(json["hasNext"].asBoolean())
|
||||||
|
assertTrue(json["audioContents"][0]["isOwned"].asBoolean())
|
||||||
|
assertFalse(json["audioContents"][0]["isRented"].asBoolean())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createMember(id: Long): Member {
|
||||||
|
return Member(
|
||||||
|
email = "viewer$id@test.com",
|
||||||
|
password = "password",
|
||||||
|
nickname = "viewer$id",
|
||||||
|
role = MemberRole.USER
|
||||||
|
).apply { this.id = id }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createTab(): CreatorChannelAudioTab {
|
||||||
|
return CreatorChannelAudioTab(
|
||||||
|
audioContentCount = 3,
|
||||||
|
paidAudioContentCount = 2,
|
||||||
|
purchasedAudioContentCount = 1,
|
||||||
|
purchasedAudioContentRate = 50.0,
|
||||||
|
themes = listOf(CreatorChannelAudioTheme(themeId = 10L, themeName = "theme")),
|
||||||
|
audioContents = listOf(
|
||||||
|
CreatorChannelAudioContent(
|
||||||
|
audioContentId = 201L,
|
||||||
|
title = "audio",
|
||||||
|
duration = "00:10:00",
|
||||||
|
imageUrl = "audio.png",
|
||||||
|
price = 30,
|
||||||
|
isAdult = false,
|
||||||
|
isPointAvailable = true,
|
||||||
|
isFirstContent = true,
|
||||||
|
seriesName = "series",
|
||||||
|
isOriginalSeries = true,
|
||||||
|
isOwned = true,
|
||||||
|
isRented = false
|
||||||
|
)
|
||||||
|
),
|
||||||
|
sort = ContentSort.OWNED,
|
||||||
|
themeId = 10L,
|
||||||
|
page = CreatorChannelPage(page = 0, size = 20),
|
||||||
|
hasNext = true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user