refactor: Interceptor에서 redirect 처리 제거 및 테스트 수정

This commit is contained in:
이상진 2025-07-25 18:09:13 +09:00
parent 41def25709
commit ede2e7f624
6 changed files with 272 additions and 259 deletions

View File

@ -31,7 +31,6 @@ class AuthInterceptor(
val member: MemberEntity = findMember(request, response) val member: MemberEntity = findMember(request, response)
if (admin != null && !member.isAdmin()) { if (admin != null && !member.isAdmin()) {
response.sendRedirect("/login")
throw AuthException(AuthErrorCode.ACCESS_DENIED) throw AuthException(AuthErrorCode.ACCESS_DENIED)
} }
@ -45,7 +44,6 @@ class AuthInterceptor(
return memberService.findById(memberId) return memberService.findById(memberId)
} catch (e: Exception) { } catch (e: Exception) {
response.sendRedirect("/login")
throw e throw e
} }
} }

View File

@ -61,7 +61,7 @@ class AuthControllerTest(
memberRepository.findByEmailAndPassword(userRequest.email, userRequest.password) memberRepository.findByEmailAndPassword(userRequest.email, userRequest.password)
} returns null } returns null
Then("에러 응답") { Then("에러 응답을 받는다.") {
val expectedError = AuthErrorCode.LOGIN_FAILED val expectedError = AuthErrorCode.LOGIN_FAILED
runPostTest( runPostTest(
mockMvc = mockMvc, mockMvc = mockMvc,
@ -118,7 +118,7 @@ class AuthControllerTest(
every { jwtHandler.getMemberIdFromToken(any()) } returns invalidMemberId every { jwtHandler.getMemberIdFromToken(any()) } returns invalidMemberId
every { memberRepository.findByIdOrNull(invalidMemberId) } returns null every { memberRepository.findByIdOrNull(invalidMemberId) } returns null
Then("에러 응답.") { Then("에러 응답을 받는다.") {
val expectedError = AuthErrorCode.UNIDENTIFIABLE_MEMBER val expectedError = AuthErrorCode.UNIDENTIFIABLE_MEMBER
runGetTest( runGetTest(
mockMvc = mockMvc, mockMvc = mockMvc,
@ -137,15 +137,14 @@ class AuthControllerTest(
When("로그인 상태가 아니라면") { When("로그인 상태가 아니라면") {
doNotLogin() doNotLogin()
Then("로그인 페이지로 이동한다.") { Then("에러 응답을 받는다.") {
val expectedError = AuthErrorCode.INVALID_TOKEN
runPostTest( runPostTest(
mockMvc = mockMvc, mockMvc = mockMvc,
endpoint = endpoint, endpoint = endpoint,
) { ) {
status { is3xxRedirection() } status { isEqualTo(expectedError.httpStatus.value()) }
header { jsonPath("$.code", equalTo(expectedError.errorCode))
string("Location", "/login")
}
} }
} }
} }

View File

@ -7,6 +7,7 @@ import io.mockk.every
import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
import org.springframework.test.web.servlet.MockMvc import org.springframework.test.web.servlet.MockMvc
import roomescape.auth.exception.AuthErrorCode
import roomescape.member.web.MemberController import roomescape.member.web.MemberController
import roomescape.member.web.MemberRetrieveListResponse import roomescape.member.web.MemberRetrieveListResponse
import roomescape.util.MemberFixture import roomescape.util.MemberFixture
@ -51,32 +52,32 @@ class MemberControllerTest(
} }
} }
`when`("관리자가 아니면 로그인 페이지로 이동한다.") { `when`("관리자가 아니면 에러 응답을 받는다.") {
then("비회원") { then("비회원") {
doNotLogin() doNotLogin()
val expectedError = AuthErrorCode.INVALID_TOKEN
runGetTest( runGetTest(
mockMvc = mockMvc, mockMvc = mockMvc,
endpoint = endpoint, endpoint = endpoint,
) { ) {
status { is3xxRedirection() } status { isEqualTo(expectedError.httpStatus.value()) }
header { }.andExpect {
string("Location", "/login") jsonPath("$.code") { value(expectedError.errorCode) }
}
} }
} }
then("일반 회원") { then("일반 회원") {
loginAsUser() loginAsUser()
val expectedError = AuthErrorCode.ACCESS_DENIED
runGetTest( runGetTest(
mockMvc = mockMvc, mockMvc = mockMvc,
endpoint = endpoint, endpoint = endpoint,
) { ) {
status { is3xxRedirection() } status { isEqualTo(expectedError.httpStatus.value()) }
header { }.andExpect {
string("Location", "/login") jsonPath("$.code") { value(expectedError.errorCode) }
}
} }
} }
} }

View File

@ -9,14 +9,13 @@ import io.restassured.module.kotlin.extensions.Given
import io.restassured.module.kotlin.extensions.Then import io.restassured.module.kotlin.extensions.Then
import io.restassured.module.kotlin.extensions.When import io.restassured.module.kotlin.extensions.When
import jakarta.persistence.EntityManager import jakarta.persistence.EntityManager
import org.hamcrest.Matchers.containsString
import org.hamcrest.Matchers.equalTo import org.hamcrest.Matchers.equalTo
import org.springframework.boot.test.context.SpringBootTest import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.test.web.server.LocalServerPort import org.springframework.boot.test.web.server.LocalServerPort
import org.springframework.http.HttpHeaders
import org.springframework.http.HttpStatus import org.springframework.http.HttpStatus
import org.springframework.http.MediaType import org.springframework.http.MediaType
import org.springframework.transaction.support.TransactionTemplate import org.springframework.transaction.support.TransactionTemplate
import roomescape.auth.exception.AuthErrorCode
import roomescape.auth.infrastructure.jwt.JwtHandler import roomescape.auth.infrastructure.jwt.JwtHandler
import roomescape.auth.web.support.MemberIdResolver import roomescape.auth.web.support.MemberIdResolver
import roomescape.member.business.MemberService import roomescape.member.business.MemberService
@ -108,7 +107,7 @@ class ReservationControllerTest(
} }
} }
test("결제 완료 후 예약 / 결제 정보 저장 과정에서 에러 발생시 결제 취소 후 에러 응답") { test("결제 완료 후 예약 / 결제 정보 저장 과정에서 에러 발생시 결제 취소 후 에러 응답을 받는다.") {
val reservationRequest = createRequest() val reservationRequest = createRequest()
val paymentApproveResponse = PaymentFixture.createApproveResponse().copy( val paymentApproveResponse = PaymentFixture.createApproveResponse().copy(
paymentKey = reservationRequest.paymentKey, paymentKey = reservationRequest.paymentKey,
@ -203,6 +202,7 @@ class ReservationControllerTest(
test("관리자만 검색할 수 있다.") { test("관리자만 검색할 수 있다.") {
login(reservations.keys.first()) login(reservations.keys.first())
val expectedError = AuthErrorCode.ACCESS_DENIED
Given { Given {
port(port) port(port)
@ -210,7 +210,8 @@ class ReservationControllerTest(
}.When { }.When {
get("/reservations/search") get("/reservations/search")
}.Then { }.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("관리자만 예약을 삭제할 수 있다.") { test("관리자만 예약을 삭제할 수 있다.") {
login(MemberFixture.create(role = Role.MEMBER)) login(MemberFixture.create(role = Role.MEMBER))
val reservation: ReservationEntity = reservations.values.flatten().first() val reservation: ReservationEntity = reservations.values.flatten().first()
val expectedError = AuthErrorCode.ACCESS_DENIED
Given { Given {
port(port) port(port)
}.When { }.When {
delete("/reservations/${reservation.id}") delete("/reservations/${reservation.id}")
}.Then { }.Then {
statusCode(302) statusCode(expectedError.httpStatus.value())
header(HttpHeaders.LOCATION, containsString("/login")) body("code", equalTo(expectedError.errorCode))
} }
} }
@ -425,6 +427,7 @@ class ReservationControllerTest(
test("관리자가 아니면 조회할 수 없다.") { test("관리자가 아니면 조회할 수 없다.") {
login(MemberFixture.create(role = Role.MEMBER)) login(MemberFixture.create(role = Role.MEMBER))
val expectedError = AuthErrorCode.ACCESS_DENIED
Given { Given {
port(port) port(port)
@ -432,7 +435,8 @@ class ReservationControllerTest(
}.When { }.When {
get("/reservations/waiting") get("/reservations/waiting")
}.Then { }.Then {
header(HttpHeaders.CONTENT_TYPE, containsString(MediaType.TEXT_HTML_VALUE)) statusCode(expectedError.httpStatus.value())
body("code", equalTo(expectedError.errorCode))
} }
} }
@ -566,14 +570,14 @@ class ReservationControllerTest(
context("POST /reservations/waiting/{id}/confirm") { context("POST /reservations/waiting/{id}/confirm") {
test("관리자만 승인할 수 있다.") { test("관리자만 승인할 수 있다.") {
login(MemberFixture.create(role = Role.MEMBER)) login(MemberFixture.create(role = Role.MEMBER))
val expectedError = AuthErrorCode.ACCESS_DENIED
Given { Given {
port(port) port(port)
}.When { }.When {
post("/reservations/waiting/1/confirm") post("/reservations/waiting/1/confirm")
}.Then { }.Then {
statusCode(302) statusCode(expectedError.httpStatus.value())
header(HttpHeaders.LOCATION, containsString("/login")) body("code", equalTo(expectedError.errorCode))
} }
} }
@ -642,14 +646,15 @@ class ReservationControllerTest(
context("POST /reservations/waiting/{id}/reject") { context("POST /reservations/waiting/{id}/reject") {
test("관리자만 거절할 수 있다.") { test("관리자만 거절할 수 있다.") {
login(MemberFixture.create(role = Role.MEMBER)) login(MemberFixture.create(role = Role.MEMBER))
val expectedError = AuthErrorCode.ACCESS_DENIED
Given { Given {
port(port) port(port)
}.When { }.When {
post("/reservations/waiting/1/reject") post("/reservations/waiting/1/reject")
}.Then { }.Then {
statusCode(302) statusCode(expectedError.httpStatus.value())
header(HttpHeaders.LOCATION, containsString("/login")) body("code", equalTo(expectedError.errorCode))
} }
} }

View File

@ -8,6 +8,7 @@ import io.kotest.matchers.shouldBe
import io.mockk.every import io.mockk.every
import io.mockk.just import io.mockk.just
import io.mockk.runs import io.mockk.runs
import org.hamcrest.Matchers.equalTo
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
import org.springframework.http.MediaType import org.springframework.http.MediaType
import org.springframework.test.web.servlet.MockMvc import org.springframework.test.web.servlet.MockMvc
@ -34,15 +35,15 @@ class ThemeControllerTest(mockMvc: MockMvc) : RoomescapeApiTest() {
When("로그인 상태가 아니라면") { When("로그인 상태가 아니라면") {
doNotLogin() doNotLogin()
Then("로그인 페이지로 이동한다.") { Then("에러 응답을 받는다.") {
val expectedError = AuthErrorCode.INVALID_TOKEN
runGetTest( runGetTest(
mockMvc = mockMvc, mockMvc = mockMvc,
endpoint = endpoint, endpoint = endpoint,
) { ) {
status { is3xxRedirection() } status { isEqualTo(expectedError.httpStatus.value()) }
header { }.andExpect {
string("Location", "/login") jsonPath("$.code") { value(expectedError.errorCode) }
}
} }
} }
} }
@ -87,30 +88,30 @@ class ThemeControllerTest(mockMvc: MockMvc) : RoomescapeApiTest() {
When("로그인 상태가 아니라면") { When("로그인 상태가 아니라면") {
doNotLogin() doNotLogin()
Then("로그인 페이지로 이동한다.") { Then("에러 응답을 받는다.") {
val expectedError = AuthErrorCode.INVALID_TOKEN
runPostTest( runPostTest(
mockMvc = mockMvc, mockMvc = mockMvc,
endpoint = endpoint, endpoint = endpoint,
body = request, body = request,
) { ) {
status { is3xxRedirection() } status { isEqualTo(expectedError.httpStatus.value()) }
header { jsonPath("$.code", equalTo(expectedError.errorCode))
string("Location", "/login")
}
} }
} }
} }
When("관리자가 아닌 회원은") { When("관리자가 아닌 회원은") {
loginAsUser() loginAsUser()
Then("로그인 페이지로 이동한다.") { Then("에러 응답을 받는다.") {
val expectedError = AuthErrorCode.ACCESS_DENIED
runPostTest( runPostTest(
mockMvc = mockMvc, mockMvc = mockMvc,
endpoint = endpoint, endpoint = endpoint,
body = request, body = request,
) { ) {
status { is3xxRedirection() } status { isEqualTo(expectedError.httpStatus.value()) }
jsonPath("$.code") { value(AuthErrorCode.ACCESS_DENIED.errorCode) } jsonPath("$.code") { value(expectedError.errorCode) }
} }
} }
} }
@ -120,7 +121,7 @@ class ThemeControllerTest(mockMvc: MockMvc) : RoomescapeApiTest() {
val expectedError = ThemeErrorCode.THEME_NAME_DUPLICATED val expectedError = ThemeErrorCode.THEME_NAME_DUPLICATED
Then("에러 응답.") { Then("에러 응답을 받는다.") {
every { every {
themeRepository.existsByName(request.name) themeRepository.existsByName(request.name)
} returns true } returns true
@ -232,28 +233,28 @@ class ThemeControllerTest(mockMvc: MockMvc) : RoomescapeApiTest() {
When("로그인 상태가 아니라면") { When("로그인 상태가 아니라면") {
doNotLogin() doNotLogin()
Then("로그인 페이지로 이동한다.") { Then("에러 응답을 받는다.") {
val expectedError = AuthErrorCode.INVALID_TOKEN
runDeleteTest( runDeleteTest(
mockMvc = mockMvc, mockMvc = mockMvc,
endpoint = endpoint, endpoint = endpoint,
) { ) {
status { is3xxRedirection() } status { isEqualTo(expectedError.httpStatus.value()) }
header { jsonPath("$.code", equalTo(expectedError.errorCode))
string("Location", "/login")
}
} }
} }
} }
When("관리자가 아닌 회원은") { When("관리자가 아닌 회원은") {
loginAsUser() loginAsUser()
Then("로그인 페이지로 이동한다.") { Then("에러 응답을 받는다.") {
val expectedError = AuthErrorCode.ACCESS_DENIED
runDeleteTest( runDeleteTest(
mockMvc = mockMvc, mockMvc = mockMvc,
endpoint = endpoint, endpoint = endpoint,
) { ) {
status { is3xxRedirection() } status { isEqualTo(expectedError.httpStatus.value()) }
jsonPath("$.code") { value(AuthErrorCode.ACCESS_DENIED.errorCode) } jsonPath("$.code", equalTo(expectedError.errorCode))
} }
} }
} }
@ -262,7 +263,7 @@ class ThemeControllerTest(mockMvc: MockMvc) : RoomescapeApiTest() {
loginAsAdmin() loginAsAdmin()
val expectedError = ThemeErrorCode.THEME_ALREADY_RESERVED val expectedError = ThemeErrorCode.THEME_ALREADY_RESERVED
Then("에러 응답") { Then("에러 응답을 받는다.") {
every { every {
themeRepository.isReservedTheme(themeId) themeRepository.isReservedTheme(themeId)
} returns true } returns true

View File

@ -6,11 +6,13 @@ import io.kotest.assertions.assertSoftly
import io.kotest.matchers.collections.shouldHaveSize import io.kotest.matchers.collections.shouldHaveSize
import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldBe
import io.mockk.every import io.mockk.every
import org.hamcrest.Matchers.equalTo
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
import org.springframework.context.annotation.Import import org.springframework.context.annotation.Import
import org.springframework.data.repository.findByIdOrNull import org.springframework.data.repository.findByIdOrNull
import org.springframework.http.MediaType import org.springframework.http.MediaType
import org.springframework.test.web.servlet.MockMvc import org.springframework.test.web.servlet.MockMvc
import roomescape.auth.exception.AuthErrorCode
import roomescape.common.config.JacksonConfig import roomescape.common.config.JacksonConfig
import roomescape.reservation.infrastructure.persistence.ReservationRepository import roomescape.reservation.infrastructure.persistence.ReservationRepository
import roomescape.time.business.TimeService import roomescape.time.business.TimeService
@ -72,14 +74,19 @@ class TimeControllerTest(
When("관리자가 아닌 경우") { When("관리자가 아닌 경우") {
loginAsUser() loginAsUser()
val expectedError = AuthErrorCode.ACCESS_DENIED
Then("로그인 페이지로 이동") { Then("에러 응답을 받는다.") {
runGetTest( runGetTest(
mockMvc = mockMvc, mockMvc = mockMvc,
endpoint = endpoint, endpoint = endpoint,
) { ) {
status { is3xxRedirection() } status { isEqualTo(expectedError.httpStatus.value()) }
header { string("Location", "/login") } }.andExpect {
content {
contentType(MediaType.APPLICATION_JSON)
jsonPath("$.code") { value(expectedError.errorCode) }
}
} }
} }
} }
@ -152,14 +159,15 @@ class TimeControllerTest(
When("관리자가 아닌 경우") { When("관리자가 아닌 경우") {
loginAsUser() loginAsUser()
Then("로그인 페이지로 이동") { Then("에러 응답을 받는다.") {
val expectedError = AuthErrorCode.ACCESS_DENIED
runPostTest( runPostTest(
mockMvc = mockMvc, mockMvc = mockMvc,
endpoint = endpoint, endpoint = endpoint,
body = TimeFixture.create(), body = TimeFixture.create(),
) { ) {
status { is3xxRedirection() } status { isEqualTo(expectedError.httpStatus.value()) }
header { string("Location", "/login") } jsonPath("$.code", equalTo(expectedError.errorCode))
} }
} }
} }
@ -232,13 +240,14 @@ class TimeControllerTest(
When("관리자가 아닌 경우") { When("관리자가 아닌 경우") {
loginAsUser() loginAsUser()
Then("로그인 페이지로 이동") { Then("에러 응답을 받는다.") {
val expectedError = AuthErrorCode.ACCESS_DENIED
runDeleteTest( runDeleteTest(
mockMvc = mockMvc, mockMvc = mockMvc,
endpoint = endpoint, endpoint = endpoint,
) { ) {
status { is3xxRedirection() } status { isEqualTo(expectedError.httpStatus.value()) }
header { string("Location", "/login") } jsonPath("$.code", equalTo(expectedError.errorCode))
} }
} }
} }