package roomescape.auth.web import com.ninjasquad.springmockk.SpykBean import io.mockk.every import org.hamcrest.Matchers.containsString import org.hamcrest.Matchers.equalTo import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest import org.springframework.data.repository.findByIdOrNull import org.springframework.test.web.servlet.MockMvc import roomescape.auth.service.AuthService import roomescape.common.exception.ErrorType import roomescape.util.MemberFixture import roomescape.util.RoomescapeApiTest @WebMvcTest(controllers = [AuthController::class]) class AuthControllerTest( @Autowired mockMvc: MockMvc ) : RoomescapeApiTest() { @SpykBean private lateinit var authService: AuthService val userRequest: LoginRequest = MemberFixture.userLoginRequest() init { Given("로그인 요청을 보낼 때") { val endpoint = "/login" When("로그인에 성공하면") { val expectedToken = "expectedToken" every { memberRepository.findByEmailAndPassword(userRequest.email, userRequest.password) } returns user every { jwtHandler.createToken(user.id!!) } returns expectedToken Then("토큰을 쿠키에 담아 응답한다") { runPostTest( mockMvc = mockMvc, endpoint = endpoint, body = userRequest, ) { status { isOk() } header { string("Set-Cookie", containsString("accessToken=$expectedToken")) string("Set-Cookie", containsString("Max-Age=1800")) string("Set-Cookie", containsString("HttpOnly")) string("Set-Cookie", containsString("Secure")) } } } } When("회원을 찾지 못하면") { every { memberRepository.findByEmailAndPassword(userRequest.email, userRequest.password) } returns null Then("400 에러를 응답한다") { runPostTest( mockMvc = mockMvc, endpoint = endpoint, body = userRequest, ) { status { isBadRequest() } jsonPath("$.errorType", equalTo(ErrorType.MEMBER_NOT_FOUND.name)) } } } When("잘못된 요청을 보내면 400 에러를 응답한다.") { Then("이메일 형식이 잘못된 경우") { val invalidRequest: LoginRequest = userRequest.copy(email = "invalid") runPostTest( mockMvc = mockMvc, endpoint = endpoint, body = invalidRequest, ) { status { isBadRequest() } jsonPath("$.message", containsString("이메일 형식이 일치하지 않습니다.")) } } Then("비밀번호가 공백인 경우") { val invalidRequest = userRequest.copy(password = " ") runPostTest( mockMvc = mockMvc, endpoint = endpoint, body = invalidRequest, ) { status { isBadRequest() } jsonPath("$.message", containsString("비밀번호는 공백일 수 없습니다.")) } } } } Given("로그인 상태를 확인할 때") { val endpoint = "/login/check" When("로그인된 회원의 ID로 요청하면") { loginAsUser() Then("회원의 이름을 응답한다") { runGetTest( mockMvc = mockMvc, endpoint = endpoint, ) { status { isOk() } jsonPath("$.data.name", equalTo(user.name)) } } } When("토큰은 있지만 회원을 찾을 수 없으면") { val invalidMemberId: Long = -1L every { jwtHandler.getMemberIdFromToken(any()) } returns invalidMemberId every { memberRepository.findByIdOrNull(invalidMemberId) } returns null Then("400 에러를 응답한다.") { runGetTest( mockMvc = mockMvc, endpoint = endpoint, ) { status { isBadRequest() } jsonPath("$.errorType", equalTo(ErrorType.MEMBER_NOT_FOUND.name)) } } } } Given("로그아웃 요청을 보낼 때") { val endpoint = "/logout" When("로그인 상태가 아니라면") { doNotLogin() Then("로그인 페이지로 이동한다.") { runPostTest( mockMvc = mockMvc, endpoint = endpoint, ) { status { is3xxRedirection() } header { string("Location", "/login") } } } } When("로그인 상태라면") { loginAsUser() Then("토큰의 존재 여부와 무관하게 토큰을 만료시킨다.") { runPostTest( mockMvc = mockMvc, endpoint = endpoint, ) { status { isOk() } header { string("Set-Cookie", containsString("Max-Age=0")) string("Set-Cookie", containsString("accessToken=")) string("Set-Cookie", containsString("Path=/")) string("Set-Cookie", containsString("HttpOnly")) string("Set-Cookie", containsString("Secure")) } } } } } } }