Skip to content

Commit

Permalink
GETP-141 test: 로그인 컨트롤러 단위 테스트 및 API 문서 작성 (#66)
Browse files Browse the repository at this point in the history
* GETP-141 rename: fixture 클래스를 각 도메인의 fixture 패키지로 이동

* GETP-141 fix: bean validation 시 커스텀 메시지가 출력되지 않는 오류 수정

* GETP-141 test: 로그인 컨트롤러 단위 테스트 및 API 문서 작성
  • Loading branch information
scv1702 committed Jun 9, 2024
1 parent f08a82e commit bfe9277
Show file tree
Hide file tree
Showing 16 changed files with 156 additions and 30 deletions.
2 changes: 2 additions & 0 deletions src/docs/asciidoc/auth/login.adoc
Original file line number Diff line number Diff line change
@@ -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"]
1 change: 1 addition & 0 deletions src/docs/asciidoc/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
include::{docdir}/auth/signup.adoc[]

=== 로그인
include::{docdir}/auth/login.adoc[]

=== 회원 가입 시 이메일 인증 코드 전송

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {})
Expand Down
23 changes: 14 additions & 9 deletions src/main/java/es/princip/getp/domain/auth/service/AuthService.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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<ApiErrorResult> businessLogicException(
final BusinessLogicException businessLogicException) {
ErrorCode errorCode = businessLogicException.getErrorCode();
HttpStatus status = errorCode.status();
return ResponseEntity.status(status).body(ApiResponse.error(errorCode));
public ResponseEntity<ApiErrorResult> validationException(
final BusinessLogicException exception) {
ErrorCode errorCode = exception.getErrorCode();
return ResponseEntity.status(errorCode.status())
.body(ApiResponse.error(errorCode.status(), errorCode.description()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {})
Expand Down
Original file line number Diff line number Diff line change
@@ -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 = {})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {})
Expand Down
Original file line number Diff line number Diff line change
@@ -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());
}
}
}
Original file line number Diff line number Diff line change
@@ -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;

Expand Down
Original file line number Diff line number Diff line change
@@ -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 = "[email protected]";
private static final String PASSWORD = "1q2w3e4r!";

public static LoginRequest createLoginRequest() {
return new LoginRequest(EMAIL, PASSWORD);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -15,6 +16,15 @@
@RequestMapping("/error-code")
public class ErrorCodeController {

@GetMapping("/login")
public Map<String, ErrorCodeResponse> getLoginErrorCode() {
Map<String, ErrorCodeResponse> map = new HashMap<>();
for (ErrorCode errorCode : LoginErrorCode.values()) {
map.put(errorCode.description().code(), ErrorCodeResponse.from(errorCode));
}
return map;
}

@GetMapping("/signup")
public Map<String, ErrorCodeResponse> getSignUpErrorCode() {
Map<String, ErrorCodeResponse> map = new HashMap<>();
Expand Down

0 comments on commit bfe9277

Please sign in to comment.