test(series-banner): 시리즈 배너 lazy 회귀 검증을 추가한다

This commit is contained in:
2026-06-29 16:36:19 +09:00
parent 6d371bb356
commit 6b1ac9778e
3 changed files with 226 additions and 36 deletions

View File

@@ -0,0 +1,113 @@
package kr.co.vividnext.sodalive.admin.content.series.banner
import kr.co.vividnext.sodalive.admin.content.series.genre.SeriesGenre
import kr.co.vividnext.sodalive.content.series.main.banner.SeriesBanner
import kr.co.vividnext.sodalive.creator.admin.content.series.Series
import kr.co.vividnext.sodalive.i18n.Lang
import kr.co.vividnext.sodalive.member.Member
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.annotation.DirtiesContext
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 javax.persistence.EntityManager
@SpringBootTest(
properties = [
"cloud.aws.cloud-front.host=https://cdn.test",
"spring.datasource.url=jdbc:h2:mem:admin-series-banner-controller-integration;" +
"MODE=MySQL;DATABASE_TO_UPPER=false;NON_KEYWORDS=VALUE;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE"
]
)
@AutoConfigureMockMvc
@ContextConfiguration(initializers = [EmbeddedRedisInitializer::class])
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
class AdminContentSeriesBannerControllerIntegrationTest @Autowired constructor(
private val mockMvc: MockMvc,
private val entityManager: EntityManager,
private val transactionTemplate: TransactionTemplate
) {
@Test
@DisplayName("관리자 시리즈 배너 목록 API는 OSIV off 환경에서 lazy 초기화 예외 없이 응답한다")
fun shouldReturnAdminSeriesBannerListWhenOpenInViewIsDisabled() {
createBannerFixture()
mockMvc.perform(
get("/admin/audio-content/series/banner/list")
.param("page", "0")
.param("size", "20")
.with(user("admin").roles("ADMIN"))
)
.andExpect(status().isOk)
.andExpect(jsonPath("$.success").value(true))
.andExpect(jsonPath("$.data.totalCount").value(1))
.andExpect(jsonPath("$.data.content[0].seriesTitle").value("series-admin-banner (일본어)"))
.andExpect(jsonPath("$.data.content[0].imagePath").value("https://cdn.test/banner/jp.png"))
}
@Test
@DisplayName("관리자 시리즈 배너 상세 API는 OSIV off 환경에서 lazy 초기화 예외 없이 응답한다")
fun shouldReturnAdminSeriesBannerDetailWhenOpenInViewIsDisabled() {
val bannerId = createBannerFixture()
mockMvc.perform(
get("/admin/audio-content/series/banner/{bannerId}", bannerId)
.with(user("admin").roles("ADMIN"))
)
.andExpect(status().isOk)
.andExpect(jsonPath("$.success").value(true))
.andExpect(jsonPath("$.data.id").value(bannerId))
.andExpect(jsonPath("$.data.seriesTitle").value("series-admin-banner"))
.andExpect(jsonPath("$.data.imagePath").value("https://cdn.test/banner/jp.png"))
}
private fun createBannerFixture(): Long {
return transactionTemplate.execute {
val creator = Member(
email = "admin-series-banner-controller@test.com",
password = "password",
nickname = "admin-series-banner-creator",
role = MemberRole.CREATOR
)
entityManager.persist(creator)
val genre = SeriesGenre(genre = "admin-series-banner-genre")
entityManager.persist(genre)
val series = Series(
title = "series-admin-banner",
introduction = "introduction",
languageCode = "ko"
).apply {
member = creator
this.genre = genre
}
entityManager.persist(series)
val banner = SeriesBanner(
imagePath = "banner/jp.png",
series = series,
sortOrder = 1,
lang = Lang.JA
)
entityManager.persist(
banner
)
entityManager.flush()
val bannerId = banner.id!!
entityManager.clear()
bannerId
}!!
}
}

View File

@@ -2,6 +2,8 @@ package kr.co.vividnext.sodalive.admin.content.series.banner
import com.amazonaws.services.s3.AmazonS3Client import com.amazonaws.services.s3.AmazonS3Client
import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.ObjectMapper
import kr.co.vividnext.sodalive.admin.content.series.banner.dto.SeriesBannerListPageResponse
import kr.co.vividnext.sodalive.admin.content.series.banner.dto.SeriesBannerResponse
import kr.co.vividnext.sodalive.aws.s3.S3Uploader import kr.co.vividnext.sodalive.aws.s3.S3Uploader
import kr.co.vividnext.sodalive.content.series.main.banner.ContentSeriesBannerService import kr.co.vividnext.sodalive.content.series.main.banner.ContentSeriesBannerService
import kr.co.vividnext.sodalive.content.series.main.banner.SeriesBanner import kr.co.vividnext.sodalive.content.series.main.banner.SeriesBanner
@@ -13,7 +15,6 @@ import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.mockito.Mockito import org.mockito.Mockito
import org.springframework.data.domain.PageImpl
import org.springframework.data.domain.PageRequest import org.springframework.data.domain.PageRequest
import org.springframework.mock.web.MockMultipartFile import org.springframework.mock.web.MockMultipartFile
import java.net.URL import java.net.URL
@@ -35,7 +36,12 @@ class AdminContentSeriesBannerControllerTest {
fun shouldRegisterJapaneseBannerThroughAdminApi() { fun shouldRegisterJapaneseBannerThroughAdminApi() {
val image = MockMultipartFile("image", "banner.png", "image/png", "image".toByteArray()) val image = MockMultipartFile("image", "banner.png", "image/png", "image".toByteArray())
val registeredBanner = createBanner(id = 10L, lang = Lang.JA, imagePath = "") val registeredBanner = createBanner(id = 10L, lang = Lang.JA, imagePath = "")
val updatedBanner = createBanner(id = 10L, lang = Lang.JA, imagePath = "") val updatedResponse = SeriesBannerResponse(
id = 10L,
imagePath = "https://cdn.test/series_banner/10/banner.png",
seriesId = 10L,
seriesTitle = "series-10"
)
Mockito.`when`(amazonS3Client.getUrl(Mockito.eq("test-bucket"), Mockito.anyString())) Mockito.`when`(amazonS3Client.getUrl(Mockito.eq("test-bucket"), Mockito.anyString()))
.thenAnswer { URL("https://cdn.test/${it.arguments[1]}") } .thenAnswer { URL("https://cdn.test/${it.arguments[1]}") }
@@ -47,11 +53,14 @@ class AdminContentSeriesBannerControllerTest {
lang = Lang.JA lang = Lang.JA
) )
).thenReturn(registeredBanner) ).thenReturn(registeredBanner)
Mockito.doAnswer { Mockito.`when`(
updatedBanner.apply { bannerService.updateBannerResponse(
imagePath = it.arguments[1] as String bannerId = eqLong(10L),
} imagePath = anyStringValue(),
}.`when`(bannerService).updateBanner(Mockito.eq(10L), Mockito.anyString(), Mockito.isNull()) seriesId = nullLongValue(),
imageHost = eqString("https://cdn.test")
)
).thenReturn(updatedResponse)
val response = controller.registerBanner( val response = controller.registerBanner(
image = image, image = image,
@@ -60,8 +69,52 @@ class AdminContentSeriesBannerControllerTest {
assertTrue(response.success) assertTrue(response.success)
assertEquals(10L, response.data?.id) assertEquals(10L, response.data?.id)
assertTrue(response.data?.imagePath?.startsWith("https://cdn.test/series_banner/10/") == true) assertEquals("https://cdn.test/series_banner/10/banner.png", response.data?.imagePath)
Mockito.verify(bannerService).registerBanner(1L, "", Lang.JA) Mockito.verify(bannerService).registerBanner(1L, "", Lang.JA)
Mockito.verify(bannerService).updateBannerResponse(
bannerId = eqLong(10L),
imagePath = anyStringValue(),
seriesId = nullLongValue(),
imageHost = eqString("https://cdn.test")
)
}
@Test
fun shouldDelegateUpdateBannerResponseToService() {
val image = MockMultipartFile("image", "banner.png", "image/png", "image".toByteArray())
val serviceResponse = SeriesBannerResponse(
id = 10L,
imagePath = "https://cdn.test/series_banner/10/banner.png",
seriesId = 20L,
seriesTitle = "series-20"
)
Mockito.`when`(amazonS3Client.getUrl(Mockito.eq("test-bucket"), Mockito.anyString()))
.thenAnswer { URL("https://cdn.test/${it.arguments[1]}") }
Mockito.`when`(bannerService.getBannerById(10L)).thenReturn(createBanner(id = 10L, lang = Lang.JA, imagePath = "old.png"))
Mockito.`when`(
bannerService.updateBannerResponse(
bannerId = eqLong(10L),
imagePath = anyStringValue(),
seriesId = eqLong(20L),
imageHost = eqString("https://cdn.test")
)
).thenReturn(serviceResponse)
val response = controller.updateBanner(
image = image,
requestString = "{\"bannerId\":10,\"seriesId\":20}"
)
assertTrue(response.success)
assertEquals("series-20", response.data?.seriesTitle)
Mockito.verify(bannerService).getBannerById(10L)
Mockito.verify(bannerService).updateBannerResponse(
bannerId = eqLong(10L),
imagePath = anyStringValue(),
seriesId = eqLong(20L),
imageHost = eqString("https://cdn.test")
)
} }
@Test @Test
@@ -77,15 +130,45 @@ class AdminContentSeriesBannerControllerTest {
@Test @Test
fun shouldAppendBannerLanguageToSeriesTitleInBannerList() { fun shouldAppendBannerLanguageToSeriesTitleInBannerList() {
val pageable = PageRequest.of(0, 20) val pageable = PageRequest.of(0, 20)
val japaneseBanner = createBanner(id = 12L, lang = Lang.JA, imagePath = "banner/jp.png") val serviceResponse = SeriesBannerListPageResponse(
totalCount = 1,
content = listOf(
SeriesBannerResponse(
id = 12L,
imagePath = "https://cdn.test/banner/jp.png",
seriesId = 12L,
seriesTitle = "series-12 (일본어)"
)
)
)
Mockito.`when`(bannerService.getActiveBanners(pageable)) Mockito.`when`(bannerService.getActiveBanners(pageable, "https://cdn.test"))
.thenReturn(PageImpl(listOf(japaneseBanner), pageable, 1)) .thenReturn(serviceResponse)
val response = controller.getBannerList(page = 0, size = 20) val response = controller.getBannerList(page = 0, size = 20)
assertTrue(response.success) assertTrue(response.success)
assertEquals("series-12 (일본어)", response.data?.content?.first()?.seriesTitle) assertEquals("series-12 (일본어)", response.data?.content?.first()?.seriesTitle)
Mockito.verify(bannerService).getActiveBanners(pageable, "https://cdn.test")
}
@Test
fun shouldDelegateBannerDetailResponseToService() {
val serviceResponse = SeriesBannerResponse(
id = 12L,
imagePath = "https://cdn.test/banner/jp.png",
seriesId = 12L,
seriesTitle = "series-12"
)
Mockito.`when`(bannerService.getBannerDetailResponse(12L, "https://cdn.test"))
.thenReturn(serviceResponse)
val response = controller.getBannerDetail(bannerId = 12L)
assertTrue(response.success)
assertEquals("series-12", response.data?.seriesTitle)
Mockito.verify(bannerService).getBannerDetailResponse(12L, "https://cdn.test")
} }
private fun createBanner(id: Long, lang: Lang, imagePath: String): SeriesBanner { private fun createBanner(id: Long, lang: Lang, imagePath: String): SeriesBanner {
@@ -105,4 +188,12 @@ class AdminContentSeriesBannerControllerTest {
it.id = id it.id = id
} }
} }
private fun anyStringValue(): String = Mockito.anyString() ?: ""
private fun eqLong(value: Long): Long = Mockito.eq(value)
private fun eqString(value: String): String = Mockito.eq(value) ?: value
private fun nullLongValue(): Long? = Mockito.isNull()
} }

View File

@@ -1,11 +1,10 @@
package kr.co.vividnext.sodalive.content.series.main package kr.co.vividnext.sodalive.content.series.main
import kr.co.vividnext.sodalive.admin.content.series.banner.dto.SeriesBannerResponse
import kr.co.vividnext.sodalive.content.ContentType import kr.co.vividnext.sodalive.content.ContentType
import kr.co.vividnext.sodalive.content.series.ContentSeriesService import kr.co.vividnext.sodalive.content.series.ContentSeriesService
import kr.co.vividnext.sodalive.content.series.GetSeriesListResponse import kr.co.vividnext.sodalive.content.series.GetSeriesListResponse
import kr.co.vividnext.sodalive.content.series.main.banner.ContentSeriesBannerService import kr.co.vividnext.sodalive.content.series.main.banner.ContentSeriesBannerService
import kr.co.vividnext.sodalive.content.series.main.banner.SeriesBanner
import kr.co.vividnext.sodalive.creator.admin.content.series.Series
import kr.co.vividnext.sodalive.i18n.Lang import kr.co.vividnext.sodalive.i18n.Lang
import kr.co.vividnext.sodalive.i18n.LangContext import kr.co.vividnext.sodalive.i18n.LangContext
import kr.co.vividnext.sodalive.member.Member import kr.co.vividnext.sodalive.member.Member
@@ -15,7 +14,6 @@ import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.mockito.Mockito import org.mockito.Mockito
import org.springframework.data.domain.PageImpl
import org.springframework.data.domain.PageRequest import org.springframework.data.domain.PageRequest
class SeriesMainControllerTest { class SeriesMainControllerTest {
@@ -41,18 +39,16 @@ class SeriesMainControllerTest {
isAdult = true isAdult = true
) )
val pageable = PageRequest.of(0, 10) val pageable = PageRequest.of(0, 10)
val japaneseBanner = SeriesBanner( val japaneseBanner = SeriesBannerResponse(
imagePath = "banner/jp.png", id = 100L,
series = createSeries(id = 10L), imagePath = "https://cdn.test/banner/jp.png",
sortOrder = 1, seriesId = 10L,
lang = Lang.JA seriesTitle = "series-10"
).also { )
it.id = 100L
}
Mockito.`when`(memberContentPreferenceService.resolveForQuery(member)).thenReturn(preference) Mockito.`when`(memberContentPreferenceService.resolveForQuery(member)).thenReturn(preference)
Mockito.`when`(bannerService.getDisplayBanners(pageable, Lang.JA)) Mockito.`when`(bannerService.getDisplayBannerResponses(pageable, Lang.JA, "https://cdn.test"))
.thenReturn(PageImpl(listOf(japaneseBanner), pageable, 1)) .thenReturn(listOf(japaneseBanner))
Mockito.`when`( Mockito.`when`(
contentSeriesService.getSeriesList( contentSeriesService.getSeriesList(
null, null,
@@ -79,8 +75,8 @@ class SeriesMainControllerTest {
assertTrue(response.success) assertTrue(response.success)
assertEquals(1, response.data?.banners?.size) assertEquals(1, response.data?.banners?.size)
assertEquals("series-10", response.data?.banners?.first()?.seriesTitle) assertEquals("series-10", response.data?.banners?.first()?.seriesTitle)
Mockito.verify(bannerService).getDisplayBanners(pageable, Lang.JA) Mockito.verify(bannerService).getDisplayBannerResponses(pageable, Lang.JA, "https://cdn.test")
Mockito.verify(bannerService, Mockito.never()).getActiveBanners(pageable) Mockito.verify(bannerService, Mockito.never()).getActiveBanners(pageable, "https://cdn.test")
} }
private fun createMember(id: Long): Member { private fun createMember(id: Long): Member {
@@ -92,14 +88,4 @@ class SeriesMainControllerTest {
it.id = id it.id = id
} }
} }
private fun createSeries(id: Long): Series {
return Series(
title = "series-$id",
introduction = "introduction-$id",
languageCode = "ja"
).also {
it.id = id
}
}
} }