test(creator): 채널 라이브 통합 응답 검증을 보강한다
This commit is contained in:
@@ -116,6 +116,41 @@ class CreatorChannelLiveControllerTest @Autowired constructor(
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("크리에이터 채널 라이브 탭 조회는 다음 페이지가 있는 대표 응답 표면을 반환한다")
|
||||
fun shouldReturnLiveTabSurfaceWhenNextPageExists() {
|
||||
val viewer = createMember(id = 10L)
|
||||
Mockito.doReturn(createResponse(liveReplayContentCount = 21, contentCount = 20, hasNext = true))
|
||||
.`when`(facade).getLiveTab(
|
||||
eqValue(1L),
|
||||
eqValue(viewer),
|
||||
eqValue(ContentSort.LATEST),
|
||||
eqValue(0),
|
||||
eqValue(20),
|
||||
anyValue(LocalDateTime.now())
|
||||
)
|
||||
|
||||
mockMvc.perform(
|
||||
get("/api/v2/creator-channels/1/live")
|
||||
.with(user(MemberAdapter(viewer)))
|
||||
)
|
||||
.andExpect(status().isOk)
|
||||
.andExpect(jsonPath("$.success").value(true))
|
||||
.andExpect(jsonPath("$.data.liveReplayContentCount").value(21))
|
||||
.andExpect(jsonPath("$.data.currentLive.liveId").value(101L))
|
||||
.andExpect(jsonPath("$.data.liveReplayContents.length()").value(20))
|
||||
.andExpect(jsonPath("$.data.sort").value("LATEST"))
|
||||
.andExpect(jsonPath("$.data.page").value(0))
|
||||
.andExpect(jsonPath("$.data.size").value(20))
|
||||
.andExpect(jsonPath("$.data.hasNext").value(true))
|
||||
.andExpect(jsonPath("$.data.liveReplayContents[0].isOwned").value(true))
|
||||
.andExpect(jsonPath("$.data.liveReplayContents[0].isRented").value(false))
|
||||
.andExpect(jsonPath("$.data.liveReplayContents[1].isOwned").value(false))
|
||||
.andExpect(jsonPath("$.data.liveReplayContents[1].isRented").value(true))
|
||||
.andExpect(jsonPath("$.data.liveReplayContents[2].isOwned").value(false))
|
||||
.andExpect(jsonPath("$.data.liveReplayContents[2].isRented").value(false))
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("크리에이터 채널 라이브 탭 조회는 잘못된 page 요청을 기존 오류 응답으로 반환한다")
|
||||
fun shouldReturnErrorResponseWhenPageIsInvalid() {
|
||||
@@ -181,9 +216,13 @@ class CreatorChannelLiveControllerTest @Autowired constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun createResponse(): CreatorChannelLiveTabResponse {
|
||||
private fun createResponse(
|
||||
liveReplayContentCount: Int = 1,
|
||||
contentCount: Int = 1,
|
||||
hasNext: Boolean = false
|
||||
): CreatorChannelLiveTabResponse {
|
||||
return CreatorChannelLiveTabResponse(
|
||||
liveReplayContentCount = 1,
|
||||
liveReplayContentCount = liveReplayContentCount,
|
||||
currentLive = CreatorChannelLiveResponse(
|
||||
liveId = 101L,
|
||||
title = "live",
|
||||
@@ -192,26 +231,30 @@ class CreatorChannelLiveControllerTest @Autowired constructor(
|
||||
price = 20,
|
||||
isAdult = true
|
||||
),
|
||||
liveReplayContents = listOf(
|
||||
CreatorChannelAudioContentResponse(
|
||||
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
|
||||
)
|
||||
),
|
||||
liveReplayContents = createAudioContents(contentCount),
|
||||
sort = ContentSort.LATEST,
|
||||
page = 0,
|
||||
size = 20,
|
||||
hasNext = false
|
||||
hasNext = hasNext
|
||||
)
|
||||
}
|
||||
|
||||
private fun createAudioContents(count: Int): List<CreatorChannelAudioContentResponse> {
|
||||
return (1..count).map { index ->
|
||||
CreatorChannelAudioContentResponse(
|
||||
audioContentId = 200L + index,
|
||||
title = "audio-$index",
|
||||
duration = "00:10:00",
|
||||
imageUrl = "audio-$index.png",
|
||||
price = 30,
|
||||
isAdult = false,
|
||||
isPointAvailable = true,
|
||||
isFirstContent = index == 1,
|
||||
seriesName = "series",
|
||||
isOriginalSeries = true,
|
||||
isOwned = index == 1,
|
||||
isRented = index == 2
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,191 @@
|
||||
package kr.co.vividnext.sodalive.v2.api.creator.channel.live.adapter.`in`.web
|
||||
|
||||
import kr.co.vividnext.sodalive.content.AudioContent
|
||||
import kr.co.vividnext.sodalive.content.order.Order
|
||||
import kr.co.vividnext.sodalive.content.order.OrderType
|
||||
import kr.co.vividnext.sodalive.content.theme.AudioContentTheme
|
||||
import kr.co.vividnext.sodalive.live.room.GenderRestriction
|
||||
import kr.co.vividnext.sodalive.live.room.LiveRoom
|
||||
import kr.co.vividnext.sodalive.member.Member
|
||||
import kr.co.vividnext.sodalive.member.MemberAdapter
|
||||
import kr.co.vividnext.sodalive.member.MemberRole
|
||||
import kr.co.vividnext.sodalive.support.EmbeddedRedisInitializer
|
||||
import org.junit.jupiter.api.DisplayName
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc
|
||||
import org.springframework.boot.test.context.SpringBootTest
|
||||
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user
|
||||
import org.springframework.test.context.ContextConfiguration
|
||||
import org.springframework.test.web.servlet.MockMvc
|
||||
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
|
||||
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath
|
||||
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
|
||||
import org.springframework.transaction.support.TransactionTemplate
|
||||
import java.time.LocalDateTime
|
||||
import javax.persistence.EntityManager
|
||||
|
||||
@SpringBootTest(
|
||||
properties = [
|
||||
"cloud.aws.cloud-front.host=https://cdn.test",
|
||||
"spring.cache.type=none",
|
||||
"spring.datasource.url=jdbc:h2:mem:creator-channel-live-e2e;MODE=MySQL;NON_KEYWORDS=VALUE;DB_CLOSE_ON_EXIT=FALSE"
|
||||
]
|
||||
)
|
||||
@AutoConfigureMockMvc
|
||||
@ContextConfiguration(initializers = [EmbeddedRedisInitializer::class])
|
||||
class CreatorChannelLiveEndToEndTest @Autowired constructor(
|
||||
private val mockMvc: MockMvc,
|
||||
private val entityManager: EntityManager,
|
||||
private val transactionTemplate: TransactionTemplate
|
||||
) {
|
||||
@Test
|
||||
@DisplayName("라이브 탭 API는 controller-service-repository를 거쳐 대표 응답을 반환한다")
|
||||
fun shouldReturnLiveTabThroughControllerServiceAndRepository() {
|
||||
val fixture = createFixture()
|
||||
|
||||
mockMvc.perform(
|
||||
get("/api/v2/creator-channels/${fixture.creatorId}/live")
|
||||
.param("sort", "LATEST")
|
||||
.param("page", "0")
|
||||
.param("size", "20")
|
||||
.with(user(MemberAdapter(fixture.viewer)))
|
||||
)
|
||||
.andExpect(status().isOk)
|
||||
.andExpect(jsonPath("$.success").value(true))
|
||||
.andExpect(jsonPath("$.data.liveReplayContentCount").value(21))
|
||||
.andExpect(jsonPath("$.data.currentLive.liveId").value(fixture.currentLiveId))
|
||||
.andExpect(jsonPath("$.data.currentLive.title").value("e2e-live"))
|
||||
.andExpect(jsonPath("$.data.currentLive.coverImageUrl").value("https://cdn.test/live-cover.png"))
|
||||
.andExpect(jsonPath("$.data.liveReplayContents.length()").value(20))
|
||||
.andExpect(jsonPath("$.data.liveReplayContents[0].audioContentId").value(fixture.keepContentId))
|
||||
.andExpect(jsonPath("$.data.liveReplayContents[0].imageUrl").value("https://cdn.test/audio-1.png"))
|
||||
.andExpect(jsonPath("$.data.liveReplayContents[0].isOwned").value(true))
|
||||
.andExpect(jsonPath("$.data.liveReplayContents[0].isRented").value(false))
|
||||
.andExpect(jsonPath("$.data.liveReplayContents[1].audioContentId").value(fixture.rentalContentId))
|
||||
.andExpect(jsonPath("$.data.liveReplayContents[1].isOwned").value(false))
|
||||
.andExpect(jsonPath("$.data.liveReplayContents[1].isRented").value(true))
|
||||
.andExpect(jsonPath("$.data.liveReplayContents[2].audioContentId").value(fixture.unorderedContentId))
|
||||
.andExpect(jsonPath("$.data.liveReplayContents[2].isOwned").value(false))
|
||||
.andExpect(jsonPath("$.data.liveReplayContents[2].isRented").value(false))
|
||||
.andExpect(jsonPath("$.data.sort").value("LATEST"))
|
||||
.andExpect(jsonPath("$.data.page").value(0))
|
||||
.andExpect(jsonPath("$.data.size").value(20))
|
||||
.andExpect(jsonPath("$.data.hasNext").value(true))
|
||||
}
|
||||
|
||||
private fun createFixture(): Fixture {
|
||||
return transactionTemplate.execute {
|
||||
val now = LocalDateTime.now()
|
||||
val viewer = saveMember("live-e2e-viewer", MemberRole.USER)
|
||||
val creator = saveMember("live-e2e-creator", MemberRole.CREATOR)
|
||||
val currentLive = saveLiveRoom(creator, now.minusHours(1))
|
||||
val liveReplayTheme = saveTheme("다시듣기")
|
||||
val contents = (1..21).map { index ->
|
||||
saveAudioContent(
|
||||
creator = creator,
|
||||
releaseDate = now.minusMinutes(index.toLong()),
|
||||
theme = liveReplayTheme,
|
||||
coverImage = "audio-$index.png"
|
||||
)
|
||||
}
|
||||
saveOrder(viewer, creator, contents[0], OrderType.KEEP)
|
||||
saveOrder(viewer, creator, contents[1], OrderType.RENTAL, endDate = now.plusDays(1))
|
||||
entityManager.flush()
|
||||
|
||||
Fixture(
|
||||
viewer = viewer,
|
||||
creatorId = creator.id!!,
|
||||
currentLiveId = currentLive.id!!,
|
||||
keepContentId = contents[0].id!!,
|
||||
rentalContentId = contents[1].id!!,
|
||||
unorderedContentId = contents[2].id!!
|
||||
)
|
||||
}!!
|
||||
}
|
||||
|
||||
private fun saveMember(nickname: String, role: MemberRole): Member {
|
||||
val member = Member(
|
||||
email = "$nickname@test.com",
|
||||
password = "password",
|
||||
nickname = nickname,
|
||||
role = role
|
||||
)
|
||||
entityManager.persist(member)
|
||||
return member
|
||||
}
|
||||
|
||||
private fun saveLiveRoom(creator: Member, beginDateTime: LocalDateTime): LiveRoom {
|
||||
val liveRoom = LiveRoom(
|
||||
title = "e2e-live",
|
||||
notice = "notice",
|
||||
beginDateTime = beginDateTime,
|
||||
numberOfPeople = 0,
|
||||
coverImage = "live-cover.png",
|
||||
isAdult = false,
|
||||
price = 50,
|
||||
isAvailableJoinCreator = true,
|
||||
genderRestriction = GenderRestriction.ALL
|
||||
)
|
||||
liveRoom.member = creator
|
||||
liveRoom.channelName = "e2e-live-channel"
|
||||
liveRoom.isActive = true
|
||||
entityManager.persist(liveRoom)
|
||||
return liveRoom
|
||||
}
|
||||
|
||||
private fun saveAudioContent(
|
||||
creator: Member,
|
||||
releaseDate: LocalDateTime,
|
||||
theme: AudioContentTheme,
|
||||
coverImage: String
|
||||
): AudioContent {
|
||||
val content = AudioContent(
|
||||
title = "audio-$coverImage",
|
||||
detail = "detail",
|
||||
languageCode = "ko",
|
||||
releaseDate = releaseDate,
|
||||
isAdult = false,
|
||||
price = 100,
|
||||
isPointAvailable = true
|
||||
)
|
||||
content.member = creator
|
||||
content.theme = theme
|
||||
content.isActive = true
|
||||
content.coverImage = coverImage
|
||||
content.duration = "00:10:00"
|
||||
entityManager.persist(content)
|
||||
return content
|
||||
}
|
||||
|
||||
private fun saveTheme(name: String): AudioContentTheme {
|
||||
val theme = AudioContentTheme(theme = name, image = "$name.png", isActive = true)
|
||||
entityManager.persist(theme)
|
||||
return theme
|
||||
}
|
||||
|
||||
private fun saveOrder(
|
||||
member: Member,
|
||||
creator: Member,
|
||||
content: AudioContent,
|
||||
type: OrderType,
|
||||
endDate: LocalDateTime? = null
|
||||
): Order {
|
||||
val order = Order(type = type, isActive = true)
|
||||
order.member = member
|
||||
order.creator = creator
|
||||
order.audioContent = content
|
||||
entityManager.persist(order)
|
||||
endDate?.let { order.endDate = it }
|
||||
return order
|
||||
}
|
||||
|
||||
private data class Fixture(
|
||||
val viewer: Member,
|
||||
val creatorId: Long,
|
||||
val currentLiveId: Long,
|
||||
val keepContentId: Long,
|
||||
val rentalContentId: Long,
|
||||
val unorderedContentId: Long
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user