309 lines
11 KiB
Kotlin

package roomescape.theme.web
import com.ninjasquad.springmockk.MockkBean
import com.ninjasquad.springmockk.SpykBean
import io.kotest.assertions.assertSoftly
import io.kotest.matchers.collections.shouldContainAll
import io.kotest.matchers.shouldBe
import io.mockk.every
import io.mockk.just
import io.mockk.runs
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
import org.springframework.http.MediaType
import org.springframework.test.web.servlet.MockMvc
import roomescape.theme.business.ThemeService
import roomescape.theme.infrastructure.persistence.ThemeRepository
import roomescape.util.RoomescapeApiTest
import roomescape.util.ThemeFixture
@WebMvcTest(ThemeController::class)
class ThemeControllerTest(mockMvc: MockMvc) : RoomescapeApiTest() {
@SpykBean
private lateinit var themeService: ThemeService
@MockkBean
private lateinit var themeRepository: ThemeRepository
init {
Given("모든 테마를 조회할 때") {
val endpoint = "/themes"
When("로그인 상태가 아니라면") {
doNotLogin()
Then("로그인 페이지로 이동한다.") {
runGetTest(
mockMvc = mockMvc,
endpoint = endpoint,
log = true
) {
status { is3xxRedirection() }
header {
string("Location", "/login")
}
}
}
}
When("로그인 상태라면") {
loginAsUser()
Then("조회에 성공한다.") {
every {
themeRepository.findAll()
} returns listOf(
ThemeFixture.create(id = 1, name = "theme1"),
ThemeFixture.create(id = 2, name = "theme2"),
ThemeFixture.create(id = 3, name = "theme3")
)
val response: ThemesResponse = runGetTest(
mockMvc = mockMvc,
endpoint = endpoint,
log = true
) {
status { isOk() }
content {
contentType(MediaType.APPLICATION_JSON)
}
}.andReturn().readValue(ThemesResponse::class.java)
assertSoftly(response.themes) {
it.size shouldBe 3
it.map { m -> m.name } shouldContainAll listOf("theme1", "theme2", "theme3")
}
}
}
}
Given("테마를 추가할 때") {
val endpoint = "/themes"
val request = ThemeRequest(
name = "theme1",
description = "description1",
thumbnail = "http://example.com/thumbnail1.jpg"
)
When("로그인 상태가 아니라면") {
doNotLogin()
Then("로그인 페이지로 이동한다.") {
runPostTest(
mockMvc = mockMvc,
endpoint = endpoint,
body = request,
log = true
) {
status { is3xxRedirection() }
header {
string("Location", "/login")
}
}
}
}
When("관리자가 아닌 회원은") {
loginAsUser()
Then("로그인 페이지로 이동한다.") {
runPostTest(
mockMvc = mockMvc,
endpoint = endpoint,
body = request,
log = true
) {
status { is3xxRedirection() }
jsonPath("$.errorType") { value("PERMISSION_DOES_NOT_EXIST") }
}
}
}
When("동일한 이름의 테마가 있으면") {
loginAsAdmin()
Then("409 에러를 응답한다.") {
every {
themeRepository.existsByName(request.name)
} returns true
runPostTest(
mockMvc = mockMvc,
endpoint = endpoint,
body = request,
log = true
) {
status { isConflict() }
jsonPath("$.errorType") { value("THEME_DUPLICATED") }
}
}
}
When("값이 잘못 입력되면 400 에러를 응답한다") {
beforeTest {
loginAsAdmin()
}
val request = ThemeRequest(
name = "theme1",
description = "description1",
thumbnail = "http://example.com/thumbnail1.jpg"
)
fun runTest(request: ThemeRequest) {
runPostTest(
mockMvc = mockMvc,
endpoint = endpoint,
body = request,
log = true
) {
status { isBadRequest() }
}
}
Then("이름이 공백인 경우") {
val invalidRequest = request.copy(name = " ")
runTest(invalidRequest)
}
Then("이름이 20글자를 초과하는 경우") {
val invalidRequest = request.copy(name = "a".repeat(21))
runTest(invalidRequest)
}
Then("설명이 공백인 경우") {
val invalidRequest = request.copy(description = " ")
runTest(invalidRequest)
}
Then("설명이 100글자를 초과하는 경우") {
val invalidRequest = request.copy(description = "a".repeat(101))
runTest(invalidRequest)
}
Then("썸네일이 공백인 경우") {
val invalidRequest = request.copy(thumbnail = " ")
runTest(invalidRequest)
}
Then("썸네일이 URL 형식이 아닌 경우") {
val invalidRequest = request.copy(thumbnail = "invalid-url")
runTest(invalidRequest)
}
}
When("저장에 성공하면") {
loginAsAdmin()
val theme = ThemeFixture.create(
id = 1,
name = request.name,
description = request.description,
thumbnail = request.thumbnail
)
every {
themeService.create(request)
} returns ThemeResponse(
id = theme.id!!,
name = theme.name,
description = theme.description,
thumbnail = theme.thumbnail
)
Then("201 응답을 받는다.") {
runPostTest(
mockMvc = mockMvc,
endpoint = endpoint,
body = request,
log = true
) {
status { isCreated() }
header {
string("Location", "/themes/${theme.id}")
}
jsonPath("$.data.id") { value(theme.id) }
jsonPath("$.data.name") { value(theme.name) }
jsonPath("$.data.description") { value(theme.description) }
jsonPath("$.data.thumbnail") { value(theme.thumbnail) }
}
}
}
}
Given("테마를 제거할 때") {
val themeId = 1L
val endpoint = "/themes/$themeId"
When("로그인 상태가 아니라면") {
doNotLogin()
Then("로그인 페이지로 이동한다.") {
runDeleteTest(
mockMvc = mockMvc,
endpoint = endpoint,
log = true
) {
status { is3xxRedirection() }
header {
string("Location", "/login")
}
}
}
}
When("관리자가 아닌 회원은") {
loginAsUser()
Then("로그인 페이지로 이동한다.") {
runDeleteTest(
mockMvc = mockMvc,
endpoint = endpoint,
log = true
) {
status { is3xxRedirection() }
jsonPath("$.errorType") { value("PERMISSION_DOES_NOT_EXIST") }
}
}
}
When("입력된 ID에 해당하는 테마가 없으면") {
loginAsAdmin()
Then("409 에러를 응답한다.") {
every {
themeRepository.isReservedTheme(themeId)
} returns true
runDeleteTest(
mockMvc = mockMvc,
endpoint = endpoint,
log = true
) {
status { isConflict() }
jsonPath("$.errorType") { value("THEME_IS_USED_CONFLICT") }
}
}
}
When("정상적으로 제거되면") {
loginAsAdmin()
every {
themeRepository.isReservedTheme(themeId)
} returns false
every {
themeRepository.deleteById(themeId)
} just runs
Then("204 응답을 받는다.") {
runDeleteTest(
mockMvc = mockMvc,
endpoint = endpoint,
log = true
) {
status { isNoContent() }
}
}
}
}
}
}