From f20b50601bb63fd3ea2f69b62aec1f33dd2e1679 Mon Sep 17 00:00:00 2001 From: Rebwon Date: Mon, 20 Sep 2021 21:01:58 +0900 Subject: [PATCH] Add classes to handle exceptions that occur in the Presentation layer --- .../account/application/AccountFacade.java | 3 +- .../domain/AccountNotFoundException.java | 10 +++ .../ConfirmRegisterAccountProcessor.java | 2 +- .../domain/RegisterAccountProcessor.java | 8 +- .../yam/app/account/domain/TokenVerifier.java | 5 +- .../LoginAccountMethodArgumentResolver.java | 5 +- .../SessionBasedLoginAccountProcessor.java | 7 +- .../presentation/AccountCommandApi.java | 13 +-- .../account/presentation/AccountQueryApi.java | 12 +-- .../presentation/LoginAccountCommand.java | 3 +- .../presentation/RegisterAccountCommand.java | 3 +- .../java/com/yam/app/common/ApiResult.java | 25 ++++++ .../app/common/DuplicateValueException.java | 15 ++++ .../app/common/EntityNotFoundException.java | 14 +++ .../app/common/GlobalApiExceptionHandler.java | 89 +++++++++++++++++++ .../com/yam/app/common/SystemException.java | 12 +++ .../common/UnauthorizedRequestException.java | 15 ++++ .../ConfirmRegisterAccountProcessorTest.java | 2 +- .../domain/RegisterAccountProcessorTest.java | 5 +- ...SessionBasedLoginAccountProcessorTest.java | 3 +- .../presentation/AccountCommandApiTests.java | 53 +++++------ .../presentation/AccountQueryApiTest.java | 29 +++++- 22 files changed, 260 insertions(+), 73 deletions(-) create mode 100644 src/main/java/com/yam/app/account/domain/AccountNotFoundException.java create mode 100644 src/main/java/com/yam/app/common/ApiResult.java create mode 100644 src/main/java/com/yam/app/common/DuplicateValueException.java create mode 100644 src/main/java/com/yam/app/common/EntityNotFoundException.java create mode 100644 src/main/java/com/yam/app/common/GlobalApiExceptionHandler.java create mode 100644 src/main/java/com/yam/app/common/SystemException.java create mode 100644 src/main/java/com/yam/app/common/UnauthorizedRequestException.java diff --git a/src/main/java/com/yam/app/account/application/AccountFacade.java b/src/main/java/com/yam/app/account/application/AccountFacade.java index 8b93297..9a077e4 100644 --- a/src/main/java/com/yam/app/account/application/AccountFacade.java +++ b/src/main/java/com/yam/app/account/application/AccountFacade.java @@ -1,5 +1,6 @@ package com.yam.app.account.application; +import com.yam.app.account.domain.AccountNotFoundException; import com.yam.app.account.domain.AccountReader; import com.yam.app.account.domain.ConfirmRegisterAccountProcessor; import com.yam.app.account.domain.LoginAccountProcessor; @@ -58,7 +59,7 @@ public class AccountFacade { @Transactional(readOnly = true) public AccountResponse findInfo(String email) { return translator.toResponse(accountReader.findByEmail(email) - .orElseThrow(IllegalArgumentException::new)); + .orElseThrow(() -> new AccountNotFoundException(email))); } } diff --git a/src/main/java/com/yam/app/account/domain/AccountNotFoundException.java b/src/main/java/com/yam/app/account/domain/AccountNotFoundException.java new file mode 100644 index 0000000..89679cd --- /dev/null +++ b/src/main/java/com/yam/app/account/domain/AccountNotFoundException.java @@ -0,0 +1,10 @@ +package com.yam.app.account.domain; + +import com.yam.app.common.EntityNotFoundException; + +public final class AccountNotFoundException extends EntityNotFoundException { + + public AccountNotFoundException(String email) { + super("Account could not be found, (email : %s)", email); + } +} diff --git a/src/main/java/com/yam/app/account/domain/ConfirmRegisterAccountProcessor.java b/src/main/java/com/yam/app/account/domain/ConfirmRegisterAccountProcessor.java index c99b396..6ac80f4 100644 --- a/src/main/java/com/yam/app/account/domain/ConfirmRegisterAccountProcessor.java +++ b/src/main/java/com/yam/app/account/domain/ConfirmRegisterAccountProcessor.java @@ -16,7 +16,7 @@ public final class ConfirmRegisterAccountProcessor { public void registerConfirm(String token, String email) { tokenVerifier.verify(token, email); var account = accountReader.findByEmail(email) - .orElseThrow(IllegalArgumentException::new); + .orElseThrow(() -> new AccountNotFoundException(email)); account.completeRegister(); accountRepository.update(account); } diff --git a/src/main/java/com/yam/app/account/domain/RegisterAccountProcessor.java b/src/main/java/com/yam/app/account/domain/RegisterAccountProcessor.java index 57df2fc..9c3b894 100644 --- a/src/main/java/com/yam/app/account/domain/RegisterAccountProcessor.java +++ b/src/main/java/com/yam/app/account/domain/RegisterAccountProcessor.java @@ -1,5 +1,7 @@ package com.yam.app.account.domain; +import com.yam.app.common.DuplicateValueException; + public final class RegisterAccountProcessor { private final AccountRepository accountRepository; @@ -15,16 +17,16 @@ public final class RegisterAccountProcessor { public Account process(String email, String nickname, String password) { if (accountReader.existsByEmail(email)) { - throw new IllegalStateException(); + throw new DuplicateValueException(email); } if (accountReader.existsByNickname(nickname)) { - throw new IllegalStateException(); + throw new DuplicateValueException(nickname); } String encodedPassword = passwordEncrypter.encode(password); accountRepository.save(Account.of(email, nickname, encodedPassword)); return accountReader.findByEmail(email) - .orElseThrow(IllegalArgumentException::new); + .orElseThrow(() -> new AccountNotFoundException(email)); } } diff --git a/src/main/java/com/yam/app/account/domain/TokenVerifier.java b/src/main/java/com/yam/app/account/domain/TokenVerifier.java index 7b0f207..4291b5b 100644 --- a/src/main/java/com/yam/app/account/domain/TokenVerifier.java +++ b/src/main/java/com/yam/app/account/domain/TokenVerifier.java @@ -10,11 +10,10 @@ public final class TokenVerifier { public void verify(String token, String email) { var account = accountReader.findByEmail(email) - .orElseThrow(IllegalArgumentException::new); + .orElseThrow(() -> new AccountNotFoundException(email)); if (!account.isValidToken(token)) { - throw new IllegalStateException(); + throw new IllegalStateException("Invalid token"); } - } } diff --git a/src/main/java/com/yam/app/account/infrastructure/LoginAccountMethodArgumentResolver.java b/src/main/java/com/yam/app/account/infrastructure/LoginAccountMethodArgumentResolver.java index b1e4471..5cba38c 100644 --- a/src/main/java/com/yam/app/account/infrastructure/LoginAccountMethodArgumentResolver.java +++ b/src/main/java/com/yam/app/account/infrastructure/LoginAccountMethodArgumentResolver.java @@ -1,5 +1,6 @@ package com.yam.app.account.infrastructure; +import com.yam.app.common.UnauthorizedRequestException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import org.springframework.core.MethodParameter; @@ -22,11 +23,11 @@ public final class LoginAccountMethodArgumentResolver implements HandlerMethodAr .getSession(false); if (session == null) { - return null; + throw new UnauthorizedRequestException("Unauthorized request"); } var sessionManager = new SessionManager(session); return sessionManager.fetchPrincipal() - .orElseThrow(IllegalArgumentException::new); + .orElseThrow(() -> new UnauthorizedRequestException("Failed fetch principal")); } } diff --git a/src/main/java/com/yam/app/account/infrastructure/SessionBasedLoginAccountProcessor.java b/src/main/java/com/yam/app/account/infrastructure/SessionBasedLoginAccountProcessor.java index deed1d0..f48a086 100644 --- a/src/main/java/com/yam/app/account/infrastructure/SessionBasedLoginAccountProcessor.java +++ b/src/main/java/com/yam/app/account/infrastructure/SessionBasedLoginAccountProcessor.java @@ -1,5 +1,6 @@ package com.yam.app.account.infrastructure; +import com.yam.app.account.domain.AccountNotFoundException; import com.yam.app.account.domain.AccountReader; import com.yam.app.account.domain.LoginAccountProcessor; import com.yam.app.account.domain.PasswordEncrypter; @@ -21,14 +22,14 @@ public final class SessionBasedLoginAccountProcessor implements LoginAccountProc @Override public void login(String email, String password) { var account = accountReader.findByEmail(email) - .orElseThrow(IllegalArgumentException::new); + .orElseThrow(() -> new AccountNotFoundException(email)); if (!account.isEmailVerified()) { - throw new IllegalStateException(); + throw new IllegalStateException("Email not verified"); } if (!passwordEncrypter.matches(password, account.getPassword())) { - throw new IllegalStateException(); + throw new IllegalStateException("Password mismatched"); } sessionManager.setPrincipal(new AccountPrincipal(email)); diff --git a/src/main/java/com/yam/app/account/presentation/AccountCommandApi.java b/src/main/java/com/yam/app/account/presentation/AccountCommandApi.java index ebd8490..00c8eef 100644 --- a/src/main/java/com/yam/app/account/presentation/AccountCommandApi.java +++ b/src/main/java/com/yam/app/account/presentation/AccountCommandApi.java @@ -42,11 +42,7 @@ public final class AccountCommandApi { @GetMapping("/api/accounts/authorize") public ResponseEntity registerConfirm( @ModelAttribute @Valid ConfirmRegisterAccountCommand command) throws Exception { - try { - accountFacade.registerConfirm(command); - } catch (Exception e) { - return ResponseEntity.badRequest().build(); - } + accountFacade.registerConfirm(command); var uri = new URI("http://localhost:3000/login"); var header = new HttpHeaders(); @@ -57,12 +53,7 @@ public final class AccountCommandApi { @PostMapping("/api/accounts/login") public ResponseEntity login( @RequestBody @Valid LoginAccountCommand request) { - try { - accountFacade.login(request); - } catch (IllegalStateException e) { - return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); - } - + accountFacade.login(request); return ResponseEntity.ok().build(); } } diff --git a/src/main/java/com/yam/app/account/presentation/AccountQueryApi.java b/src/main/java/com/yam/app/account/presentation/AccountQueryApi.java index 379907a..fd6ed88 100644 --- a/src/main/java/com/yam/app/account/presentation/AccountQueryApi.java +++ b/src/main/java/com/yam/app/account/presentation/AccountQueryApi.java @@ -3,7 +3,6 @@ package com.yam.app.account.presentation; import com.yam.app.account.application.AccountFacade; import com.yam.app.account.infrastructure.AccountPrincipal; import com.yam.app.account.infrastructure.LoginAccount; -import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; @@ -26,16 +25,7 @@ public final class AccountQueryApi { @GetMapping("/api/accounts/me") public ResponseEntity findInfo( @LoginAccount AccountPrincipal accountPrincipal) { - if (accountPrincipal == null) { - return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); - } - - try { - return ResponseEntity.ok(accountFacade.findInfo( - accountPrincipal.getEmail())); - } catch (Exception e) { - return ResponseEntity.badRequest().build(); - } + return ResponseEntity.ok(accountFacade.findInfo(accountPrincipal.getEmail())); } } diff --git a/src/main/java/com/yam/app/account/presentation/LoginAccountCommand.java b/src/main/java/com/yam/app/account/presentation/LoginAccountCommand.java index 7e4efa5..d3238cc 100644 --- a/src/main/java/com/yam/app/account/presentation/LoginAccountCommand.java +++ b/src/main/java/com/yam/app/account/presentation/LoginAccountCommand.java @@ -14,6 +14,7 @@ public final class LoginAccountCommand { @NotBlank @Pattern(regexp = "^[A-Za-z1-9~!@#$%^&*()+|=]{8,12}$", - message = "비밀번호는 영어와 숫자, 특수문자로 8~12자리 이내로 입력해주세요.") + message = "Please enter the password in English, numbers, " + + "and special characters within 8-12 digits.") private String password; } diff --git a/src/main/java/com/yam/app/account/presentation/RegisterAccountCommand.java b/src/main/java/com/yam/app/account/presentation/RegisterAccountCommand.java index 6acef0c..a4b1e0d 100644 --- a/src/main/java/com/yam/app/account/presentation/RegisterAccountCommand.java +++ b/src/main/java/com/yam/app/account/presentation/RegisterAccountCommand.java @@ -17,7 +17,8 @@ public final class RegisterAccountCommand { @NotBlank @Pattern(regexp = "^[A-Za-z1-9~!@#$%^&*()+|=]{8,12}$", - message = "비밀번호는 영어와 숫자, 특수문자로 8~12자리 이내로 입력해주세요.") + message = "Please enter the password in English, numbers, " + + "and special characters within 8-12 digits.") private String password; } diff --git a/src/main/java/com/yam/app/common/ApiResult.java b/src/main/java/com/yam/app/common/ApiResult.java new file mode 100644 index 0000000..df5e4b8 --- /dev/null +++ b/src/main/java/com/yam/app/common/ApiResult.java @@ -0,0 +1,25 @@ +package com.yam.app.common; + +import lombok.Getter; + +@Getter +public final class ApiResult { + + private final boolean success; + private final T data; + private final String message; + + private ApiResult(boolean success, T data, String message) { + this.success = success; + this.data = data; + this.message = message; + } + + public static ApiResult success(T data) { + return new ApiResult<>(true, data, null); + } + + public static ApiResult error(String message) { + return new ApiResult<>(false, null, message); + } +} diff --git a/src/main/java/com/yam/app/common/DuplicateValueException.java b/src/main/java/com/yam/app/common/DuplicateValueException.java new file mode 100644 index 0000000..38c8ffd --- /dev/null +++ b/src/main/java/com/yam/app/common/DuplicateValueException.java @@ -0,0 +1,15 @@ +package com.yam.app.common; + +import org.springframework.http.HttpStatus; + +public final class DuplicateValueException extends SystemException { + + public DuplicateValueException(String value) { + super("These are duplicated value, (value : %s)", value); + } + + @Override + public HttpStatus getStatus() { + return HttpStatus.CONFLICT; + } +} diff --git a/src/main/java/com/yam/app/common/EntityNotFoundException.java b/src/main/java/com/yam/app/common/EntityNotFoundException.java new file mode 100644 index 0000000..0254a96 --- /dev/null +++ b/src/main/java/com/yam/app/common/EntityNotFoundException.java @@ -0,0 +1,14 @@ +package com.yam.app.common; + +import org.springframework.http.HttpStatus; + +public class EntityNotFoundException extends SystemException { + + public EntityNotFoundException(String format, Object... args) { + super(format, args); + } + + public HttpStatus getStatus() { + return HttpStatus.NOT_FOUND; + } +} diff --git a/src/main/java/com/yam/app/common/GlobalApiExceptionHandler.java b/src/main/java/com/yam/app/common/GlobalApiExceptionHandler.java new file mode 100644 index 0000000..f9ef0fe --- /dev/null +++ b/src/main/java/com/yam/app/common/GlobalApiExceptionHandler.java @@ -0,0 +1,89 @@ +package com.yam.app.common; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.BindException; +import org.springframework.web.HttpMediaTypeNotSupportedException; +import org.springframework.web.HttpRequestMethodNotSupportedException; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; + +@Slf4j +@ControllerAdvice +public final class GlobalApiExceptionHandler { + + @ExceptionHandler(MethodArgumentNotValidException.class) + private ResponseEntity> handleMethod(MethodArgumentNotValidException e) { + log.error("MethodArgumentNotValidException", e); + return ResponseEntity + .badRequest() + .body(ApiResult.error("Invalid argument")); + } + + @ExceptionHandler(BindException.class) + private ResponseEntity> handleBind(BindException e) { + log.error("BindException", e); + return ResponseEntity + .badRequest() + .body(ApiResult.error("Invalid argument")); + } + + @ExceptionHandler(HttpMediaTypeNotSupportedException.class) + private ResponseEntity> handleNotSupported(HttpMediaTypeNotSupportedException e) { + log.error("HttpMediaTypeNotSupportedException", e); + return ResponseEntity + .status(HttpStatus.UNSUPPORTED_MEDIA_TYPE) + .body(ApiResult.error("Http media type not supported")); + } + + @ExceptionHandler(HttpRequestMethodNotSupportedException.class) + private ResponseEntity> handleNotSupported( + HttpRequestMethodNotSupportedException e) { + log.error("HttpRequestMethodNotSupportedException", e); + return ResponseEntity + .status(HttpStatus.METHOD_NOT_ALLOWED) + .body(ApiResult.error("Http request method not supported")); + } + + @ExceptionHandler(Exception.class) + private ResponseEntity> handleException(Exception e) { + log.error("Exception", e); + return ResponseEntity + .internalServerError() + .body(ApiResult.error("Internal server error")); + } + + @ExceptionHandler(EntityNotFoundException.class) + private ResponseEntity> handleNotFound(EntityNotFoundException e) { + log.error("EntityNotFoundException", e); + return ResponseEntity + .status(e.getStatus()) + .body(ApiResult.error("Not found resources")); + } + + @ExceptionHandler(DuplicateValueException.class) + private ResponseEntity> handleDuplicate(DuplicateValueException e) { + log.error("DuplicateValueException", e); + return ResponseEntity + .status(e.getStatus()) + .body(ApiResult.error("Duplicated value")); + } + + @ExceptionHandler(IllegalStateException.class) + private ResponseEntity> handleIllegalState(IllegalStateException e) { + log.error("IllegalStateException", e); + return ResponseEntity + .badRequest() + .body(ApiResult.error(e.getMessage())); + } + + @ExceptionHandler(UnauthorizedRequestException.class) + private ResponseEntity> handleUnauthorized(UnauthorizedRequestException e) { + log.error("UnauthorizedRequestException", e); + return ResponseEntity + .status(e.getStatus()) + .body(ApiResult.error(e.getMessage())); + } +} diff --git a/src/main/java/com/yam/app/common/SystemException.java b/src/main/java/com/yam/app/common/SystemException.java new file mode 100644 index 0000000..97933de --- /dev/null +++ b/src/main/java/com/yam/app/common/SystemException.java @@ -0,0 +1,12 @@ +package com.yam.app.common; + +import org.springframework.http.HttpStatus; + +public abstract class SystemException extends RuntimeException { + + public SystemException(String format, Object... args) { + super(String.format(format, args)); + } + + public abstract HttpStatus getStatus(); +} diff --git a/src/main/java/com/yam/app/common/UnauthorizedRequestException.java b/src/main/java/com/yam/app/common/UnauthorizedRequestException.java new file mode 100644 index 0000000..ec9573c --- /dev/null +++ b/src/main/java/com/yam/app/common/UnauthorizedRequestException.java @@ -0,0 +1,15 @@ +package com.yam.app.common; + +import org.springframework.http.HttpStatus; + +public final class UnauthorizedRequestException extends SystemException { + + public UnauthorizedRequestException(String message) { + super(message); + } + + @Override + public HttpStatus getStatus() { + return HttpStatus.UNAUTHORIZED; + } +} diff --git a/src/test/java/com/yam/app/account/domain/ConfirmRegisterAccountProcessorTest.java b/src/test/java/com/yam/app/account/domain/ConfirmRegisterAccountProcessorTest.java index 772cf57..bc03846 100644 --- a/src/test/java/com/yam/app/account/domain/ConfirmRegisterAccountProcessorTest.java +++ b/src/test/java/com/yam/app/account/domain/ConfirmRegisterAccountProcessorTest.java @@ -46,7 +46,7 @@ final class ConfirmRegisterAccountProcessorTest { DynamicTest.dynamicTest("이메일 검증에 실패하여 예외를 리턴한다.", () -> { // Act & Assert - assertThatExceptionOfType(IllegalArgumentException.class) + assertThatExceptionOfType(AccountNotFoundException.class) .isThrownBy(() -> confirmRegisterAccountProcessor.registerConfirm( account.getEmailCheckToken(), "HiIamNotExistEmail@naver.com")); diff --git a/src/test/java/com/yam/app/account/domain/RegisterAccountProcessorTest.java b/src/test/java/com/yam/app/account/domain/RegisterAccountProcessorTest.java index 1c1ad25..fee95a5 100644 --- a/src/test/java/com/yam/app/account/domain/RegisterAccountProcessorTest.java +++ b/src/test/java/com/yam/app/account/domain/RegisterAccountProcessorTest.java @@ -3,6 +3,7 @@ package com.yam.app.account.domain; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import com.yam.app.common.DuplicateValueException; import java.util.Arrays; import java.util.Collection; import org.junit.jupiter.api.DisplayName; @@ -30,12 +31,12 @@ final class RegisterAccountProcessorTest { }), DynamicTest.dynamicTest("이메일 검증에 실패하여 예외를 리턴한다.", () -> { // Act & Assert - assertThatExceptionOfType(IllegalStateException.class) + assertThatExceptionOfType(DuplicateValueException.class) .isThrownBy(() -> processor.process("rebwon@gmail.com", "rebwon", "password!")); }), DynamicTest.dynamicTest("닉네임 검증에 실패하여 예외를 리턴한다.", () -> { // Act & Assert - assertThatExceptionOfType(IllegalStateException.class) + assertThatExceptionOfType(DuplicateValueException.class) .isThrownBy(() -> processor.process("kitty@gmail.com", "rebwon", "password!")); }) ); diff --git a/src/test/java/com/yam/app/account/infrastructure/SessionBasedLoginAccountProcessorTest.java b/src/test/java/com/yam/app/account/infrastructure/SessionBasedLoginAccountProcessorTest.java index 43eac32..87bf6a6 100644 --- a/src/test/java/com/yam/app/account/infrastructure/SessionBasedLoginAccountProcessorTest.java +++ b/src/test/java/com/yam/app/account/infrastructure/SessionBasedLoginAccountProcessorTest.java @@ -5,6 +5,7 @@ import static org.assertj.core.api.Assertions.catchThrowable; import static org.junit.jupiter.api.DynamicTest.dynamicTest; import com.yam.app.account.domain.Account; +import com.yam.app.account.domain.AccountNotFoundException; import com.yam.app.account.domain.FakeAccountRepository; import com.yam.app.account.domain.PasswordEncrypterStub; import java.util.Arrays; @@ -53,7 +54,7 @@ final class SessionBasedLoginAccountProcessorTest { ); // Assert - assertThat(throwable).isInstanceOf(IllegalArgumentException.class); + assertThat(throwable).isInstanceOf(AccountNotFoundException.class); }), dynamicTest("이메일은 유효하나 검증을 완료하지 않은 경우 예외를 리턴한다.", () -> { // Act diff --git a/src/test/java/com/yam/app/account/presentation/AccountCommandApiTests.java b/src/test/java/com/yam/app/account/presentation/AccountCommandApiTests.java index f458b30..41ae3f5 100644 --- a/src/test/java/com/yam/app/account/presentation/AccountCommandApiTests.java +++ b/src/test/java/com/yam/app/account/presentation/AccountCommandApiTests.java @@ -7,6 +7,7 @@ import static org.mockito.Mockito.doThrow; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; 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.fasterxml.jackson.databind.ObjectMapper; @@ -23,6 +24,7 @@ import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultActions; @DisplayName("Account Command HTTP API") @WebMvcTest(AccountCommandApi.class) @@ -40,7 +42,7 @@ final class AccountCommandApiTests { class LoginApi { @Test - @DisplayName("이메일, 비밀번호 형식은 유효하나 로그인이 실패한 경우 401 에러를 반환한다.") + @DisplayName("이메일, 비밀번호 형식은 유효하나 로그인이 실패한 경우 400 에러를 반환한다.") void login_fail() throws Exception { // Arrange var request = new LoginAccountCommand(); @@ -59,7 +61,7 @@ final class AccountCommandApiTests { // Assert actions .andDo(print()) - .andExpect(status().isUnauthorized()); + .andExpect(status().isBadRequest()); } @ParameterizedTest @@ -79,9 +81,7 @@ final class AccountCommandApiTests { ); // Assert - actions - .andDo(print()) - .andExpect(status().isBadRequest()); + assertThatInvalidArgumentError(actions); } @ParameterizedTest @@ -101,9 +101,7 @@ final class AccountCommandApiTests { ); // Assert - actions - .andDo(print()) - .andExpect(status().isBadRequest()); + assertThatInvalidArgumentError(actions); } @ParameterizedTest @@ -123,9 +121,7 @@ final class AccountCommandApiTests { ); //Assert - actions - .andDo(print()) - .andExpect(status().isBadRequest()); + assertThatInvalidArgumentError(actions); } } @@ -155,9 +151,7 @@ final class AccountCommandApiTests { ); // Assert - actions - .andDo(print()) - .andExpect(status().isBadRequest()); + assertThatInvalidArgumentError(actions); } @ParameterizedTest @@ -170,8 +164,6 @@ final class AccountCommandApiTests { command.setEmail(arg); // Act - doThrow(IllegalStateException.class).when(accountFacade).registerConfirm(command); - final var actions = mockMvc.perform(get(EMAIL_CONFIRM) .accept(MediaType.APPLICATION_JSON) .contentType(MediaType.APPLICATION_JSON) @@ -180,9 +172,7 @@ final class AccountCommandApiTests { ); // Assert - actions - .andDo(print()) - .andExpect(status().isBadRequest()); + assertThatInvalidArgumentError(actions); } } @@ -208,7 +198,10 @@ final class AccountCommandApiTests { // Assert actions - .andExpect(status().isUnsupportedMediaType()); + .andExpect(status().isUnsupportedMediaType()) + .andExpect(jsonPath("$.success").value(false)) + .andExpect(jsonPath("$.data").doesNotExist()) + .andExpect(jsonPath("$.message").value("Http media type not supported")); } @ParameterizedTest @@ -229,9 +222,7 @@ final class AccountCommandApiTests { ); // Assert - actions - .andDo(print()) - .andExpect(status().isBadRequest()); + assertThatInvalidArgumentError(actions); } @ParameterizedTest @@ -252,9 +243,7 @@ final class AccountCommandApiTests { ); // Assert - actions - .andDo(print()) - .andExpect(status().isBadRequest()); + assertThatInvalidArgumentError(actions); } @ParameterizedTest @@ -275,11 +264,17 @@ final class AccountCommandApiTests { ); // Assert - actions - .andDo(print()) - .andExpect(status().isBadRequest()); + assertThatInvalidArgumentError(actions); } } + private void assertThatInvalidArgumentError(ResultActions actions) throws Exception { + actions + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.success").value(false)) + .andExpect(jsonPath("$.data").doesNotExist()) + .andExpect(jsonPath("$.message").value("Invalid argument")); + } + } diff --git a/src/test/java/com/yam/app/account/presentation/AccountQueryApiTest.java b/src/test/java/com/yam/app/account/presentation/AccountQueryApiTest.java index d0ca355..72b03f1 100644 --- a/src/test/java/com/yam/app/account/presentation/AccountQueryApiTest.java +++ b/src/test/java/com/yam/app/account/presentation/AccountQueryApiTest.java @@ -2,7 +2,7 @@ package com.yam.app.account.presentation; import static com.yam.app.account.presentation.AccountApiUri.FIND_INFO; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -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.yam.app.account.application.AccountFacade; @@ -13,6 +13,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.http.MediaType; +import org.springframework.mock.web.MockHttpSession; import org.springframework.test.web.servlet.MockMvc; @DisplayName("Account Query HTTP API") @@ -38,8 +39,30 @@ class AccountQueryApiTest { //Assert actions - .andDo(print()) - .andExpect(status().isUnauthorized()); + .andExpect(status().isUnauthorized()) + .andExpect(jsonPath("$.success").value(false)) + .andExpect(jsonPath("$.data").doesNotExist()) + .andExpect(jsonPath("$.message").value("Unauthorized request")); + } + + @Test + @DisplayName("잘못된 인증 정보로 요청을 보낸 경우 401 응답을 반환한다.") + void failed_fetch_session_principal() throws Exception { + //Act + var session = new MockHttpSession(); + + final var actions = mockMvc.perform(get(FIND_INFO) + .accept(MediaType.APPLICATION_JSON) + .contentType(MediaType.APPLICATION_JSON) + .session(session) + ); + + //Assert + actions + .andExpect(status().isUnauthorized()) + .andExpect(jsonPath("$.success").value(false)) + .andExpect(jsonPath("$.data").doesNotExist()) + .andExpect(jsonPath("$.message").value("Failed fetch principal")); } } }