test #426
@@ -0,0 +1,35 @@
|
|||||||
|
package kr.co.vividnext.sodalive.v2.api.creator.channel.live.application
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
|
import kr.co.vividnext.sodalive.v2.api.creator.channel.live.dto.CreatorChannelLiveTabResponse
|
||||||
|
import kr.co.vividnext.sodalive.v2.common.domain.ContentSort
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.live.application.CreatorChannelLiveQueryService
|
||||||
|
import org.springframework.stereotype.Service
|
||||||
|
import org.springframework.transaction.annotation.Transactional
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
class CreatorChannelLiveFacade(
|
||||||
|
private val creatorChannelLiveQueryService: CreatorChannelLiveQueryService
|
||||||
|
) {
|
||||||
|
fun getLiveTab(
|
||||||
|
creatorId: Long,
|
||||||
|
viewer: Member,
|
||||||
|
sort: ContentSort,
|
||||||
|
page: Int,
|
||||||
|
size: Int,
|
||||||
|
now: LocalDateTime = LocalDateTime.now()
|
||||||
|
): CreatorChannelLiveTabResponse {
|
||||||
|
return CreatorChannelLiveTabResponse.from(
|
||||||
|
creatorChannelLiveQueryService.getLiveTab(
|
||||||
|
creatorId = creatorId,
|
||||||
|
viewer = viewer,
|
||||||
|
sort = sort,
|
||||||
|
page = page,
|
||||||
|
size = size,
|
||||||
|
now = now
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
package kr.co.vividnext.sodalive.v2.api.creator.channel.live.dto
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
|
import kr.co.vividnext.sodalive.v2.common.domain.ContentSort
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.live.domain.CreatorChannelAudioContent
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.live.domain.CreatorChannelLive
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.live.domain.CreatorChannelLiveTab
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
import java.time.ZoneOffset
|
||||||
|
|
||||||
|
data class CreatorChannelLiveTabResponse(
|
||||||
|
val liveReplayContentCount: Int,
|
||||||
|
val currentLive: CreatorChannelLiveResponse?,
|
||||||
|
val liveReplayContents: List<CreatorChannelAudioContentResponse>,
|
||||||
|
val sort: ContentSort,
|
||||||
|
val page: Int,
|
||||||
|
val size: Int,
|
||||||
|
@JsonProperty("hasNext")
|
||||||
|
val hasNext: Boolean
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
fun from(tab: CreatorChannelLiveTab): CreatorChannelLiveTabResponse {
|
||||||
|
return CreatorChannelLiveTabResponse(
|
||||||
|
liveReplayContentCount = tab.liveReplayContentCount,
|
||||||
|
currentLive = tab.currentLive?.let(CreatorChannelLiveResponse::from),
|
||||||
|
liveReplayContents = tab.liveReplayContents.map(CreatorChannelAudioContentResponse::from),
|
||||||
|
sort = tab.sort,
|
||||||
|
page = tab.page.page,
|
||||||
|
size = tab.page.size,
|
||||||
|
hasNext = tab.hasNext
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class CreatorChannelAudioContentResponse(
|
||||||
|
val audioContentId: Long,
|
||||||
|
val title: String,
|
||||||
|
val duration: String?,
|
||||||
|
val imageUrl: String?,
|
||||||
|
val price: Int,
|
||||||
|
@JsonProperty("isAdult")
|
||||||
|
val isAdult: Boolean,
|
||||||
|
@JsonProperty("isPointAvailable")
|
||||||
|
val isPointAvailable: Boolean,
|
||||||
|
@JsonProperty("isFirstContent")
|
||||||
|
val isFirstContent: Boolean,
|
||||||
|
val seriesName: String?,
|
||||||
|
@JsonProperty("isOriginalSeries")
|
||||||
|
val isOriginalSeries: Boolean?,
|
||||||
|
@JsonProperty("isOwned")
|
||||||
|
val isOwned: Boolean,
|
||||||
|
@JsonProperty("isRented")
|
||||||
|
val isRented: Boolean
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
fun from(content: CreatorChannelAudioContent): CreatorChannelAudioContentResponse {
|
||||||
|
return CreatorChannelAudioContentResponse(
|
||||||
|
audioContentId = content.audioContentId,
|
||||||
|
title = content.title,
|
||||||
|
duration = content.duration,
|
||||||
|
imageUrl = content.imageUrl,
|
||||||
|
price = content.price,
|
||||||
|
isAdult = content.isAdult,
|
||||||
|
isPointAvailable = content.isPointAvailable,
|
||||||
|
isFirstContent = content.isFirstContent,
|
||||||
|
seriesName = content.seriesName,
|
||||||
|
isOriginalSeries = content.isOriginalSeries,
|
||||||
|
isOwned = content.isOwned,
|
||||||
|
isRented = content.isRented
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class CreatorChannelLiveResponse(
|
||||||
|
val liveId: Long,
|
||||||
|
val title: String,
|
||||||
|
val coverImageUrl: String?,
|
||||||
|
val beginDateTimeUtc: String,
|
||||||
|
val price: Int,
|
||||||
|
@JsonProperty("isAdult")
|
||||||
|
val isAdult: Boolean
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
fun from(live: CreatorChannelLive): CreatorChannelLiveResponse {
|
||||||
|
return CreatorChannelLiveResponse(
|
||||||
|
liveId = live.liveId,
|
||||||
|
title = live.title,
|
||||||
|
coverImageUrl = live.coverImageUrl,
|
||||||
|
beginDateTimeUtc = live.beginDateTime.toUtcIso(),
|
||||||
|
price = live.price,
|
||||||
|
isAdult = live.isAdult
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun LocalDateTime.toUtcIso(): String {
|
||||||
|
return atOffset(ZoneOffset.UTC).toInstant().toString()
|
||||||
|
}
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
package kr.co.vividnext.sodalive.v2.api.creator.channel.live.application
|
||||||
|
|
||||||
|
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.live.application.CreatorChannelLiveQueryService
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.live.domain.CreatorChannelAudioContent
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.live.domain.CreatorChannelLive
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.live.domain.CreatorChannelLiveTab
|
||||||
|
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 CreatorChannelLiveFacadeTest {
|
||||||
|
@Test
|
||||||
|
@DisplayName("라이브 탭 facade는 query service 결과를 공개 응답 DTO로 변환한다")
|
||||||
|
fun shouldMapLiveTabQueryResultToPublicResponse() {
|
||||||
|
val service = Mockito.mock(CreatorChannelLiveQueryService::class.java)
|
||||||
|
val facade = CreatorChannelLiveFacade(service)
|
||||||
|
val viewer = createMember(id = 10L)
|
||||||
|
val now = LocalDateTime.of(2026, 6, 17, 10, 0)
|
||||||
|
Mockito.doReturn(createTab()).`when`(service).getLiveTab(
|
||||||
|
creatorId = 1L,
|
||||||
|
viewer = viewer,
|
||||||
|
sort = ContentSort.LATEST,
|
||||||
|
page = 0,
|
||||||
|
size = 20,
|
||||||
|
now = now
|
||||||
|
)
|
||||||
|
|
||||||
|
val response = facade.getLiveTab(
|
||||||
|
creatorId = 1L,
|
||||||
|
viewer = viewer,
|
||||||
|
sort = ContentSort.LATEST,
|
||||||
|
page = 0,
|
||||||
|
size = 20,
|
||||||
|
now = now
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals(1, response.liveReplayContentCount)
|
||||||
|
assertEquals(101L, response.currentLive?.liveId)
|
||||||
|
assertEquals("2026-06-17T01:00:00Z", response.currentLive?.beginDateTimeUtc)
|
||||||
|
assertEquals(201L, response.liveReplayContents.first().audioContentId)
|
||||||
|
assertTrue(response.liveReplayContents.first().isOwned)
|
||||||
|
assertFalse(response.liveReplayContents.first().isRented)
|
||||||
|
assertEquals(ContentSort.LATEST, response.sort)
|
||||||
|
assertEquals(0, response.page)
|
||||||
|
assertEquals(20, response.size)
|
||||||
|
assertFalse(response.hasNext)
|
||||||
|
}
|
||||||
|
|
||||||
|
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(): CreatorChannelLiveTab {
|
||||||
|
return CreatorChannelLiveTab(
|
||||||
|
liveReplayContentCount = 1,
|
||||||
|
currentLive = CreatorChannelLive(
|
||||||
|
liveId = 101L,
|
||||||
|
title = "live",
|
||||||
|
coverImageUrl = "live.png",
|
||||||
|
beginDateTime = LocalDateTime.of(2026, 6, 17, 1, 0),
|
||||||
|
price = 20,
|
||||||
|
isAdult = true
|
||||||
|
),
|
||||||
|
liveReplayContents = listOf(
|
||||||
|
CreatorChannelAudioContent(
|
||||||
|
audioContentId = 201L,
|
||||||
|
title = "audio",
|
||||||
|
duration = "00:10:00",
|
||||||
|
imageUrl = "audio.png",
|
||||||
|
price = 30,
|
||||||
|
isAdult = false,
|
||||||
|
isPointAvailable = true,
|
||||||
|
isFirstContent = true,
|
||||||
|
publishedAt = LocalDateTime.of(2026, 6, 16, 1, 0),
|
||||||
|
seriesName = "series",
|
||||||
|
isOriginalSeries = true,
|
||||||
|
isOwned = true,
|
||||||
|
isRented = false
|
||||||
|
)
|
||||||
|
),
|
||||||
|
sort = ContentSort.LATEST,
|
||||||
|
page = CreatorChannelPage(page = 0, size = 20),
|
||||||
|
hasNext = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user