-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'refactor/#108' of https://github.com/woowacourse-teams/…
…2024-devel-up into refactor/#108
- Loading branch information
Showing
8 changed files
with
293 additions
and
2 deletions.
There are no files selected for viewing
2 changes: 1 addition & 1 deletion
2
...main/java/develup/support/CorsConfig.java → ...va/develup/support/config/CorsConfig.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
2 changes: 1 addition & 1 deletion
2
...n/java/develup/support/SwaggerConfig.java → ...develup/support/config/SwaggerConfig.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
22 changes: 22 additions & 0 deletions
22
backend/src/main/java/develup/support/exception/DevelupException.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package develup.support.exception; | ||
|
||
import org.springframework.http.HttpStatus; | ||
|
||
public class DevelupException extends RuntimeException { | ||
|
||
private final ExceptionType exceptionType; | ||
|
||
public DevelupException(ExceptionType exceptionType) { | ||
super(exceptionType.getMessage()); | ||
this.exceptionType = exceptionType; | ||
} | ||
|
||
public DevelupException(ExceptionType exceptionType, Throwable cause) { | ||
super(exceptionType.getMessage(), cause); | ||
this.exceptionType = exceptionType; | ||
} | ||
|
||
public HttpStatus getStatus() { | ||
return exceptionType.getStatus(); | ||
} | ||
} |
10 changes: 10 additions & 0 deletions
10
backend/src/main/java/develup/support/exception/ExceptionDetail.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package develup.support.exception; | ||
|
||
import org.springframework.validation.FieldError; | ||
|
||
public record ExceptionDetail(String field, String message) { | ||
|
||
public ExceptionDetail(FieldError error) { | ||
this(error.getField(), error.getDefaultMessage()); | ||
} | ||
} |
19 changes: 19 additions & 0 deletions
19
backend/src/main/java/develup/support/exception/ExceptionResponse.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package develup.support.exception; | ||
|
||
import java.util.Arrays; | ||
import java.util.Collections; | ||
import java.util.List; | ||
import org.springframework.validation.FieldError; | ||
|
||
public record ExceptionResponse(String message, List<ExceptionDetail> details) { | ||
|
||
private static final String INVALID_VALUE_MESSAGE = "올바른 값을 입력해주세요."; | ||
|
||
public ExceptionResponse(String message) { | ||
this(message, Collections.emptyList()); | ||
} | ||
|
||
public ExceptionResponse(FieldError[] errors) { | ||
this(INVALID_VALUE_MESSAGE, Arrays.stream(errors).map(ExceptionDetail::new).toList()); | ||
} | ||
} |
24 changes: 24 additions & 0 deletions
24
backend/src/main/java/develup/support/exception/ExceptionType.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package develup.support.exception; | ||
|
||
import org.springframework.http.HttpStatus; | ||
|
||
public enum ExceptionType { | ||
|
||
; | ||
|
||
private final HttpStatus status; | ||
private final String message; | ||
|
||
ExceptionType(HttpStatus status, String message) { | ||
this.status = status; | ||
this.message = message; | ||
} | ||
|
||
public HttpStatus getStatus() { | ||
return status; | ||
} | ||
|
||
public String getMessage() { | ||
return message; | ||
} | ||
} |
81 changes: 81 additions & 0 deletions
81
backend/src/main/java/develup/support/exception/GlobalExceptionHandler.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
package develup.support.exception; | ||
|
||
import java.util.List; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
import org.springframework.http.HttpStatus; | ||
import org.springframework.http.ResponseEntity; | ||
import org.springframework.http.converter.HttpMessageNotReadableException; | ||
import org.springframework.validation.BindingResult; | ||
import org.springframework.validation.FieldError; | ||
import org.springframework.web.HttpRequestMethodNotSupportedException; | ||
import org.springframework.web.bind.MethodArgumentNotValidException; | ||
import org.springframework.web.bind.annotation.ExceptionHandler; | ||
import org.springframework.web.bind.annotation.RestControllerAdvice; | ||
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; | ||
import org.springframework.web.servlet.resource.NoResourceFoundException; | ||
|
||
@RestControllerAdvice | ||
public class GlobalExceptionHandler { | ||
|
||
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class); | ||
|
||
@ExceptionHandler(MethodArgumentNotValidException.class) | ||
public ResponseEntity<ExceptionResponse> handle(MethodArgumentNotValidException e) { | ||
log.warn("[MethodArgumentNotValidException] {}", e.getMessage(), e); | ||
|
||
BindingResult bindingResult = e.getBindingResult(); | ||
List<FieldError> fieldError = bindingResult.getFieldErrors(); | ||
|
||
return ResponseEntity.status(HttpStatus.BAD_REQUEST) | ||
.body(new ExceptionResponse(fieldError.toArray(FieldError[]::new))); | ||
} | ||
|
||
@ExceptionHandler(MethodArgumentTypeMismatchException.class) | ||
public ResponseEntity<ExceptionResponse> handle(MethodArgumentTypeMismatchException e) { | ||
log.warn("[MethodArgumentTypeMismatchException] {}", e.getMessage(), e); | ||
|
||
return ResponseEntity.status(HttpStatus.BAD_REQUEST) | ||
.body(new ExceptionResponse("요청 값의 타입이 잘못되었습니다.")); | ||
} | ||
|
||
@ExceptionHandler(HttpRequestMethodNotSupportedException.class) | ||
public ResponseEntity<ExceptionResponse> handle(HttpRequestMethodNotSupportedException e) { | ||
log.warn("[HttpRequestMethodNotSupportedException] {}", e.getMessage(), e); | ||
|
||
return ResponseEntity.status(HttpStatus.METHOD_NOT_ALLOWED) | ||
.body(new ExceptionResponse("지원하지 않는 HTTP 메서드입니다.")); | ||
} | ||
|
||
@ExceptionHandler(NoResourceFoundException.class) | ||
public ResponseEntity<ExceptionResponse> handle(NoResourceFoundException e) { | ||
log.warn("[NoResourceFoundException] {}", e.getMessage(), e); | ||
|
||
return ResponseEntity.status(HttpStatus.NOT_FOUND) | ||
.body(new ExceptionResponse("요청하신 리소스를 찾을 수 없습니다.")); | ||
} | ||
|
||
@ExceptionHandler(HttpMessageNotReadableException.class) | ||
public ResponseEntity<ExceptionResponse> handle(HttpMessageNotReadableException e) { | ||
log.warn("[HttpMessageNotReadableException] {}", e.getMessage(), e); | ||
|
||
return ResponseEntity.status(HttpStatus.BAD_REQUEST) | ||
.body(new ExceptionResponse("요청을 읽을 수 없습니다.")); | ||
} | ||
|
||
@ExceptionHandler(DevelupException.class) | ||
public ResponseEntity<ExceptionResponse> handle(DevelupException e) { | ||
log.warn("[DevelupException] {}", e.getMessage(), e); | ||
|
||
return ResponseEntity.status(e.getStatus()) | ||
.body(new ExceptionResponse(e.getMessage())); | ||
} | ||
|
||
@ExceptionHandler(Exception.class) | ||
public ResponseEntity<ExceptionResponse> handle(Exception e) { | ||
log.error("[Exception] {}", e.getMessage(), e); | ||
|
||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) | ||
.body(new ExceptionResponse("서버 오류가 발생했습니다.")); | ||
} | ||
} |
135 changes: 135 additions & 0 deletions
135
backend/src/test/java/develup/support/exception/GlobalExceptionHandlerTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
package develup.support.exception; | ||
|
||
import static org.mockito.Mockito.when; | ||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; | ||
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.MockMvcResultMatchers.jsonPath; | ||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; | ||
|
||
import com.fasterxml.jackson.databind.ObjectMapper; | ||
import develup.auth.AuthService; | ||
import jakarta.validation.Valid; | ||
import jakarta.validation.constraints.NotBlank; | ||
import org.junit.jupiter.api.DisplayName; | ||
import org.junit.jupiter.api.Test; | ||
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.http.ResponseEntity; | ||
import org.springframework.stereotype.Controller; | ||
import org.springframework.test.web.servlet.MockMvc; | ||
import org.springframework.web.bind.annotation.GetMapping; | ||
import org.springframework.web.bind.annotation.PathVariable; | ||
import org.springframework.web.bind.annotation.PostMapping; | ||
import org.springframework.web.bind.annotation.RequestBody; | ||
|
||
@WebMvcTest(GlobalExceptionHandlerTest.TestHandler.class) | ||
class GlobalExceptionHandlerTest { | ||
|
||
@MockBean | ||
TestHandler target; | ||
|
||
@MockBean | ||
AuthService authService; | ||
|
||
@Autowired | ||
MockMvc mockMvc; | ||
|
||
@Autowired | ||
ObjectMapper objectMapper; | ||
|
||
@Test | ||
@DisplayName("DTO를 검증 예외를 처리한다.") | ||
void methodArgumentNotValidException() throws Exception { | ||
mockMvc.perform( | ||
post("/") | ||
.contentType(MediaType.APPLICATION_JSON) | ||
.content(objectMapper.writeValueAsString(new TestHandler.TestRequest("", ""))) | ||
) | ||
.andExpect(status().isBadRequest()) | ||
.andExpect(jsonPath("$.message").value("올바른 값을 입력해주세요.")) | ||
.andExpect(jsonPath("$.details[?(@.field == 'name')]").exists()) | ||
.andExpect(jsonPath("$.details[?(@.message == '이름은 필수 값입니다.')]").exists()) | ||
.andExpect(jsonPath("$.details[?(@.field == 'email')]").exists()) | ||
.andExpect(jsonPath("$.details[?(@.message == '이메일은 필수 값입니다.')]").exists()); | ||
} | ||
|
||
@Test | ||
@DisplayName("존재하지 않는 리소스 요청을 처리한다.") | ||
void unknownResource() throws Exception { | ||
mockMvc.perform(get("/unknown/")) | ||
.andExpect(status().isNotFound()) | ||
.andExpect(jsonPath("$.message").value("요청하신 리소스를 찾을 수 없습니다.")); | ||
} | ||
|
||
@Test | ||
@DisplayName("존재하지 않는 HTTP 메서드 요청을 처리한다.") | ||
void unknownMethod() throws Exception { | ||
mockMvc.perform(delete("/")) | ||
.andExpect(status().isMethodNotAllowed()) | ||
.andExpect(jsonPath("$.message").value("지원하지 않는 HTTP 메서드입니다.")); | ||
} | ||
|
||
@Test | ||
@DisplayName("읽을 수 없는 HTTP 메시지를 처리한다.") | ||
void notReadable() throws Exception { | ||
mockMvc.perform( | ||
post("/") | ||
.contentType(MediaType.APPLICATION_JSON) | ||
.content(objectMapper.writeValueAsString("}{")) | ||
) | ||
.andExpect(status().isBadRequest()) | ||
.andExpect(jsonPath("$.message").value("요청을 읽을 수 없습니다.")); | ||
} | ||
|
||
@Test | ||
@DisplayName("타입이 일치하지 않는 경우를 처리한다.") | ||
void typeMismatch() throws Exception { | ||
mockMvc.perform(get("/abc")) | ||
.andExpect(status().isBadRequest()) | ||
.andExpect(jsonPath("$.message").value("요청 값의 타입이 잘못되었습니다.")); | ||
} | ||
|
||
@Test | ||
@DisplayName("예기치 못한 예외를 처리한다.") | ||
void unExpectedException() throws Exception { | ||
when(target.get()).thenThrow(new UnexpectedException()); | ||
|
||
mockMvc.perform(get("/")) | ||
.andExpect(status().isInternalServerError()) | ||
.andExpect(jsonPath("$.message").value("서버 오류가 발생했습니다.")); | ||
} | ||
|
||
private static class UnexpectedException extends RuntimeException { | ||
} | ||
|
||
@Controller | ||
static class TestHandler { | ||
|
||
@GetMapping | ||
ResponseEntity<String> get() { | ||
return ResponseEntity.ok("sample"); | ||
} | ||
|
||
@GetMapping("/{id}") | ||
ResponseEntity<String> get(@PathVariable Long id) { | ||
return ResponseEntity.ok("sample"); | ||
} | ||
|
||
@PostMapping | ||
ResponseEntity<Void> post(@Valid @RequestBody TestRequest request) { | ||
return ResponseEntity.noContent().build(); | ||
} | ||
|
||
record TestRequest( | ||
@NotBlank(message = "이름은 필수 값입니다.") | ||
String name, | ||
|
||
@NotBlank(message = "이메일은 필수 값입니다.") | ||
String email | ||
) { | ||
} | ||
} | ||
} |