From ede2e7f624bbe1f67f414e784a698288b22f37ad Mon Sep 17 00:00:00 2001 From: pricelees Date: Fri, 25 Jul 2025 18:09:13 +0900 Subject: [PATCH] =?UTF-8?q?refactor:=20Interceptor=EC=97=90=EC=84=9C=20red?= =?UTF-8?q?irect=20=EC=B2=98=EB=A6=AC=20=EC=A0=9C=EA=B1=B0=20=EB=B0=8F=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/web/support/AuthInterceptor.kt | 6 +- .../roomescape/auth/web/AuthControllerTest.kt | 55 +++--- .../member/controller/MemberControllerTest.kt | 43 ++-- .../web/ReservationControllerTest.kt | 187 +++++++++--------- .../theme/web/ThemeControllerTest.kt | 135 ++++++------- .../roomescape/time/web/TimeControllerTest.kt | 105 +++++----- 6 files changed, 272 insertions(+), 259 deletions(-) diff --git a/src/main/kotlin/roomescape/auth/web/support/AuthInterceptor.kt b/src/main/kotlin/roomescape/auth/web/support/AuthInterceptor.kt index c7b92706..19b0bbed 100644 --- a/src/main/kotlin/roomescape/auth/web/support/AuthInterceptor.kt +++ b/src/main/kotlin/roomescape/auth/web/support/AuthInterceptor.kt @@ -13,8 +13,8 @@ import roomescape.member.infrastructure.persistence.MemberEntity @Component class AuthInterceptor( - private val memberService: MemberService, - private val jwtHandler: JwtHandler + private val memberService: MemberService, + private val jwtHandler: JwtHandler ) : HandlerInterceptor { override fun preHandle(request: HttpServletRequest, response: HttpServletResponse, handler: Any): Boolean { if (handler !is HandlerMethod) { @@ -31,7 +31,6 @@ class AuthInterceptor( val member: MemberEntity = findMember(request, response) if (admin != null && !member.isAdmin()) { - response.sendRedirect("/login") throw AuthException(AuthErrorCode.ACCESS_DENIED) } @@ -45,7 +44,6 @@ class AuthInterceptor( return memberService.findById(memberId) } catch (e: Exception) { - response.sendRedirect("/login") throw e } } diff --git a/src/test/kotlin/roomescape/auth/web/AuthControllerTest.kt b/src/test/kotlin/roomescape/auth/web/AuthControllerTest.kt index 9fc7b3f3..da9eab89 100644 --- a/src/test/kotlin/roomescape/auth/web/AuthControllerTest.kt +++ b/src/test/kotlin/roomescape/auth/web/AuthControllerTest.kt @@ -16,7 +16,7 @@ import roomescape.util.RoomescapeApiTest @WebMvcTest(controllers = [AuthController::class]) class AuthControllerTest( - val mockMvc: MockMvc + val mockMvc: MockMvc ) : RoomescapeApiTest() { @SpykBean @@ -41,9 +41,9 @@ class AuthControllerTest( Then("토큰을 쿠키에 담아 응답한다") { runPostTest( - mockMvc = mockMvc, - endpoint = endpoint, - body = userRequest, + mockMvc = mockMvc, + endpoint = endpoint, + body = userRequest, ) { status { isOk() } header { @@ -61,12 +61,12 @@ class AuthControllerTest( memberRepository.findByEmailAndPassword(userRequest.email, userRequest.password) } returns null - Then("에러 응답") { + Then("에러 응답을 받는다.") { val expectedError = AuthErrorCode.LOGIN_FAILED runPostTest( - mockMvc = mockMvc, - endpoint = endpoint, - body = userRequest, + mockMvc = mockMvc, + endpoint = endpoint, + body = userRequest, ) { status { isEqualTo(expectedError.httpStatus.value()) } jsonPath("$.code", equalTo(expectedError.errorCode)) @@ -78,14 +78,14 @@ class AuthControllerTest( Then("400 에러를 응답한다") { listOf( - userRequest.copy(email = "invalid"), - userRequest.copy(password = " "), - "{\"email\": \"null\", \"password\": \"null\"}" + userRequest.copy(email = "invalid"), + userRequest.copy(password = " "), + "{\"email\": \"null\", \"password\": \"null\"}" ).forEach { runPostTest( - mockMvc = mockMvc, - endpoint = endpoint, - body = it, + mockMvc = mockMvc, + endpoint = endpoint, + body = it, ) { status { isEqualTo(expectedErrorCode.httpStatus.value()) } jsonPath("$.code", equalTo(expectedErrorCode.errorCode)) @@ -103,8 +103,8 @@ class AuthControllerTest( Then("회원의 이름을 응답한다") { runGetTest( - mockMvc = mockMvc, - endpoint = endpoint, + mockMvc = mockMvc, + endpoint = endpoint, ) { status { isOk() } jsonPath("$.data.name", equalTo(user.name)) @@ -118,11 +118,11 @@ class AuthControllerTest( every { jwtHandler.getMemberIdFromToken(any()) } returns invalidMemberId every { memberRepository.findByIdOrNull(invalidMemberId) } returns null - Then("에러 응답.") { + Then("에러 응답을 받는다.") { val expectedError = AuthErrorCode.UNIDENTIFIABLE_MEMBER runGetTest( - mockMvc = mockMvc, - endpoint = endpoint, + mockMvc = mockMvc, + endpoint = endpoint, ) { status { isEqualTo(expectedError.httpStatus.value()) } jsonPath("$.code", equalTo(expectedError.errorCode)) @@ -137,15 +137,14 @@ class AuthControllerTest( When("로그인 상태가 아니라면") { doNotLogin() - Then("로그인 페이지로 이동한다.") { + Then("에러 응답을 받는다.") { + val expectedError = AuthErrorCode.INVALID_TOKEN runPostTest( - mockMvc = mockMvc, - endpoint = endpoint, + mockMvc = mockMvc, + endpoint = endpoint, ) { - status { is3xxRedirection() } - header { - string("Location", "/login") - } + status { isEqualTo(expectedError.httpStatus.value()) } + jsonPath("$.code", equalTo(expectedError.errorCode)) } } } @@ -155,8 +154,8 @@ class AuthControllerTest( Then("토큰의 존재 여부와 무관하게 토큰을 만료시킨다.") { runPostTest( - mockMvc = mockMvc, - endpoint = endpoint, + mockMvc = mockMvc, + endpoint = endpoint, ) { status { isOk() } header { diff --git a/src/test/kotlin/roomescape/member/controller/MemberControllerTest.kt b/src/test/kotlin/roomescape/member/controller/MemberControllerTest.kt index b34e2555..176b2aea 100644 --- a/src/test/kotlin/roomescape/member/controller/MemberControllerTest.kt +++ b/src/test/kotlin/roomescape/member/controller/MemberControllerTest.kt @@ -7,6 +7,7 @@ import io.mockk.every import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest import org.springframework.test.web.servlet.MockMvc +import roomescape.auth.exception.AuthErrorCode import roomescape.member.web.MemberController import roomescape.member.web.MemberRetrieveListResponse import roomescape.util.MemberFixture @@ -15,7 +16,7 @@ import kotlin.random.Random @WebMvcTest(controllers = [MemberController::class]) class MemberControllerTest( - @Autowired private val mockMvc: MockMvc + @Autowired private val mockMvc: MockMvc ) : RoomescapeApiTest() { init { @@ -23,9 +24,9 @@ class MemberControllerTest( val endpoint = "/members" every { memberRepository.findAll() } returns listOf( - MemberFixture.create(id = Random.nextLong(), name = "name1"), - MemberFixture.create(id = Random.nextLong(), name = "name2"), - MemberFixture.create(id = Random.nextLong(), name = "name3"), + MemberFixture.create(id = Random.nextLong(), name = "name1"), + MemberFixture.create(id = Random.nextLong(), name = "name2"), + MemberFixture.create(id = Random.nextLong(), name = "name3"), ) `when`("관리자가 보내면") { @@ -33,15 +34,15 @@ class MemberControllerTest( then("성공한다.") { val result: String = runGetTest( - mockMvc = mockMvc, - endpoint = endpoint, + mockMvc = mockMvc, + endpoint = endpoint, ) { status { isOk() } }.andReturn().response.contentAsString val response: MemberRetrieveListResponse = readValue( - responseJson = result, - valueType = MemberRetrieveListResponse::class.java + responseJson = result, + valueType = MemberRetrieveListResponse::class.java ) assertSoftly(response.members) { @@ -51,32 +52,32 @@ class MemberControllerTest( } } - `when`("관리자가 아니면 로그인 페이지로 이동한다.") { + `when`("관리자가 아니면 에러 응답을 받는다.") { then("비회원") { doNotLogin() + val expectedError = AuthErrorCode.INVALID_TOKEN runGetTest( - mockMvc = mockMvc, - endpoint = endpoint, + mockMvc = mockMvc, + endpoint = endpoint, ) { - status { is3xxRedirection() } - header { - string("Location", "/login") - } + status { isEqualTo(expectedError.httpStatus.value()) } + }.andExpect { + jsonPath("$.code") { value(expectedError.errorCode) } } } then("일반 회원") { loginAsUser() + val expectedError = AuthErrorCode.ACCESS_DENIED runGetTest( - mockMvc = mockMvc, - endpoint = endpoint, + mockMvc = mockMvc, + endpoint = endpoint, ) { - status { is3xxRedirection() } - header { - string("Location", "/login") - } + status { isEqualTo(expectedError.httpStatus.value()) } + }.andExpect { + jsonPath("$.code") { value(expectedError.errorCode) } } } } diff --git a/src/test/kotlin/roomescape/reservation/web/ReservationControllerTest.kt b/src/test/kotlin/roomescape/reservation/web/ReservationControllerTest.kt index 04e3fb2a..054ffe3b 100644 --- a/src/test/kotlin/roomescape/reservation/web/ReservationControllerTest.kt +++ b/src/test/kotlin/roomescape/reservation/web/ReservationControllerTest.kt @@ -9,14 +9,13 @@ import io.restassured.module.kotlin.extensions.Given import io.restassured.module.kotlin.extensions.Then import io.restassured.module.kotlin.extensions.When import jakarta.persistence.EntityManager -import org.hamcrest.Matchers.containsString import org.hamcrest.Matchers.equalTo import org.springframework.boot.test.context.SpringBootTest import org.springframework.boot.test.web.server.LocalServerPort -import org.springframework.http.HttpHeaders import org.springframework.http.HttpStatus import org.springframework.http.MediaType import org.springframework.transaction.support.TransactionTemplate +import roomescape.auth.exception.AuthErrorCode import roomescape.auth.infrastructure.jwt.JwtHandler import roomescape.auth.web.support.MemberIdResolver import roomescape.member.business.MemberService @@ -38,9 +37,9 @@ import java.time.LocalTime @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) class ReservationControllerTest( - @LocalServerPort val port: Int, - val entityManager: EntityManager, - val transactionTemplate: TransactionTemplate + @LocalServerPort val port: Int, + val entityManager: EntityManager, + val transactionTemplate: TransactionTemplate ) : FunSpec({ extension(DatabaseCleanerExtension(mode = CleanerMode.AFTER_EACH_TEST)) }) { @@ -66,9 +65,9 @@ class ReservationControllerTest( test("정상 응답") { val reservationRequest = createRequest() val paymentApproveResponse = PaymentFixture.createApproveResponse().copy( - paymentKey = reservationRequest.paymentKey, - orderId = reservationRequest.orderId, - totalAmount = reservationRequest.amount, + paymentKey = reservationRequest.paymentKey, + orderId = reservationRequest.orderId, + totalAmount = reservationRequest.amount, ) every { @@ -108,12 +107,12 @@ class ReservationControllerTest( } } - test("결제 완료 후 예약 / 결제 정보 저장 과정에서 에러 발생시 결제 취소 후 에러 응답") { + test("결제 완료 후 예약 / 결제 정보 저장 과정에서 에러 발생시 결제 취소 후 에러 응답을 받는다.") { val reservationRequest = createRequest() val paymentApproveResponse = PaymentFixture.createApproveResponse().copy( - paymentKey = reservationRequest.paymentKey, - orderId = reservationRequest.orderId, - totalAmount = reservationRequest.amount, + paymentKey = reservationRequest.paymentKey, + orderId = reservationRequest.orderId, + totalAmount = reservationRequest.amount, ) every { @@ -129,8 +128,8 @@ class ReservationControllerTest( } returns PaymentFixture.createCancelResponse() val canceledPaymentSizeBeforeApiCall: Long = entityManager.createQuery( - "SELECT COUNT(c) FROM CanceledPaymentEntity c", - Long::class.java + "SELECT COUNT(c) FROM CanceledPaymentEntity c", + Long::class.java ).singleResult Given { @@ -145,8 +144,8 @@ class ReservationControllerTest( } val canceledPaymentSizeAfterApiCall: Long = entityManager.createQuery( - "SELECT COUNT(c) FROM CanceledPaymentEntity c", - Long::class.java + "SELECT COUNT(c) FROM CanceledPaymentEntity c", + Long::class.java ).singleResult canceledPaymentSizeAfterApiCall shouldBe canceledPaymentSizeBeforeApiCall + 1L @@ -203,6 +202,7 @@ class ReservationControllerTest( test("관리자만 검색할 수 있다.") { login(reservations.keys.first()) + val expectedError = AuthErrorCode.ACCESS_DENIED Given { port(port) @@ -210,7 +210,8 @@ class ReservationControllerTest( }.When { get("/reservations/search") }.Then { - header(HttpHeaders.CONTENT_TYPE, containsString(MediaType.TEXT_HTML_VALUE)) + statusCode(expectedError.httpStatus.value()) + body("code", equalTo(expectedError.errorCode)) } } @@ -309,14 +310,15 @@ class ReservationControllerTest( test("관리자만 예약을 삭제할 수 있다.") { login(MemberFixture.create(role = Role.MEMBER)) val reservation: ReservationEntity = reservations.values.flatten().first() + val expectedError = AuthErrorCode.ACCESS_DENIED Given { port(port) }.When { delete("/reservations/${reservation.id}") }.Then { - statusCode(302) - header(HttpHeaders.LOCATION, containsString("/login")) + statusCode(expectedError.httpStatus.value()) + body("code", equalTo(expectedError.errorCode)) } } @@ -326,8 +328,8 @@ class ReservationControllerTest( transactionTemplate.execute { val reservation: ReservationEntity = entityManager.find( - ReservationEntity::class.java, - reservationId + ReservationEntity::class.java, + reservationId ) reservation.reservationStatus = ReservationStatus.CONFIRMED_PAYMENT_REQUIRED entityManager.persist(reservation) @@ -346,8 +348,8 @@ class ReservationControllerTest( // 예약이 삭제되었는지 확인 transactionTemplate.executeWithoutResult { val deletedReservation = entityManager.find( - ReservationEntity::class.java, - reservationId + ReservationEntity::class.java, + reservationId ) deletedReservation shouldBe null } @@ -371,8 +373,8 @@ class ReservationControllerTest( } returns PaymentFixture.createCancelResponse() val canceledPaymentSizeBeforeApiCall: Long = entityManager.createQuery( - "SELECT COUNT(c) FROM CanceledPaymentEntity c", - Long::class.java + "SELECT COUNT(c) FROM CanceledPaymentEntity c", + Long::class.java ).singleResult Given { @@ -384,8 +386,8 @@ class ReservationControllerTest( } val canceledPaymentSizeAfterApiCall: Long = entityManager.createQuery( - "SELECT COUNT(c) FROM CanceledPaymentEntity c", - Long::class.java + "SELECT COUNT(c) FROM CanceledPaymentEntity c", + Long::class.java ).singleResult canceledPaymentSizeAfterApiCall shouldBe canceledPaymentSizeBeforeApiCall + 1L @@ -397,10 +399,10 @@ class ReservationControllerTest( val member = login(MemberFixture.create(role = Role.ADMIN)) val adminRequest: AdminReservationCreateRequest = createRequest().let { AdminReservationCreateRequest( - date = it.date, - themeId = it.themeId, - timeId = it.timeId, - memberId = member.id!!, + date = it.date, + themeId = it.themeId, + timeId = it.timeId, + memberId = member.id!!, ) } @@ -425,6 +427,7 @@ class ReservationControllerTest( test("관리자가 아니면 조회할 수 없다.") { login(MemberFixture.create(role = Role.MEMBER)) + val expectedError = AuthErrorCode.ACCESS_DENIED Given { port(port) @@ -432,14 +435,15 @@ class ReservationControllerTest( }.When { get("/reservations/waiting") }.Then { - header(HttpHeaders.CONTENT_TYPE, containsString(MediaType.TEXT_HTML_VALUE)) + statusCode(expectedError.httpStatus.value()) + body("code", equalTo(expectedError.errorCode)) } } test("대기 중인 예약 목록을 조회한다.") { login(MemberFixture.create(role = Role.ADMIN)) val expected = reservations.values.flatten() - .count { it.reservationStatus == ReservationStatus.WAITING } + .count { it.reservationStatus == ReservationStatus.WAITING } Given { port(port) @@ -458,9 +462,9 @@ class ReservationControllerTest( val member = login(MemberFixture.create(role = Role.MEMBER)) val waitingCreateRequest: WaitingCreateRequest = createRequest().let { WaitingCreateRequest( - date = it.date, - themeId = it.themeId, - timeId = it.timeId + date = it.date, + themeId = it.themeId, + timeId = it.timeId ) } @@ -483,11 +487,11 @@ class ReservationControllerTest( transactionTemplate.executeWithoutResult { val reservation = ReservationFixture.create( - date = reservationRequest.date, - theme = entityManager.find(ThemeEntity::class.java, reservationRequest.themeId), - time = entityManager.find(TimeEntity::class.java, reservationRequest.timeId), - member = member, - status = ReservationStatus.WAITING + date = reservationRequest.date, + theme = entityManager.find(ThemeEntity::class.java, reservationRequest.themeId), + time = entityManager.find(TimeEntity::class.java, reservationRequest.timeId), + member = member, + status = ReservationStatus.WAITING ) entityManager.persist(reservation) entityManager.flush() @@ -496,9 +500,9 @@ class ReservationControllerTest( // 이미 예약된 시간, 테마로 대기 예약 요청 val waitingCreateRequest = WaitingCreateRequest( - date = reservationRequest.date, - themeId = reservationRequest.themeId, - timeId = reservationRequest.timeId + date = reservationRequest.date, + themeId = reservationRequest.themeId, + timeId = reservationRequest.timeId ) val expectedError = ReservationErrorCode.ALREADY_RESERVE @@ -524,8 +528,8 @@ class ReservationControllerTest( test("대기 중인 예약을 취소한다.") { val member = login(MemberFixture.create(role = Role.MEMBER)) val waiting: ReservationEntity = createSingleReservation( - member = member, - status = ReservationStatus.WAITING + member = member, + status = ReservationStatus.WAITING ) Given { @@ -538,8 +542,8 @@ class ReservationControllerTest( transactionTemplate.executeWithoutResult { _ -> entityManager.find( - ReservationEntity::class.java, - waiting.id + ReservationEntity::class.java, + waiting.id ) shouldBe null } } @@ -547,8 +551,8 @@ class ReservationControllerTest( test("이미 확정된 예약을 삭제하면 예외 응답") { val member = login(MemberFixture.create(role = Role.MEMBER)) val reservation: ReservationEntity = createSingleReservation( - member = member, - status = ReservationStatus.CONFIRMED_PAYMENT_REQUIRED + member = member, + status = ReservationStatus.CONFIRMED_PAYMENT_REQUIRED ) val expectedError = ReservationErrorCode.ALREADY_CONFIRMED @@ -566,22 +570,22 @@ class ReservationControllerTest( context("POST /reservations/waiting/{id}/confirm") { test("관리자만 승인할 수 있다.") { login(MemberFixture.create(role = Role.MEMBER)) - + val expectedError = AuthErrorCode.ACCESS_DENIED Given { port(port) }.When { post("/reservations/waiting/1/confirm") }.Then { - statusCode(302) - header(HttpHeaders.LOCATION, containsString("/login")) + statusCode(expectedError.httpStatus.value()) + body("code", equalTo(expectedError.errorCode)) } } test("대기 예약을 승인하면 결제 대기 상태로 변경") { val member = login(MemberFixture.create(role = Role.ADMIN)) val reservation = createSingleReservation( - member = member, - status = ReservationStatus.WAITING + member = member, + status = ReservationStatus.WAITING ) Given { @@ -594,8 +598,8 @@ class ReservationControllerTest( transactionTemplate.executeWithoutResult { _ -> entityManager.find( - ReservationEntity::class.java, - reservation.id + ReservationEntity::class.java, + reservation.id )?.also { it.reservationStatus shouldBe ReservationStatus.CONFIRMED_PAYMENT_REQUIRED } ?: throw AssertionError("Reservation not found") @@ -605,8 +609,8 @@ class ReservationControllerTest( test("다른 확정된 예약을 승인하면 예외 응답") { val admin = login(MemberFixture.create(role = Role.ADMIN)) val alreadyReserved = createSingleReservation( - member = admin, - status = ReservationStatus.CONFIRMED + member = admin, + status = ReservationStatus.CONFIRMED ) val member = MemberFixture.create(account = "account", role = Role.MEMBER).also { it -> @@ -615,11 +619,11 @@ class ReservationControllerTest( } } val waiting = ReservationFixture.create( - date = alreadyReserved.date, - time = alreadyReserved.time, - theme = alreadyReserved.theme, - member = member, - status = ReservationStatus.WAITING + date = alreadyReserved.date, + time = alreadyReserved.time, + theme = alreadyReserved.theme, + member = member, + status = ReservationStatus.WAITING ).also { transactionTemplate.executeWithoutResult { _ -> entityManager.persist(it) @@ -642,22 +646,23 @@ class ReservationControllerTest( context("POST /reservations/waiting/{id}/reject") { test("관리자만 거절할 수 있다.") { login(MemberFixture.create(role = Role.MEMBER)) + val expectedError = AuthErrorCode.ACCESS_DENIED Given { port(port) }.When { post("/reservations/waiting/1/reject") }.Then { - statusCode(302) - header(HttpHeaders.LOCATION, containsString("/login")) + statusCode(expectedError.httpStatus.value()) + body("code", equalTo(expectedError.errorCode)) } } test("거절된 예약은 삭제된다.") { val member = login(MemberFixture.create(role = Role.ADMIN)) val reservation = createSingleReservation( - member = member, - status = ReservationStatus.WAITING + member = member, + status = ReservationStatus.WAITING ) Given { @@ -670,8 +675,8 @@ class ReservationControllerTest( transactionTemplate.executeWithoutResult { _ -> entityManager.find( - ReservationEntity::class.java, - reservation.id + ReservationEntity::class.java, + reservation.id ) shouldBe null } } @@ -679,18 +684,18 @@ class ReservationControllerTest( } fun createSingleReservation( - date: LocalDate = LocalDate.now().plusDays(1), - time: LocalTime = LocalTime.now(), - themeName: String = "Default Theme", - member: MemberEntity = MemberFixture.create(role = Role.MEMBER), - status: ReservationStatus = ReservationStatus.CONFIRMED_PAYMENT_REQUIRED + date: LocalDate = LocalDate.now().plusDays(1), + time: LocalTime = LocalTime.now(), + themeName: String = "Default Theme", + member: MemberEntity = MemberFixture.create(role = Role.MEMBER), + status: ReservationStatus = ReservationStatus.CONFIRMED_PAYMENT_REQUIRED ): ReservationEntity { return ReservationFixture.create( - date = date, - theme = ThemeFixture.create(name = themeName), - time = TimeFixture.create(startAt = time), - member = member, - status = status + date = date, + theme = ThemeFixture.create(name = themeName), + time = TimeFixture.create(startAt = time), + member = member, + status = status ).also { it -> transactionTemplate.execute { _ -> if (member.id == null) { @@ -708,8 +713,8 @@ class ReservationControllerTest( fun createDummyReservations(): MutableMap> { val reservations: MutableMap> = mutableMapOf() val members: List = listOf( - MemberFixture.create(role = Role.MEMBER), - MemberFixture.create(role = Role.MEMBER) + MemberFixture.create(role = Role.MEMBER), + MemberFixture.create(role = Role.MEMBER) ) transactionTemplate.executeWithoutResult { @@ -728,11 +733,11 @@ class ReservationControllerTest( entityManager.persist(time) val reservation = ReservationFixture.create( - date = LocalDate.now().plusDays(index.toLong()), - theme = theme, - time = time, - member = members[index % members.size], - status = ReservationStatus.CONFIRMED + date = LocalDate.now().plusDays(index.toLong()), + theme = theme, + time = time, + member = members[index % members.size], + status = ReservationStatus.CONFIRMED ) entityManager.persist(reservation) reservations.getOrPut(reservation.member) { mutableListOf() }.add(reservation) @@ -745,8 +750,8 @@ class ReservationControllerTest( } fun createRequest( - theme: ThemeEntity = ThemeFixture.create(), - time: TimeEntity = TimeFixture.create(), + theme: ThemeEntity = ThemeFixture.create(), + time: TimeEntity = TimeFixture.create(), ): ReservationCreateWithPaymentRequest { lateinit var reservationCreateWithPaymentRequest: ReservationCreateWithPaymentRequest @@ -755,8 +760,8 @@ class ReservationControllerTest( entityManager.persist(time) reservationCreateWithPaymentRequest = ReservationFixture.createRequest( - themeId = theme.id!!, - timeId = time.id!!, + themeId = theme.id!!, + timeId = time.id!!, ) entityManager.flush() diff --git a/src/test/kotlin/roomescape/theme/web/ThemeControllerTest.kt b/src/test/kotlin/roomescape/theme/web/ThemeControllerTest.kt index eb959887..c8fb56d6 100644 --- a/src/test/kotlin/roomescape/theme/web/ThemeControllerTest.kt +++ b/src/test/kotlin/roomescape/theme/web/ThemeControllerTest.kt @@ -8,6 +8,7 @@ import io.kotest.matchers.shouldBe import io.mockk.every import io.mockk.just import io.mockk.runs +import org.hamcrest.Matchers.equalTo import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest import org.springframework.http.MediaType import org.springframework.test.web.servlet.MockMvc @@ -34,15 +35,15 @@ class ThemeControllerTest(mockMvc: MockMvc) : RoomescapeApiTest() { When("로그인 상태가 아니라면") { doNotLogin() - Then("로그인 페이지로 이동한다.") { + Then("에러 응답을 받는다.") { + val expectedError = AuthErrorCode.INVALID_TOKEN runGetTest( - mockMvc = mockMvc, - endpoint = endpoint, + mockMvc = mockMvc, + endpoint = endpoint, ) { - status { is3xxRedirection() } - header { - string("Location", "/login") - } + status { isEqualTo(expectedError.httpStatus.value()) } + }.andExpect { + jsonPath("$.code") { value(expectedError.errorCode) } } } } @@ -54,14 +55,14 @@ class ThemeControllerTest(mockMvc: MockMvc) : RoomescapeApiTest() { every { themeRepository.findAll() } returns listOf( - ThemeFixture.create(id = 1, name = "theme1"), - ThemeFixture.create(id = 2, name = "theme2"), - ThemeFixture.create(id = 3, name = "theme3") + ThemeFixture.create(id = 1, name = "theme1"), + ThemeFixture.create(id = 2, name = "theme2"), + ThemeFixture.create(id = 3, name = "theme3") ) val response: ThemeRetrieveListResponse = runGetTest( - mockMvc = mockMvc, - endpoint = endpoint, + mockMvc = mockMvc, + endpoint = endpoint, ) { status { isOk() } content { @@ -80,37 +81,37 @@ class ThemeControllerTest(mockMvc: MockMvc) : RoomescapeApiTest() { Given("테마를 추가할 때") { val endpoint = "/themes" val request = ThemeCreateRequest( - name = "theme1", - description = "description1", - thumbnail = "http://example.com/thumbnail1.jpg" + name = "theme1", + description = "description1", + thumbnail = "http://example.com/thumbnail1.jpg" ) When("로그인 상태가 아니라면") { doNotLogin() - Then("로그인 페이지로 이동한다.") { + Then("에러 응답을 받는다.") { + val expectedError = AuthErrorCode.INVALID_TOKEN runPostTest( - mockMvc = mockMvc, - endpoint = endpoint, - body = request, + mockMvc = mockMvc, + endpoint = endpoint, + body = request, ) { - status { is3xxRedirection() } - header { - string("Location", "/login") - } + status { isEqualTo(expectedError.httpStatus.value()) } + jsonPath("$.code", equalTo(expectedError.errorCode)) } } } When("관리자가 아닌 회원은") { loginAsUser() - Then("로그인 페이지로 이동한다.") { + Then("에러 응답을 받는다.") { + val expectedError = AuthErrorCode.ACCESS_DENIED runPostTest( - mockMvc = mockMvc, - endpoint = endpoint, - body = request, + mockMvc = mockMvc, + endpoint = endpoint, + body = request, ) { - status { is3xxRedirection() } - jsonPath("$.code") { value(AuthErrorCode.ACCESS_DENIED.errorCode) } + status { isEqualTo(expectedError.httpStatus.value()) } + jsonPath("$.code") { value(expectedError.errorCode) } } } } @@ -120,15 +121,15 @@ class ThemeControllerTest(mockMvc: MockMvc) : RoomescapeApiTest() { val expectedError = ThemeErrorCode.THEME_NAME_DUPLICATED - Then("에러 응답.") { + Then("에러 응답을 받는다.") { every { themeRepository.existsByName(request.name) } returns true runPostTest( - mockMvc = mockMvc, - endpoint = endpoint, - body = request, + mockMvc = mockMvc, + endpoint = endpoint, + body = request, ) { status { isEqualTo(expectedError.httpStatus.value()) } jsonPath("$.code") { value(expectedError.errorCode) } @@ -142,16 +143,16 @@ class ThemeControllerTest(mockMvc: MockMvc) : RoomescapeApiTest() { } val request = ThemeCreateRequest( - name = "theme1", - description = "description1", - thumbnail = "http://example.com/thumbnail1.jpg" + name = "theme1", + description = "description1", + thumbnail = "http://example.com/thumbnail1.jpg" ) fun runTest(request: ThemeCreateRequest) { runPostTest( - mockMvc = mockMvc, - endpoint = endpoint, - body = request, + mockMvc = mockMvc, + endpoint = endpoint, + body = request, ) { status { isBadRequest() } } @@ -192,26 +193,26 @@ class ThemeControllerTest(mockMvc: MockMvc) : RoomescapeApiTest() { loginAsAdmin() val theme = ThemeFixture.create( - id = 1, - name = request.name, - description = request.description, - thumbnail = request.thumbnail + id = 1, + name = request.name, + description = request.description, + thumbnail = request.thumbnail ) every { themeService.createTheme(request) } returns ThemeRetrieveResponse( - id = theme.id!!, - name = theme.name, - description = theme.description, - thumbnail = theme.thumbnail + id = theme.id!!, + name = theme.name, + description = theme.description, + thumbnail = theme.thumbnail ) Then("201 응답을 받는다.") { runPostTest( - mockMvc = mockMvc, - endpoint = endpoint, - body = request, + mockMvc = mockMvc, + endpoint = endpoint, + body = request, ) { status { isCreated() } header { @@ -232,28 +233,28 @@ class ThemeControllerTest(mockMvc: MockMvc) : RoomescapeApiTest() { When("로그인 상태가 아니라면") { doNotLogin() - Then("로그인 페이지로 이동한다.") { + Then("에러 응답을 받는다.") { + val expectedError = AuthErrorCode.INVALID_TOKEN runDeleteTest( - mockMvc = mockMvc, - endpoint = endpoint, + mockMvc = mockMvc, + endpoint = endpoint, ) { - status { is3xxRedirection() } - header { - string("Location", "/login") - } + status { isEqualTo(expectedError.httpStatus.value()) } + jsonPath("$.code", equalTo(expectedError.errorCode)) } } } When("관리자가 아닌 회원은") { loginAsUser() - Then("로그인 페이지로 이동한다.") { + Then("에러 응답을 받는다.") { + val expectedError = AuthErrorCode.ACCESS_DENIED runDeleteTest( - mockMvc = mockMvc, - endpoint = endpoint, + mockMvc = mockMvc, + endpoint = endpoint, ) { - status { is3xxRedirection() } - jsonPath("$.code") { value(AuthErrorCode.ACCESS_DENIED.errorCode) } + status { isEqualTo(expectedError.httpStatus.value()) } + jsonPath("$.code", equalTo(expectedError.errorCode)) } } } @@ -262,14 +263,14 @@ class ThemeControllerTest(mockMvc: MockMvc) : RoomescapeApiTest() { loginAsAdmin() val expectedError = ThemeErrorCode.THEME_ALREADY_RESERVED - Then("에러 응답") { + Then("에러 응답을 받는다.") { every { themeRepository.isReservedTheme(themeId) } returns true runDeleteTest( - mockMvc = mockMvc, - endpoint = endpoint, + mockMvc = mockMvc, + endpoint = endpoint, ) { status { isEqualTo(expectedError.httpStatus.value()) } jsonPath("$.code") { value(expectedError.errorCode) } @@ -290,8 +291,8 @@ class ThemeControllerTest(mockMvc: MockMvc) : RoomescapeApiTest() { Then("204 응답을 받는다.") { runDeleteTest( - mockMvc = mockMvc, - endpoint = endpoint, + mockMvc = mockMvc, + endpoint = endpoint, ) { status { isNoContent() } } diff --git a/src/test/kotlin/roomescape/time/web/TimeControllerTest.kt b/src/test/kotlin/roomescape/time/web/TimeControllerTest.kt index 19ae8465..b9ef49a9 100644 --- a/src/test/kotlin/roomescape/time/web/TimeControllerTest.kt +++ b/src/test/kotlin/roomescape/time/web/TimeControllerTest.kt @@ -6,11 +6,13 @@ import io.kotest.assertions.assertSoftly import io.kotest.matchers.collections.shouldHaveSize import io.kotest.matchers.shouldBe import io.mockk.every +import org.hamcrest.Matchers.equalTo import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest import org.springframework.context.annotation.Import import org.springframework.data.repository.findByIdOrNull import org.springframework.http.MediaType import org.springframework.test.web.servlet.MockMvc +import roomescape.auth.exception.AuthErrorCode import roomescape.common.config.JacksonConfig import roomescape.reservation.infrastructure.persistence.ReservationRepository import roomescape.time.business.TimeService @@ -27,7 +29,7 @@ import java.time.LocalTime @WebMvcTest(TimeController::class) @Import(JacksonConfig::class) class TimeControllerTest( - val mockMvc: MockMvc, + val mockMvc: MockMvc, ) : RoomescapeApiTest() { @SpykBean @@ -52,13 +54,13 @@ class TimeControllerTest( every { timeRepository.findAll() } returns listOf( - TimeFixture.create(id = 1L), - TimeFixture.create(id = 2L) + TimeFixture.create(id = 1L), + TimeFixture.create(id = 2L) ) runGetTest( - mockMvc = mockMvc, - endpoint = endpoint, + mockMvc = mockMvc, + endpoint = endpoint, ) { status { isOk() } content { @@ -72,14 +74,19 @@ class TimeControllerTest( When("관리자가 아닌 경우") { loginAsUser() + val expectedError = AuthErrorCode.ACCESS_DENIED - Then("로그인 페이지로 이동") { + Then("에러 응답을 받는다.") { runGetTest( - mockMvc = mockMvc, - endpoint = endpoint, + mockMvc = mockMvc, + endpoint = endpoint, ) { - status { is3xxRedirection() } - header { string("Location", "/login") } + status { isEqualTo(expectedError.httpStatus.value()) } + }.andExpect { + content { + contentType(MediaType.APPLICATION_JSON) + jsonPath("$.code") { value(expectedError.errorCode) } + } } } } @@ -97,13 +104,13 @@ class TimeControllerTest( Then("시간 형식이 HH:mm이 아니거나, 범위를 벗어나면 400 응답") { listOf( - "{\"startAt\": \"23:30:30\"}", - "{\"startAt\": \"24:59\"}", + "{\"startAt\": \"23:30:30\"}", + "{\"startAt\": \"24:59\"}", ).forEach { runPostTest( - mockMvc = mockMvc, - endpoint = endpoint, - body = it, + mockMvc = mockMvc, + endpoint = endpoint, + body = it, ) { status { isBadRequest() } } @@ -116,9 +123,9 @@ class TimeControllerTest( } returns TimeCreateResponse(id = 1, startAt = time) runPostTest( - mockMvc = mockMvc, - endpoint = endpoint, - body = request, + mockMvc = mockMvc, + endpoint = endpoint, + body = request, ) { status { isCreated() } content { @@ -136,9 +143,9 @@ class TimeControllerTest( } returns true runPostTest( - mockMvc = mockMvc, - endpoint = endpoint, - body = request, + mockMvc = mockMvc, + endpoint = endpoint, + body = request, ) { status { isEqualTo(expectedError.httpStatus.value()) } content { @@ -152,14 +159,15 @@ class TimeControllerTest( When("관리자가 아닌 경우") { loginAsUser() - Then("로그인 페이지로 이동") { + Then("에러 응답을 받는다.") { + val expectedError = AuthErrorCode.ACCESS_DENIED runPostTest( - mockMvc = mockMvc, - endpoint = endpoint, - body = TimeFixture.create(), + mockMvc = mockMvc, + endpoint = endpoint, + body = TimeFixture.create(), ) { - status { is3xxRedirection() } - header { string("Location", "/login") } + status { isEqualTo(expectedError.httpStatus.value()) } + jsonPath("$.code", equalTo(expectedError.errorCode)) } } } @@ -179,8 +187,8 @@ class TimeControllerTest( } returns Unit runDeleteTest( - mockMvc = mockMvc, - endpoint = endpoint, + mockMvc = mockMvc, + endpoint = endpoint, ) { status { isNoContent() } } @@ -194,8 +202,8 @@ class TimeControllerTest( } returns null runDeleteTest( - mockMvc = mockMvc, - endpoint = "/times/$id", + mockMvc = mockMvc, + endpoint = "/times/$id", ) { status { isEqualTo(expectedError.httpStatus.value()) } content { @@ -217,8 +225,8 @@ class TimeControllerTest( } returns listOf(ReservationFixture.create()) runDeleteTest( - mockMvc = mockMvc, - endpoint = "/times/$id", + mockMvc = mockMvc, + endpoint = "/times/$id", ) { status { isEqualTo(expectedError.httpStatus.value()) } content { @@ -232,13 +240,14 @@ class TimeControllerTest( When("관리자가 아닌 경우") { loginAsUser() - Then("로그인 페이지로 이동") { + Then("에러 응답을 받는다.") { + val expectedError = AuthErrorCode.ACCESS_DENIED runDeleteTest( - mockMvc = mockMvc, - endpoint = endpoint, + mockMvc = mockMvc, + endpoint = endpoint, ) { - status { is3xxRedirection() } - header { string("Location", "/login") } + status { isEqualTo(expectedError.httpStatus.value()) } + jsonPath("$.code", equalTo(expectedError.errorCode)) } } } @@ -252,8 +261,8 @@ class TimeControllerTest( When("저장된 예약 시간이 있으면") { val times: List = listOf( - TimeFixture.create(id = 1L, startAt = LocalTime.of(10, 0)), - TimeFixture.create(id = 2L, startAt = LocalTime.of(11, 0)) + TimeFixture.create(id = 1L, startAt = LocalTime.of(10, 0)), + TimeFixture.create(id = 2L, startAt = LocalTime.of(11, 0)) ) every { @@ -265,17 +274,17 @@ class TimeControllerTest( every { reservationRepository.findByDateAndThemeId(date, themeId) } returns listOf( - ReservationFixture.create( - id = 1L, - date = date, - theme = ThemeFixture.create(id = themeId), - time = times[0] - ) + ReservationFixture.create( + id = 1L, + date = date, + theme = ThemeFixture.create(id = themeId), + time = times[0] + ) ) val response = runGetTest( - mockMvc = mockMvc, - endpoint = "/times/search?date=$date&themeId=$themeId", + mockMvc = mockMvc, + endpoint = "/times/search?date=$date&themeId=$themeId", ) { status { isOk() } content {