test #426

Merged
klaus merged 415 commits from test into main 2026-06-27 00:35:30 +00:00
2 changed files with 253 additions and 19 deletions
Showing only changes of commit e525f9de64 - Show all commits

View File

@@ -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
)
}
}
}

View File

@@ -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
)
}