From bfe927794f5ec069ec88e3344ef537b688ba9899 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=8B=A0=EC=B0=AC=EA=B7=9C=28Shin=20Changyu=29?= <63400830+scv1702@users.noreply.github.com> Date: Sun, 9 Jun 2024 13:06:27 +0900 Subject: [PATCH] =?UTF-8?q?GETP-141=20test:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8?= =?UTF-8?q?=20=EC=BB=A8=ED=8A=B8=EB=A1=A4=EB=9F=AC=20=EB=8B=A8=EC=9C=84=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EB=B0=8F=20API=20=EB=AC=B8?= =?UTF-8?q?=EC=84=9C=20=EC=9E=91=EC=84=B1=20(#66)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * GETP-141 rename: fixture 클래스를 각 도메인의 fixture 패키지로 이동 * GETP-141 fix: bean validation 시 커스텀 메시지가 출력되지 않는 오류 수정 * GETP-141 test: 로그인 컨트롤러 단위 테스트 및 API 문서 작성 --- src/docs/asciidoc/auth/login.adoc | 2 + src/docs/asciidoc/index.adoc | 1 + .../getp/domain/auth/annotation/Password.java | 5 +- .../getp/domain/auth/service/AuthService.java | 23 +++-- .../BusinessLogicExceptionHandler.java | 17 ++-- .../global/validator/annotation/Enum.java | 3 +- .../validator/annotation/Hyperlink.java | 11 ++- .../validator/annotation/PhoneNumber.java | 5 +- .../auth/controller/AuthControllerTest.java | 87 +++++++++++++++++++ .../fixture/EmailVerificationFixture.java | 2 +- .../domain/auth/fixture/LoginFixture.java | 12 +++ .../service/EmailVerificationServiceTest.java | 2 +- .../domain/auth/support/SignUpFixture.java | 2 +- .../member/service/MemberServiceTest.java | 2 +- .../ServiceTermFixture.java | 2 +- .../controller/ErrorCodeController.java | 10 +++ 16 files changed, 156 insertions(+), 30 deletions(-) create mode 100644 src/docs/asciidoc/auth/login.adoc create mode 100644 src/test/java/es/princip/getp/domain/auth/controller/AuthControllerTest.java rename src/test/java/es/princip/getp/{ => domain/auth}/fixture/EmailVerificationFixture.java (86%) create mode 100644 src/test/java/es/princip/getp/domain/auth/fixture/LoginFixture.java rename src/test/java/es/princip/getp/domain/serviceTerm/{support => fixture}/ServiceTermFixture.java (96%) diff --git a/src/docs/asciidoc/auth/login.adoc b/src/docs/asciidoc/auth/login.adoc new file mode 100644 index 00000000..fe48ff14 --- /dev/null +++ b/src/docs/asciidoc/auth/login.adoc @@ -0,0 +1,2 @@ +operation::/login/login[snippets="http-request,request-fields,http-response,response-fields-data"] +operation::/login/login-error-code[snippets="error-code-fields"] \ No newline at end of file diff --git a/src/docs/asciidoc/index.adoc b/src/docs/asciidoc/index.adoc index 8c684992..a68ddba5 100644 --- a/src/docs/asciidoc/index.adoc +++ b/src/docs/asciidoc/index.adoc @@ -15,6 +15,7 @@ include::{docdir}/auth/signup.adoc[] === 로그인 +include::{docdir}/auth/login.adoc[] === 회원 가입 시 이메일 인증 코드 전송 diff --git a/src/main/java/es/princip/getp/domain/auth/annotation/Password.java b/src/main/java/es/princip/getp/domain/auth/annotation/Password.java index 675eb9bb..32a21d79 100644 --- a/src/main/java/es/princip/getp/domain/auth/annotation/Password.java +++ b/src/main/java/es/princip/getp/domain/auth/annotation/Password.java @@ -3,13 +3,16 @@ import jakarta.validation.Constraint; import jakarta.validation.Payload; import jakarta.validation.constraints.Pattern; + import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Pattern( - regexp = "^(?=.*[A-Za-z])(?=.*\\d)(?=.*[!@#$%^&*_+=/])[A-Za-z\\d!@#$%^&*_+=/]{8,20}$") + regexp = "^(?=.*[A-Za-z])(?=.*\\d)(?=.*[!@#$%^&*_+=/])[A-Za-z\\d!@#$%^&*_+=/]{8,20}$", + message = "{validation.constraints.Password.message}" +) @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD, ElementType.PARAMETER}) @Constraint(validatedBy = {}) diff --git a/src/main/java/es/princip/getp/domain/auth/service/AuthService.java b/src/main/java/es/princip/getp/domain/auth/service/AuthService.java index bb0840bb..4fbb4ff0 100644 --- a/src/main/java/es/princip/getp/domain/auth/service/AuthService.java +++ b/src/main/java/es/princip/getp/domain/auth/service/AuthService.java @@ -3,6 +3,7 @@ import es.princip.getp.domain.auth.domain.entity.TokenVerification; import es.princip.getp.domain.auth.dto.request.LoginRequest; import es.princip.getp.domain.auth.dto.response.Token; +import es.princip.getp.domain.auth.exception.LoginErrorCode; import es.princip.getp.domain.auth.exception.TokenErrorCode; import es.princip.getp.domain.auth.repository.TokenVerificationRepository; import es.princip.getp.domain.member.service.MemberService; @@ -33,16 +34,20 @@ public class AuthService { public Token login(LoginRequest request) { String email = request.email(); String password = request.password(); - UsernamePasswordAuthenticationToken authenticationToken = + try { + UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(email, password); - Authentication authentication = authenticationManagerBuilder - .getObject() - .authenticate(authenticationToken); - PrincipalDetails principalDetails = (PrincipalDetails) authentication.getPrincipal(); - Long memberId = principalDetails.getMember().getMemberId(); - Token token = jwtTokenProvider.generateToken(authentication); - cacheRefreshToken(memberId, token.refreshToken()); - return token; + Authentication authentication = authenticationManagerBuilder + .getObject() + .authenticate(authenticationToken); + PrincipalDetails principalDetails = (PrincipalDetails) authentication.getPrincipal(); + Long memberId = principalDetails.getMember().getMemberId(); + Token token = jwtTokenProvider.generateToken(authentication); + cacheRefreshToken(memberId, token.refreshToken()); + return token; + } catch (Exception e) { + throw new BusinessLogicException(LoginErrorCode.INCORRECT_EMAIL_OR_PASSWORD); + } } public Token reissueAccessToken(HttpServletRequest servletRequest) { diff --git a/src/main/java/es/princip/getp/global/exception/handler/BusinessLogicExceptionHandler.java b/src/main/java/es/princip/getp/global/exception/handler/BusinessLogicExceptionHandler.java index d7418636..0436a89c 100644 --- a/src/main/java/es/princip/getp/global/exception/handler/BusinessLogicExceptionHandler.java +++ b/src/main/java/es/princip/getp/global/exception/handler/BusinessLogicExceptionHandler.java @@ -1,22 +1,21 @@ package es.princip.getp.global.exception.handler; +import es.princip.getp.global.exception.BusinessLogicException; import es.princip.getp.global.exception.ErrorCode; -import org.springframework.http.HttpStatus; +import es.princip.getp.global.util.ApiResponse; +import es.princip.getp.global.util.ApiResponse.ApiErrorResult; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; -import es.princip.getp.global.exception.BusinessLogicException; -import es.princip.getp.global.util.ApiResponse; -import es.princip.getp.global.util.ApiResponse.ApiErrorResult; @RestControllerAdvice public class BusinessLogicExceptionHandler { @ExceptionHandler(BusinessLogicException.class) - public ResponseEntity businessLogicException( - final BusinessLogicException businessLogicException) { - ErrorCode errorCode = businessLogicException.getErrorCode(); - HttpStatus status = errorCode.status(); - return ResponseEntity.status(status).body(ApiResponse.error(errorCode)); + public ResponseEntity validationException( + final BusinessLogicException exception) { + ErrorCode errorCode = exception.getErrorCode(); + return ResponseEntity.status(errorCode.status()) + .body(ApiResponse.error(errorCode.status(), errorCode.description())); } } \ No newline at end of file diff --git a/src/main/java/es/princip/getp/global/validator/annotation/Enum.java b/src/main/java/es/princip/getp/global/validator/annotation/Enum.java index f42c53db..12b81c7f 100644 --- a/src/main/java/es/princip/getp/global/validator/annotation/Enum.java +++ b/src/main/java/es/princip/getp/global/validator/annotation/Enum.java @@ -3,12 +3,13 @@ import jakarta.validation.Constraint; import jakarta.validation.Payload; import jakarta.validation.constraints.NotNull; + import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -@NotNull +@NotNull(message = "{validation.constraints.Enum.message}") @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD, ElementType.PARAMETER}) @Constraint(validatedBy = {}) diff --git a/src/main/java/es/princip/getp/global/validator/annotation/Hyperlink.java b/src/main/java/es/princip/getp/global/validator/annotation/Hyperlink.java index 55460738..afe88465 100644 --- a/src/main/java/es/princip/getp/global/validator/annotation/Hyperlink.java +++ b/src/main/java/es/princip/getp/global/validator/annotation/Hyperlink.java @@ -1,15 +1,18 @@ package es.princip.getp.global.validator.annotation; +import jakarta.validation.Constraint; +import jakarta.validation.Payload; +import jakarta.validation.constraints.Pattern; + import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import jakarta.validation.Constraint; -import jakarta.validation.Payload; -import jakarta.validation.constraints.Pattern; @Pattern( - regexp = "^(https|ftp|mailto|tel)://.*") + regexp = "^(https|ftp|mailto|tel)://.*", + message = "{validation.constraints.Hyperlink.message}" +) @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.FIELD }) @Constraint(validatedBy = {}) diff --git a/src/main/java/es/princip/getp/global/validator/annotation/PhoneNumber.java b/src/main/java/es/princip/getp/global/validator/annotation/PhoneNumber.java index 1d52aec1..e4a53db3 100644 --- a/src/main/java/es/princip/getp/global/validator/annotation/PhoneNumber.java +++ b/src/main/java/es/princip/getp/global/validator/annotation/PhoneNumber.java @@ -3,13 +3,16 @@ import jakarta.validation.Constraint; import jakarta.validation.Payload; import jakarta.validation.constraints.Pattern; + import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Pattern( - regexp = "^[^-]*$") + regexp = "^[^-]*$", + message = "{validation.constraints.PhoneNumber.message}" +) @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.FIELD }) @Constraint(validatedBy = {}) diff --git a/src/test/java/es/princip/getp/domain/auth/controller/AuthControllerTest.java b/src/test/java/es/princip/getp/domain/auth/controller/AuthControllerTest.java new file mode 100644 index 00000000..c747d2c8 --- /dev/null +++ b/src/test/java/es/princip/getp/domain/auth/controller/AuthControllerTest.java @@ -0,0 +1,87 @@ +package es.princip.getp.domain.auth.controller; + +import es.princip.getp.domain.auth.dto.request.LoginRequest; +import es.princip.getp.domain.auth.dto.response.Token; +import es.princip.getp.domain.auth.exception.LoginErrorCode; +import es.princip.getp.domain.auth.fixture.LoginFixture; +import es.princip.getp.domain.auth.service.AuthService; +import es.princip.getp.global.controller.ErrorCodeController; +import es.princip.getp.global.exception.BusinessLogicException; +import es.princip.getp.global.support.AbstractControllerTest; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; + +import static es.princip.getp.global.support.ErrorCodeFields.errorCodeFields; +import static es.princip.getp.global.support.FieldDescriptorHelper.getDescriptor; +import static es.princip.getp.global.support.PayloadDocumentationHelper.responseFields; +import static org.mockito.BDDMockito.given; +import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields; +import static org.springframework.restdocs.snippet.Attributes.key; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest({AuthController.class, ErrorCodeController.class}) +class AuthControllerTest extends AbstractControllerTest { + + @MockBean + private AuthService authService; + + @DisplayName("사용자는") + @Nested + class Login { + + @Test + void loginErrorCode() throws Exception { + mockMvc.perform(get("/error-code/login")) + .andDo(restDocs.document(errorCodeFields(LoginErrorCode.values()))); + } + + @DisplayName("로그인을 할 수 있다.") + @Test + void login() throws Exception { + given(authService.login(LoginFixture.createLoginRequest())) + .willReturn(Token.builder() + .grantType("Bearer") + .accessToken("access-token") + .refreshToken("refresh-token") + .build()); + + mockMvc.perform(post("/auth/login") + .content(objectMapper.writeValueAsString(LoginFixture.createLoginRequest())) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isCreated()) + .andDo( + restDocs.document( + requestFields( + getDescriptor("email", "이메일", LoginRequest.class), + getDescriptor("password", "비밀번호", LoginRequest.class) + ), + responseFields( + getDescriptor("grantType", "토큰 타입", Token.class) + .attributes(key("format").value("Bearer")), + getDescriptor("accessToken", "Access Token", Token.class), + getDescriptor("refreshToken", "Refresh Token", Token.class) + ) + ) + ) + .andDo(print()); + } + + @DisplayName("올바르지 않은 이메일 또는 비밀번호인 경우 로그인할 수 없다.") + @Test + void login_WhenEmailAndPasswordIsIncorrect_ShouldFail() throws Exception { + given(authService.login(LoginFixture.createLoginRequest())) + .willThrow(new BusinessLogicException(LoginErrorCode.INCORRECT_EMAIL_OR_PASSWORD)); + + mockMvc.perform(post("/auth/login") + .content(objectMapper.writeValueAsString(LoginFixture.createLoginRequest())) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(errorCode(LoginErrorCode.INCORRECT_EMAIL_OR_PASSWORD)) + .andDo(print()); + } + } +} \ No newline at end of file diff --git a/src/test/java/es/princip/getp/fixture/EmailVerificationFixture.java b/src/test/java/es/princip/getp/domain/auth/fixture/EmailVerificationFixture.java similarity index 86% rename from src/test/java/es/princip/getp/fixture/EmailVerificationFixture.java rename to src/test/java/es/princip/getp/domain/auth/fixture/EmailVerificationFixture.java index ae8ab2b7..b02b6904 100644 --- a/src/test/java/es/princip/getp/fixture/EmailVerificationFixture.java +++ b/src/test/java/es/princip/getp/domain/auth/fixture/EmailVerificationFixture.java @@ -1,4 +1,4 @@ -package es.princip.getp.fixture; +package es.princip.getp.domain.auth.fixture; import es.princip.getp.domain.auth.domain.entity.EmailVerification; diff --git a/src/test/java/es/princip/getp/domain/auth/fixture/LoginFixture.java b/src/test/java/es/princip/getp/domain/auth/fixture/LoginFixture.java new file mode 100644 index 00000000..4ce2f9d1 --- /dev/null +++ b/src/test/java/es/princip/getp/domain/auth/fixture/LoginFixture.java @@ -0,0 +1,12 @@ +package es.princip.getp.domain.auth.fixture; + +import es.princip.getp.domain.auth.dto.request.LoginRequest; + +public class LoginFixture { + private static final String EMAIL = "test@example.com"; + private static final String PASSWORD = "1q2w3e4r!"; + + public static LoginRequest createLoginRequest() { + return new LoginRequest(EMAIL, PASSWORD); + } +} diff --git a/src/test/java/es/princip/getp/domain/auth/service/EmailVerificationServiceTest.java b/src/test/java/es/princip/getp/domain/auth/service/EmailVerificationServiceTest.java index 77e0fe0d..fd964b2e 100644 --- a/src/test/java/es/princip/getp/domain/auth/service/EmailVerificationServiceTest.java +++ b/src/test/java/es/princip/getp/domain/auth/service/EmailVerificationServiceTest.java @@ -15,7 +15,7 @@ import java.util.Optional; -import static es.princip.getp.fixture.EmailVerificationFixture.createEmailVerification; +import static es.princip.getp.domain.auth.fixture.EmailVerificationFixture.createEmailVerification; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; diff --git a/src/test/java/es/princip/getp/domain/auth/support/SignUpFixture.java b/src/test/java/es/princip/getp/domain/auth/support/SignUpFixture.java index ca5026a2..76594784 100644 --- a/src/test/java/es/princip/getp/domain/auth/support/SignUpFixture.java +++ b/src/test/java/es/princip/getp/domain/auth/support/SignUpFixture.java @@ -4,7 +4,7 @@ import es.princip.getp.domain.auth.dto.response.SignUpResponse; import es.princip.getp.domain.member.domain.enums.MemberType; import es.princip.getp.domain.serviceTerm.dto.reqeust.ServiceTermAgreementRequest; -import es.princip.getp.domain.serviceTerm.support.ServiceTermFixture; +import es.princip.getp.domain.serviceTerm.fixture.ServiceTermFixture; import java.util.List; diff --git a/src/test/java/es/princip/getp/domain/member/service/MemberServiceTest.java b/src/test/java/es/princip/getp/domain/member/service/MemberServiceTest.java index 4181c404..de91c50f 100644 --- a/src/test/java/es/princip/getp/domain/member/service/MemberServiceTest.java +++ b/src/test/java/es/princip/getp/domain/member/service/MemberServiceTest.java @@ -6,8 +6,8 @@ import es.princip.getp.domain.member.dto.request.UpdateMemberRequest; import es.princip.getp.domain.member.exception.MemberErrorCode; import es.princip.getp.domain.member.repository.MemberRepository; +import es.princip.getp.domain.serviceTerm.fixture.ServiceTermFixture; import es.princip.getp.domain.serviceTerm.service.ServiceTermService; -import es.princip.getp.domain.serviceTerm.support.ServiceTermFixture; import es.princip.getp.domain.storage.service.ImageStorageService; import es.princip.getp.global.exception.BusinessLogicException; import es.princip.getp.global.util.PathUtil; diff --git a/src/test/java/es/princip/getp/domain/serviceTerm/support/ServiceTermFixture.java b/src/test/java/es/princip/getp/domain/serviceTerm/fixture/ServiceTermFixture.java similarity index 96% rename from src/test/java/es/princip/getp/domain/serviceTerm/support/ServiceTermFixture.java rename to src/test/java/es/princip/getp/domain/serviceTerm/fixture/ServiceTermFixture.java index 6c6b3fa8..6f8f8906 100644 --- a/src/test/java/es/princip/getp/domain/serviceTerm/support/ServiceTermFixture.java +++ b/src/test/java/es/princip/getp/domain/serviceTerm/fixture/ServiceTermFixture.java @@ -1,4 +1,4 @@ -package es.princip.getp.domain.serviceTerm.support; +package es.princip.getp.domain.serviceTerm.fixture; import es.princip.getp.domain.serviceTerm.dto.reqeust.ServiceTermAgreementRequest; import es.princip.getp.domain.serviceTerm.dto.response.ServiceTermAgreementResponse; diff --git a/src/test/java/es/princip/getp/global/controller/ErrorCodeController.java b/src/test/java/es/princip/getp/global/controller/ErrorCodeController.java index 12e80c35..313ba0b5 100644 --- a/src/test/java/es/princip/getp/global/controller/ErrorCodeController.java +++ b/src/test/java/es/princip/getp/global/controller/ErrorCodeController.java @@ -1,5 +1,6 @@ package es.princip.getp.global.controller; +import es.princip.getp.domain.auth.exception.LoginErrorCode; import es.princip.getp.domain.auth.exception.SignUpErrorCode; import es.princip.getp.domain.storage.exception.ImageErrorCode; import es.princip.getp.global.domain.dto.response.ErrorCodeResponse; @@ -15,6 +16,15 @@ @RequestMapping("/error-code") public class ErrorCodeController { + @GetMapping("/login") + public Map getLoginErrorCode() { + Map map = new HashMap<>(); + for (ErrorCode errorCode : LoginErrorCode.values()) { + map.put(errorCode.description().code(), ErrorCodeResponse.from(errorCode)); + } + return map; + } + @GetMapping("/signup") public Map getSignUpErrorCode() { Map map = new HashMap<>();