diff --git a/src/docs/asciidoc/auth-api.adoc b/src/docs/asciidoc/auth-api.adoc index 9c6d731..07b80df 100644 --- a/src/docs/asciidoc/auth-api.adoc +++ b/src/docs/asciidoc/auth-api.adoc @@ -8,6 +8,9 @@ include::{snippetsDir}/loginUser/1/http-request.adoc[] ==== 성공 Response include::{snippetsDir}/loginUser/1/http-response.adoc[] +==== Response Headers +include::{snippetsDir}/loginUser/1/response-headers.adoc[] + ==== Response Body Fields include::{snippetsDir}/loginUser/1/response-fields.adoc[] @@ -26,13 +29,44 @@ include::{snippetsDir}/kakaoLogin/1/http-request.adoc[] ==== 성공 Response 성공 1. 카카오 로그인 성공, 서비스에 가입된 유저인 경우 include::{snippetsDir}/kakaoLogin/1/http-response.adoc[] -include::{snippetsDir}/kakaoLogin/1/response-fields.adoc[] 성공 2. 카카오 로그인은 성공했지만, 서비스에 가입된 유저가 아닌 경우 (세션에 카카오 유저 정보를 임시로 등록하고 응답 쿠키에 SESSION 등록, 만료시간 5분) include::{snippetsDir}/kakaoLogin/2/http-response.adoc[] +==== Response Headers +성공 1. +include::{snippetsDir}/kakaoLogin/1/response-headers.adoc[] + +성공 2. +include::{snippetsDir}/kakaoLogin/2/response-headers.adoc[] + +==== Response Body Fields +성공 1. +include::{snippetsDir}/kakaoLogin/1/response-fields.adoc[] + ==== 실패 Response 실패 1. include::{snippetsDir}/kakaoLogin/3/http-response.adoc[] 실패 2. include::{snippetsDir}/kakaoLogin/4/http-response.adoc[] + +--- + +=== **3. 로그아웃** + +로그아웃 api 입니다. 요청 헤더에 Key=Cookie, Value="SESSION=" 형식의 세션 쿠키가 존재해야합니다. + +==== Request +include::{snippetsDir}/logout/1/http-request.adoc[] + +==== Request Header +include::{snippetsDir}/logout/1/request-headers.adoc[] + +==== 성공 Response +include::{snippetsDir}/logout/1/http-response.adoc[] + +==== Response Body Fields +include::{snippetsDir}/logout/1/response-fields.adoc[] + +==== 실패 Response +include::{snippetsDir}/logout/2/http-response.adoc[] diff --git a/src/main/java/com/ftm/server/web/controller/auth/LogoutController.java b/src/main/java/com/ftm/server/web/controller/auth/LogoutController.java new file mode 100644 index 0000000..5b6cd94 --- /dev/null +++ b/src/main/java/com/ftm/server/web/controller/auth/LogoutController.java @@ -0,0 +1,27 @@ +package com.ftm.server.web.controller.auth; + +import com.ftm.server.common.response.ApiResponse; +import com.ftm.server.common.response.enums.SuccessResponseCode; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpSession; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +public class LogoutController { + + @PostMapping("/api/auth/logout") + public ResponseEntity> logout(HttpServletRequest req) { + HttpSession session = req.getSession(false); + if (session != null) { + session.invalidate(); + } + + return ResponseEntity.status(HttpStatus.OK) + .body(ApiResponse.success(SuccessResponseCode.OK)); + } +} diff --git a/src/test/java/com/ftm/server/auth/KakaoLoginTest.java b/src/test/java/com/ftm/server/auth/KakaoLoginTest.java index adf48e2..2b2c376 100644 --- a/src/test/java/com/ftm/server/auth/KakaoLoginTest.java +++ b/src/test/java/com/ftm/server/auth/KakaoLoginTest.java @@ -115,7 +115,7 @@ private RestDocumentationResultHandler getDocument( MvcResult result = resultActions.andReturn(); // 세션 쿠키 수동 추가 (문서화 통과용) - result.getResponse().addHeader("Set-Cookie", "SESSION=mock-session-id; Path=/; HttpOnly"); + result.getResponse().addHeader("Set-Cookie", "SESSION=session-id; Path=/; HttpOnly"); // then resultActions @@ -124,7 +124,8 @@ private RestDocumentationResultHandler getDocument( .andDo(print()); // documentation - resultActions.andDo(getDocument(1, "SESSION 쿠키: 로그인 성공 시 발급되는 세션 쿠키 (기본 만료 시간 30분)")); + resultActions.andDo( + getDocument(1, "로그인 성공 시 발급되는 세션 쿠키 (만료 시간: 1시간, Value: SESSION=session-id 형태)")); } @Test @@ -140,7 +141,7 @@ private RestDocumentationResultHandler getDocument( MvcResult result = resultActions.andReturn(); // 세션 쿠키 수동 추가 (문서화 통과용) - result.getResponse().addHeader("Set-Cookie", "SESSION=mock-session-id; Path=/; HttpOnly"); + result.getResponse().addHeader("Set-Cookie", "SESSION=session-id; Path=/; HttpOnly"); // then resultActions @@ -149,7 +150,9 @@ private RestDocumentationResultHandler getDocument( .andDo(print()); // documentation - resultActions.andDo(getDocument(2, "SESSION 쿠키: 소셜 회원가입이 필요한 사용자를 위한 임시 세션 (만료 시간 5분)")); + resultActions.andDo( + getDocument( + 2, "소셜 유저 정보를 담은 임시 세션 쿠키 (만료 시간: 5분, , Value: SESSION=session-id 형태)")); } @Test diff --git a/src/test/java/com/ftm/server/auth/LogoutTest.java b/src/test/java/com/ftm/server/auth/LogoutTest.java new file mode 100644 index 0000000..9c9cc96 --- /dev/null +++ b/src/test/java/com/ftm/server/auth/LogoutTest.java @@ -0,0 +1,133 @@ +package com.ftm.server.auth; + +import static com.epages.restdocs.apispec.ResourceDocumentation.resource; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; +import static org.springframework.restdocs.headers.HeaderDocumentation.*; +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.*; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint; +import static org.springframework.restdocs.payload.JsonFieldType.*; +import static org.springframework.restdocs.payload.JsonFieldType.STRING; +import static org.springframework.restdocs.payload.PayloadDocumentation.*; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.epages.restdocs.apispec.ResourceSnippetParameters; +import com.ftm.server.BaseTest; +import com.ftm.server.application.dto.command.GeneralUserCreationCommand; +import com.ftm.server.application.port.AuthenticationPort; +import com.ftm.server.application.port.repository.UserImageRepository; +import com.ftm.server.application.port.repository.UserRepository; +import com.ftm.server.common.response.enums.ErrorResponseCode; +import com.ftm.server.domain.entity.User; +import com.ftm.server.domain.entity.UserImage; +import com.ftm.server.web.dto.request.UserLoginRequest; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.mock.web.MockHttpSession; +import org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders; +import org.springframework.restdocs.mockmvc.RestDocumentationResultHandler; +import org.springframework.restdocs.payload.FieldDescriptor; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.test.web.servlet.ResultActions; +import org.springframework.transaction.annotation.Transactional; + +public class LogoutTest extends BaseTest { + + @Autowired private UserRepository userRepository; + + @Autowired private UserImageRepository userImageRepository; + + @Autowired private AuthenticationPort authenticationPort; + + private final List responseFieldLogout = + List.of( + fieldWithPath("status").type(NUMBER).description("응답 상태"), + fieldWithPath("code").type(STRING).description("상태 코드"), + fieldWithPath("message").type(STRING).description("메시지"), + fieldWithPath("data").type(OBJECT).optional().description("data")); + + private RestDocumentationResultHandler getDocument(Integer identifier) { + return document( + "logout/" + identifier, + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint(), getModifiedHeader()), + requestHeaders( + headerWithName("Cookie") + .description("로그인 시 발급받은 SESSION Cookie") + .optional()), + responseFields(responseFieldLogout), + resource( + ResourceSnippetParameters.builder() + .tag("인증/인가") + .summary("로그아웃 api") + .description("로그아웃 api 입니다.") + .responseFields(responseFieldLogout) + .build())); + } + + @BeforeEach + void setUp() { + GeneralUserCreationCommand command = + new GeneralUserCreationCommand( + "test@gmail.com", + authenticationPort.passwordEncode("test1234!"), + "test", + null, + null); + User testUser = User.createGeneralUser(command); + User saved = userRepository.save(testUser); + userImageRepository.save(UserImage.createUserImage(saved)); + } + + @Test + @Transactional + void 로그아웃_성공() throws Exception { + // given + UserLoginRequest request = new UserLoginRequest("test@gmail.com", "test1234!"); + + // when + MvcResult loginResult = + mockMvc.perform( + RestDocumentationRequestBuilders.post("/api/auth/login") + .contentType(APPLICATION_JSON_VALUE) + .content(mapper.writeValueAsString(request))) + .andExpect(status().isOk()) + .andReturn(); + MockHttpSession session = (MockHttpSession) loginResult.getRequest().getSession(false); + + ResultActions resultActions = + mockMvc.perform( + RestDocumentationRequestBuilders.post("/api/auth/logout").session(session)); + + // 요청 헤더에 세션 쿠키 수동 추가 (문서화 통과용) + MvcResult result = resultActions.andReturn(); + result.getRequest().addHeader("Cookie", "SESSION=mock-session-id; Path=/; HttpOnly"); + + // then + resultActions.andExpect(status().isOk()).andDo(print()); + + // documentation + resultActions.andDo(getDocument(1)); + } + + @Test + @Transactional + void 로그아웃_실패() throws Exception { + // when + ResultActions resultActions = + mockMvc.perform(RestDocumentationRequestBuilders.post("/api/auth/logout")); + + // then + resultActions + .andExpect(status().is(ErrorResponseCode.NOT_AUTHENTICATED.getHttpStatus().value())) + .andExpect(jsonPath("code").value(ErrorResponseCode.NOT_AUTHENTICATED.getCode())) + .andDo(print()); + + // documentation + resultActions.andDo(getDocument(2)); + } +} diff --git a/src/test/java/com/ftm/server/auth/UserLoginTest.java b/src/test/java/com/ftm/server/auth/UserLoginTest.java index c36414d..f622a4b 100644 --- a/src/test/java/com/ftm/server/auth/UserLoginTest.java +++ b/src/test/java/com/ftm/server/auth/UserLoginTest.java @@ -82,7 +82,8 @@ private RestDocumentationResultHandler getDocument(Integer identifier) { requestFields(requestFieldLoginUser), responseHeaders( headerWithName("Set-Cookie") - .description("세션 ID를 담고 있는 쿠키 (SESSION), 만료 시간: 1시간") + .description( + "로그인 성공 시 발급되는 세션 쿠키 (만료 시간: 1시간, Value: SESSION=session-id 형태)") .optional()), responseFields(responseFieldLoginUser), resource( @@ -120,7 +121,7 @@ void setUp() { MvcResult result = resultActions.andReturn(); // 세션 쿠키 수동 추가 (문서화 통과용) - result.getResponse().addHeader("Set-Cookie", "SESSION=mock-session-id; Path=/; HttpOnly"); + result.getResponse().addHeader("Set-Cookie", "SESSION=session-id; Path=/; HttpOnly"); // then resultActions