From 336d2bb35cdd29ea1f8a691b9f0fe8b09d14f7bd Mon Sep 17 00:00:00 2001 From: coli-geonwoo Date: Sun, 15 Dec 2024 23:34:41 +0900 Subject: [PATCH 01/20] =?UTF-8?q?feat:=20=EC=97=90=EB=9F=AC=20=EA=B0=9D?= =?UTF-8?q?=EC=B2=B4=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/exception/ErrorResponse.java | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 src/main/java/com/debatetimer/controller/exception/ErrorResponse.java diff --git a/src/main/java/com/debatetimer/controller/exception/ErrorResponse.java b/src/main/java/com/debatetimer/controller/exception/ErrorResponse.java new file mode 100644 index 0000000..8303012 --- /dev/null +++ b/src/main/java/com/debatetimer/controller/exception/ErrorResponse.java @@ -0,0 +1,10 @@ +package com.debatetimer.controller.exception; + +import jakarta.validation.constraints.NotBlank; + +public record ErrorResponse( + @NotBlank + String message +) { + +} From eb9a265fda66cd8bc5d90f8e968581c37ab008db Mon Sep 17 00:00:00 2001 From: coli-geonwoo Date: Sat, 21 Dec 2024 23:05:05 +0900 Subject: [PATCH 02/20] =?UTF-8?q?feat:=20=EC=BB=A4=EC=8A=A4=ED=85=80=20?= =?UTF-8?q?=EC=97=90=EB=9F=AC=20=EA=B0=9D=EC=B2=B4=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/debatetimer/controller/exception/ErrorResponse.java | 3 +++ .../java/com/debatetimer/swagger/annotation/ErrorCode400.java | 3 ++- .../java/com/debatetimer/swagger/annotation/ErrorCode401.java | 3 ++- .../java/com/debatetimer/swagger/annotation/ErrorCode404.java | 3 ++- .../java/com/debatetimer/swagger/annotation/ErrorCode500.java | 3 ++- 5 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/debatetimer/controller/exception/ErrorResponse.java b/src/main/java/com/debatetimer/controller/exception/ErrorResponse.java index 8303012..0f9f939 100644 --- a/src/main/java/com/debatetimer/controller/exception/ErrorResponse.java +++ b/src/main/java/com/debatetimer/controller/exception/ErrorResponse.java @@ -1,8 +1,11 @@ package com.debatetimer.controller.exception; +import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; +@Schema(description = "예외 객체") public record ErrorResponse( + @Schema(description = "사용자에게 보여줄 예외 메시지") @NotBlank String message ) { diff --git a/src/main/java/com/debatetimer/swagger/annotation/ErrorCode400.java b/src/main/java/com/debatetimer/swagger/annotation/ErrorCode400.java index d984723..81c912d 100644 --- a/src/main/java/com/debatetimer/swagger/annotation/ErrorCode400.java +++ b/src/main/java/com/debatetimer/swagger/annotation/ErrorCode400.java @@ -1,5 +1,6 @@ package com.debatetimer.swagger.annotation; +import com.debatetimer.controller.exception.ErrorResponse; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; @@ -15,7 +16,7 @@ @ApiResponse( responseCode = "400", description = "클라이언트 입력 오류", - content = @Content(schema = @Schema(implementation = ProblemDetail.class)) + content = @Content(schema = @Schema(implementation = ErrorResponse.class)) ) public @interface ErrorCode400 { diff --git a/src/main/java/com/debatetimer/swagger/annotation/ErrorCode401.java b/src/main/java/com/debatetimer/swagger/annotation/ErrorCode401.java index 1ed90c7..3e6e063 100644 --- a/src/main/java/com/debatetimer/swagger/annotation/ErrorCode401.java +++ b/src/main/java/com/debatetimer/swagger/annotation/ErrorCode401.java @@ -1,5 +1,6 @@ package com.debatetimer.swagger.annotation; +import com.debatetimer.controller.exception.ErrorResponse; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; @@ -15,7 +16,7 @@ @ApiResponse( responseCode = "401", description = "인증되지 않은 사용자", - content = @Content(schema = @Schema(implementation = ProblemDetail.class)) + content = @Content(schema = @Schema(implementation = ErrorResponse.class)) ) public @interface ErrorCode401 { diff --git a/src/main/java/com/debatetimer/swagger/annotation/ErrorCode404.java b/src/main/java/com/debatetimer/swagger/annotation/ErrorCode404.java index 1656dcd..6c5726b 100644 --- a/src/main/java/com/debatetimer/swagger/annotation/ErrorCode404.java +++ b/src/main/java/com/debatetimer/swagger/annotation/ErrorCode404.java @@ -1,5 +1,6 @@ package com.debatetimer.swagger.annotation; +import com.debatetimer.controller.exception.ErrorResponse; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; @@ -14,7 +15,7 @@ @Retention(RetentionPolicy.RUNTIME) @ApiResponse( responseCode = "404", - content = @Content(schema = @Schema(implementation = ProblemDetail.class)) + content = @Content(schema = @Schema(implementation = ErrorResponse.class)) ) public @interface ErrorCode404 { diff --git a/src/main/java/com/debatetimer/swagger/annotation/ErrorCode500.java b/src/main/java/com/debatetimer/swagger/annotation/ErrorCode500.java index d4d96ec..b2d94a3 100644 --- a/src/main/java/com/debatetimer/swagger/annotation/ErrorCode500.java +++ b/src/main/java/com/debatetimer/swagger/annotation/ErrorCode500.java @@ -1,5 +1,6 @@ package com.debatetimer.swagger.annotation; +import com.debatetimer.controller.exception.ErrorResponse; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; @@ -15,7 +16,7 @@ @ApiResponse( responseCode = "500", description = "서버 오류", - content = @Content(schema = @Schema(implementation = ProblemDetail.class)) + content = @Content(schema = @Schema(implementation = ErrorResponse.class)) ) public @interface ErrorCode500 { From da3076512e0928c62f50a3e356f6db318a20478c Mon Sep 17 00:00:00 2001 From: coli-geonwoo Date: Sat, 21 Dec 2024 23:28:46 +0900 Subject: [PATCH 03/20] =?UTF-8?q?chore:=20gitkeep=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/debatetimer/controller/.gitkeep | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/main/java/com/debatetimer/controller/.gitkeep diff --git a/src/main/java/com/debatetimer/controller/.gitkeep b/src/main/java/com/debatetimer/controller/.gitkeep deleted file mode 100644 index e69de29..0000000 From 9f1486fe4e2d00a25f3a7e695f75154a1a47e7fd Mon Sep 17 00:00:00 2001 From: coli-geonwoo Date: Sat, 21 Dec 2024 23:29:34 +0900 Subject: [PATCH 04/20] =?UTF-8?q?feat:=20=EC=BB=A4=EC=8A=A4=ED=85=80=20?= =?UTF-8?q?=EC=97=90=EB=9F=AC=20=EA=B0=9D=EC=B2=B4=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../exception/custom/DTBadRequestException.java | 10 ++++++++++ .../controller/exception/custom/DTException.java | 15 +++++++++++++++ .../exception/custom/DTNotFoundException.java | 10 ++++++++++ .../exception/custom/DTServerErrorException.java | 10 ++++++++++ .../exception/custom/DTUnauthorizedException.java | 10 ++++++++++ 5 files changed, 55 insertions(+) create mode 100644 src/main/java/com/debatetimer/controller/exception/custom/DTBadRequestException.java create mode 100644 src/main/java/com/debatetimer/controller/exception/custom/DTException.java create mode 100644 src/main/java/com/debatetimer/controller/exception/custom/DTNotFoundException.java create mode 100644 src/main/java/com/debatetimer/controller/exception/custom/DTServerErrorException.java create mode 100644 src/main/java/com/debatetimer/controller/exception/custom/DTUnauthorizedException.java diff --git a/src/main/java/com/debatetimer/controller/exception/custom/DTBadRequestException.java b/src/main/java/com/debatetimer/controller/exception/custom/DTBadRequestException.java new file mode 100644 index 0000000..44ee875 --- /dev/null +++ b/src/main/java/com/debatetimer/controller/exception/custom/DTBadRequestException.java @@ -0,0 +1,10 @@ +package com.debatetimer.controller.exception.custom; + +import org.springframework.http.HttpStatus; + +public class DTBadRequestException extends DTException { + + public DTBadRequestException(String message) { + super(message, HttpStatus.BAD_REQUEST); + } +} diff --git a/src/main/java/com/debatetimer/controller/exception/custom/DTException.java b/src/main/java/com/debatetimer/controller/exception/custom/DTException.java new file mode 100644 index 0000000..ae4e0bc --- /dev/null +++ b/src/main/java/com/debatetimer/controller/exception/custom/DTException.java @@ -0,0 +1,15 @@ +package com.debatetimer.controller.exception.custom; + +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +public abstract class DTException extends RuntimeException { + + private final HttpStatus httpStatus; + + public DTException(String message, HttpStatus httpStatus) { + super(message); + this.httpStatus = httpStatus; + } +} diff --git a/src/main/java/com/debatetimer/controller/exception/custom/DTNotFoundException.java b/src/main/java/com/debatetimer/controller/exception/custom/DTNotFoundException.java new file mode 100644 index 0000000..8c622d1 --- /dev/null +++ b/src/main/java/com/debatetimer/controller/exception/custom/DTNotFoundException.java @@ -0,0 +1,10 @@ +package com.debatetimer.controller.exception.custom; + +import org.springframework.http.HttpStatus; + +public class DTNotFoundException extends DTException { + + public DTNotFoundException(String message) { + super(message, HttpStatus.NOT_FOUND); + } +} diff --git a/src/main/java/com/debatetimer/controller/exception/custom/DTServerErrorException.java b/src/main/java/com/debatetimer/controller/exception/custom/DTServerErrorException.java new file mode 100644 index 0000000..79ae934 --- /dev/null +++ b/src/main/java/com/debatetimer/controller/exception/custom/DTServerErrorException.java @@ -0,0 +1,10 @@ +package com.debatetimer.controller.exception.custom; + +import org.springframework.http.HttpStatus; + +public class DTServerErrorException extends DTException { + + public DTServerErrorException(String message) { + super(message, HttpStatus.INTERNAL_SERVER_ERROR); + } +} diff --git a/src/main/java/com/debatetimer/controller/exception/custom/DTUnauthorizedException.java b/src/main/java/com/debatetimer/controller/exception/custom/DTUnauthorizedException.java new file mode 100644 index 0000000..b5d2050 --- /dev/null +++ b/src/main/java/com/debatetimer/controller/exception/custom/DTUnauthorizedException.java @@ -0,0 +1,10 @@ +package com.debatetimer.controller.exception.custom; + +import org.springframework.http.HttpStatus; + +public class DTUnauthorizedException extends DTException { + + public DTUnauthorizedException(String message) { + super(message, HttpStatus.UNAUTHORIZED); + } +} From 95de60951aedad29aa384e468002ec38d760c971 Mon Sep 17 00:00:00 2001 From: coli-geonwoo Date: Sat, 21 Dec 2024 23:29:52 +0900 Subject: [PATCH 05/20] =?UTF-8?q?feat:=20=EC=98=88=EC=99=B8=20=ED=95=B8?= =?UTF-8?q?=EB=93=A4=EB=9F=AC=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../handler/GlobalExceptionHandler.java | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 src/main/java/com/debatetimer/controller/exception/handler/GlobalExceptionHandler.java diff --git a/src/main/java/com/debatetimer/controller/exception/handler/GlobalExceptionHandler.java b/src/main/java/com/debatetimer/controller/exception/handler/GlobalExceptionHandler.java new file mode 100644 index 0000000..e61af7f --- /dev/null +++ b/src/main/java/com/debatetimer/controller/exception/handler/GlobalExceptionHandler.java @@ -0,0 +1,51 @@ +package com.debatetimer.controller.exception.handler; + +import com.debatetimer.controller.exception.ErrorResponse; +import com.debatetimer.controller.exception.custom.DTException; +import java.util.stream.Collectors; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.support.DefaultMessageSourceResolvable; +import org.springframework.http.HttpStatus; +import org.springframework.http.ProblemDetail; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.servlet.resource.NoResourceFoundException; + +@Slf4j +@ControllerAdvice +public class GlobalExceptionHandler { + + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity handleMethodArgumentNotValidException(MethodArgumentNotValidException exception) { + String exceptionMessage = exception.getAllErrors().stream() + .map(DefaultMessageSourceResolvable::getDefaultMessage) + .collect(Collectors.joining(" | ")); + log.warn("message: {}", exceptionMessage); + return toResponse(HttpStatus.BAD_REQUEST, exception.getMessage()); + } + + @ExceptionHandler(NoResourceFoundException.class) + public ResponseEntity handleNoResourceFoundException(NoResourceFoundException exception) { + return toResponse(HttpStatus.NOT_FOUND, exception.getMessage()); + } + + @ExceptionHandler(DTException.class) + public ResponseEntity handleCustomException(DTException exception) { + log.warn("message: {}", exception.getMessage()); + return toResponse(exception.getHttpStatus(), exception.getMessage()); + } + + @ExceptionHandler(Exception.class) + public ProblemDetail handleException(Exception exception) { + log.error("exception: {}", exception); + return ProblemDetail.forStatusAndDetail(HttpStatus.INTERNAL_SERVER_ERROR, "서버 에러"); + } + + private ResponseEntity toResponse(HttpStatus status, String message) { + ErrorResponse errorResponse = new ErrorResponse(message); + return ResponseEntity.status(status) + .body(errorResponse); + } +} From 9f7e655b93b0c16af01e7f44268fd9166ea45ff1 Mon Sep 17 00:00:00 2001 From: coli-geonwoo Date: Sat, 21 Dec 2024 23:46:44 +0900 Subject: [PATCH 06/20] =?UTF-8?q?feat:=20=EB=8D=B0=EC=9D=B4=ED=84=B0?= =?UTF-8?q?=EB=B2=A0=EC=9D=B4=EC=8A=A4=20=ED=81=B4=EB=A6=AC=EB=84=88=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/debatetimer/DataBaseCleaner.java | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 src/test/java/com/debatetimer/DataBaseCleaner.java diff --git a/src/test/java/com/debatetimer/DataBaseCleaner.java b/src/test/java/com/debatetimer/DataBaseCleaner.java new file mode 100644 index 0000000..ac71b5c --- /dev/null +++ b/src/test/java/com/debatetimer/DataBaseCleaner.java @@ -0,0 +1,48 @@ +package com.debatetimer; + +import jakarta.persistence.EntityManager; +import java.util.List; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.springframework.context.ApplicationContext; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.transaction.support.TransactionTemplate; + +public class DataBaseCleaner implements BeforeEachCallback { + + @Override + public void beforeEach(ExtensionContext extensionContext) throws Exception { + ApplicationContext context = SpringExtension.getApplicationContext(extensionContext); + cleanup(context); + } + + private void cleanup(ApplicationContext context) { + EntityManager em = context.getBean(EntityManager.class); + TransactionTemplate transactionTemplate = context.getBean(TransactionTemplate.class); + + transactionTemplate.execute(action -> { + em.clear(); + truncateTables(em); + return null; + }); + } + + private void truncateTables(EntityManager em) { + em.createNativeQuery("SET REFERENTIAL_INTEGRITY FALSE").executeUpdate(); + for (String tableName : findTableNames(em)) { + em.createNativeQuery("TRUNCATE TABLE %s RESTART IDENTITY".formatted(tableName)).executeUpdate(); + } + em.createNativeQuery("SET REFERENTIAL_INTEGRITY TRUE").executeUpdate(); + } + + @SuppressWarnings("unchecked") + private List findTableNames(EntityManager em) { + String tableNameSelectQuery = """ + SELECT TABLE_NAME + FROM INFORMATION_SCHEMA.TABLES + WHERE TABLE_SCHEMA = 'PUBLIC' + """; + return em.createNativeQuery(tableNameSelectQuery).getResultList(); + } +} + From 82640a4fcfc84159b11e92e7716a09988a0479fb Mon Sep 17 00:00:00 2001 From: coli-geonwoo Date: Sun, 22 Dec 2024 00:38:21 +0900 Subject: [PATCH 07/20] =?UTF-8?q?feat:=20BaseControllerTest=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/debatetimer/BaseControllerTest.java | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 src/test/java/com/debatetimer/BaseControllerTest.java diff --git a/src/test/java/com/debatetimer/BaseControllerTest.java b/src/test/java/com/debatetimer/BaseControllerTest.java new file mode 100644 index 0000000..0da5d7f --- /dev/null +++ b/src/test/java/com/debatetimer/BaseControllerTest.java @@ -0,0 +1,22 @@ +package com.debatetimer; + +import io.restassured.RestAssured; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.test.context.ActiveProfiles; + +@ExtendWith(DataBaseCleaner.class) +@ActiveProfiles("test") +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public abstract class BaseControllerTest { + + @LocalServerPort + private int port; + + @BeforeEach + void setPort() { + RestAssured.port = port; + } +} From 65f7a46a45c073955f48985d87c464dfbade363c Mon Sep 17 00:00:00 2001 From: coli-geonwoo Date: Sun, 22 Dec 2024 00:39:08 +0900 Subject: [PATCH 08/20] =?UTF-8?q?feat:=20=EC=98=88=EC=99=B8=20=ED=95=B8?= =?UTF-8?q?=EB=93=A4=EB=9F=AC=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../handler/GlobalExceptionHandlerTest.java | 125 ++++++++++++++++++ .../exception/handler/TestController.java | 14 ++ 2 files changed, 139 insertions(+) create mode 100644 src/test/java/com/debatetimer/controller/exception/handler/GlobalExceptionHandlerTest.java create mode 100644 src/test/java/com/debatetimer/controller/exception/handler/TestController.java diff --git a/src/test/java/com/debatetimer/controller/exception/handler/GlobalExceptionHandlerTest.java b/src/test/java/com/debatetimer/controller/exception/handler/GlobalExceptionHandlerTest.java new file mode 100644 index 0000000..41bd474 --- /dev/null +++ b/src/test/java/com/debatetimer/controller/exception/handler/GlobalExceptionHandlerTest.java @@ -0,0 +1,125 @@ +package com.debatetimer.controller.exception.handler; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; + +import com.debatetimer.BaseControllerTest; +import com.debatetimer.controller.exception.ErrorResponse; +import com.debatetimer.controller.exception.custom.DTBadRequestException; +import com.debatetimer.controller.exception.custom.DTException; +import com.debatetimer.controller.exception.custom.DTNotFoundException; +import com.debatetimer.controller.exception.custom.DTServerErrorException; +import com.debatetimer.controller.exception.custom.DTUnauthorizedException; +import io.restassured.RestAssured; +import io.restassured.http.ContentType; +import java.util.List; +import java.util.stream.Stream; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.springframework.http.HttpStatus; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.servlet.resource.NoResourceFoundException; + +class GlobalExceptionHandlerTest extends BaseControllerTest { + + @MockitoBean + private TestController testController; + + @Nested + class handleMethodArgumentNotValidException { + + @Test + void 상태코드_400_을_반환한다() throws Exception { + MethodArgumentNotValidException exception = mock(MethodArgumentNotValidException.class); + doReturn(List.of()) + .when(exception) + .getAllErrors(); + + doThrow(exception) + .when(testController) + .testMethod(); + + RestAssured.given().log().all() + .contentType(ContentType.JSON) + .when() + .get("/test") + .then() + .statusCode(400); + } + } + + @Nested + class handleNoResourceFoundException { + + @Test + void 상태코드_404를_반환한다() throws Exception { + doThrow(NoResourceFoundException.class) + .when(testController) + .testMethod(); + + RestAssured.given().log().all() + .contentType(ContentType.JSON) + .when() + .get("/test") + .then() + .statusCode(HttpStatus.NOT_FOUND.value()); + } + } + + @Nested + class handleException { + + @Test + void 상태코드_500을_반환한다() throws Exception { + doThrow(Exception.class) + .when(testController) + .testMethod(); + + RestAssured.given().log().all() + .contentType(ContentType.JSON) + .when() + .get("/test") + .then() + .statusCode(HttpStatus.INTERNAL_SERVER_ERROR.value()); + } + } + + @Nested + class handleCustomException { + + @ParameterizedTest + @MethodSource("customExceptionProvider") + void 커스텀_예외의_상태코드와_메시지를_담은_응답객체를_반환한다(DTException dtException) throws Exception { + int expectedCode = dtException.getHttpStatus().value(); + String expectedMessage = dtException.getMessage(); + doThrow(dtException) + .when(testController) + .testMethod(); + + ErrorResponse actualResponse = RestAssured.given().log().all() + .contentType(ContentType.JSON) + .when() + .get("/test") + .then() + .statusCode(expectedCode) + .extract() + .as(ErrorResponse.class); + + assertThat(actualResponse.message()).isEqualTo(expectedMessage); + } + + static Stream customExceptionProvider() { + return Stream.of( + new DTBadRequestException("badRequestException"), + new DTUnauthorizedException("unAuthorizedException"), + new DTServerErrorException("serverErrorException"), + new DTNotFoundException("notFoundException") + ); + } + } +} diff --git a/src/test/java/com/debatetimer/controller/exception/handler/TestController.java b/src/test/java/com/debatetimer/controller/exception/handler/TestController.java new file mode 100644 index 0000000..cab8f23 --- /dev/null +++ b/src/test/java/com/debatetimer/controller/exception/handler/TestController.java @@ -0,0 +1,14 @@ +package com.debatetimer.controller.exception.handler; + +import org.springframework.context.annotation.Profile; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@Profile("test") +@RestController +public class TestController { + + @GetMapping("/test") + void testMethod() throws Exception { + } +} From 2bd899af1dbdd126e126ca0cfb0d0e092ebba55e Mon Sep 17 00:00:00 2001 From: coli-geonwoo Date: Mon, 23 Dec 2024 01:34:19 +0900 Subject: [PATCH 09/20] =?UTF-8?q?test:=20handlerTest=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../exception/custom/DTNotFoundException.java | 10 -- .../custom/DTUnauthorizedException.java | 10 -- .../com/debatetimer/BaseControllerTest.java | 22 --- .../java/com/debatetimer/DataBaseCleaner.java | 48 ------- .../handler/GlobalExceptionHandlerTest.java | 125 ------------------ .../exception/handler/TestController.java | 14 -- 6 files changed, 229 deletions(-) delete mode 100644 src/main/java/com/debatetimer/controller/exception/custom/DTNotFoundException.java delete mode 100644 src/main/java/com/debatetimer/controller/exception/custom/DTUnauthorizedException.java delete mode 100644 src/test/java/com/debatetimer/BaseControllerTest.java delete mode 100644 src/test/java/com/debatetimer/DataBaseCleaner.java delete mode 100644 src/test/java/com/debatetimer/controller/exception/handler/GlobalExceptionHandlerTest.java delete mode 100644 src/test/java/com/debatetimer/controller/exception/handler/TestController.java diff --git a/src/main/java/com/debatetimer/controller/exception/custom/DTNotFoundException.java b/src/main/java/com/debatetimer/controller/exception/custom/DTNotFoundException.java deleted file mode 100644 index 8c622d1..0000000 --- a/src/main/java/com/debatetimer/controller/exception/custom/DTNotFoundException.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.debatetimer.controller.exception.custom; - -import org.springframework.http.HttpStatus; - -public class DTNotFoundException extends DTException { - - public DTNotFoundException(String message) { - super(message, HttpStatus.NOT_FOUND); - } -} diff --git a/src/main/java/com/debatetimer/controller/exception/custom/DTUnauthorizedException.java b/src/main/java/com/debatetimer/controller/exception/custom/DTUnauthorizedException.java deleted file mode 100644 index b5d2050..0000000 --- a/src/main/java/com/debatetimer/controller/exception/custom/DTUnauthorizedException.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.debatetimer.controller.exception.custom; - -import org.springframework.http.HttpStatus; - -public class DTUnauthorizedException extends DTException { - - public DTUnauthorizedException(String message) { - super(message, HttpStatus.UNAUTHORIZED); - } -} diff --git a/src/test/java/com/debatetimer/BaseControllerTest.java b/src/test/java/com/debatetimer/BaseControllerTest.java deleted file mode 100644 index 0da5d7f..0000000 --- a/src/test/java/com/debatetimer/BaseControllerTest.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.debatetimer; - -import io.restassured.RestAssured; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.extension.ExtendWith; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.web.server.LocalServerPort; -import org.springframework.test.context.ActiveProfiles; - -@ExtendWith(DataBaseCleaner.class) -@ActiveProfiles("test") -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -public abstract class BaseControllerTest { - - @LocalServerPort - private int port; - - @BeforeEach - void setPort() { - RestAssured.port = port; - } -} diff --git a/src/test/java/com/debatetimer/DataBaseCleaner.java b/src/test/java/com/debatetimer/DataBaseCleaner.java deleted file mode 100644 index ac71b5c..0000000 --- a/src/test/java/com/debatetimer/DataBaseCleaner.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.debatetimer; - -import jakarta.persistence.EntityManager; -import java.util.List; -import org.junit.jupiter.api.extension.BeforeEachCallback; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.springframework.context.ApplicationContext; -import org.springframework.test.context.junit.jupiter.SpringExtension; -import org.springframework.transaction.support.TransactionTemplate; - -public class DataBaseCleaner implements BeforeEachCallback { - - @Override - public void beforeEach(ExtensionContext extensionContext) throws Exception { - ApplicationContext context = SpringExtension.getApplicationContext(extensionContext); - cleanup(context); - } - - private void cleanup(ApplicationContext context) { - EntityManager em = context.getBean(EntityManager.class); - TransactionTemplate transactionTemplate = context.getBean(TransactionTemplate.class); - - transactionTemplate.execute(action -> { - em.clear(); - truncateTables(em); - return null; - }); - } - - private void truncateTables(EntityManager em) { - em.createNativeQuery("SET REFERENTIAL_INTEGRITY FALSE").executeUpdate(); - for (String tableName : findTableNames(em)) { - em.createNativeQuery("TRUNCATE TABLE %s RESTART IDENTITY".formatted(tableName)).executeUpdate(); - } - em.createNativeQuery("SET REFERENTIAL_INTEGRITY TRUE").executeUpdate(); - } - - @SuppressWarnings("unchecked") - private List findTableNames(EntityManager em) { - String tableNameSelectQuery = """ - SELECT TABLE_NAME - FROM INFORMATION_SCHEMA.TABLES - WHERE TABLE_SCHEMA = 'PUBLIC' - """; - return em.createNativeQuery(tableNameSelectQuery).getResultList(); - } -} - diff --git a/src/test/java/com/debatetimer/controller/exception/handler/GlobalExceptionHandlerTest.java b/src/test/java/com/debatetimer/controller/exception/handler/GlobalExceptionHandlerTest.java deleted file mode 100644 index 41bd474..0000000 --- a/src/test/java/com/debatetimer/controller/exception/handler/GlobalExceptionHandlerTest.java +++ /dev/null @@ -1,125 +0,0 @@ -package com.debatetimer.controller.exception.handler; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.mock; - -import com.debatetimer.BaseControllerTest; -import com.debatetimer.controller.exception.ErrorResponse; -import com.debatetimer.controller.exception.custom.DTBadRequestException; -import com.debatetimer.controller.exception.custom.DTException; -import com.debatetimer.controller.exception.custom.DTNotFoundException; -import com.debatetimer.controller.exception.custom.DTServerErrorException; -import com.debatetimer.controller.exception.custom.DTUnauthorizedException; -import io.restassured.RestAssured; -import io.restassured.http.ContentType; -import java.util.List; -import java.util.stream.Stream; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.MethodSource; -import org.springframework.http.HttpStatus; -import org.springframework.test.context.bean.override.mockito.MockitoBean; -import org.springframework.web.bind.MethodArgumentNotValidException; -import org.springframework.web.servlet.resource.NoResourceFoundException; - -class GlobalExceptionHandlerTest extends BaseControllerTest { - - @MockitoBean - private TestController testController; - - @Nested - class handleMethodArgumentNotValidException { - - @Test - void 상태코드_400_을_반환한다() throws Exception { - MethodArgumentNotValidException exception = mock(MethodArgumentNotValidException.class); - doReturn(List.of()) - .when(exception) - .getAllErrors(); - - doThrow(exception) - .when(testController) - .testMethod(); - - RestAssured.given().log().all() - .contentType(ContentType.JSON) - .when() - .get("/test") - .then() - .statusCode(400); - } - } - - @Nested - class handleNoResourceFoundException { - - @Test - void 상태코드_404를_반환한다() throws Exception { - doThrow(NoResourceFoundException.class) - .when(testController) - .testMethod(); - - RestAssured.given().log().all() - .contentType(ContentType.JSON) - .when() - .get("/test") - .then() - .statusCode(HttpStatus.NOT_FOUND.value()); - } - } - - @Nested - class handleException { - - @Test - void 상태코드_500을_반환한다() throws Exception { - doThrow(Exception.class) - .when(testController) - .testMethod(); - - RestAssured.given().log().all() - .contentType(ContentType.JSON) - .when() - .get("/test") - .then() - .statusCode(HttpStatus.INTERNAL_SERVER_ERROR.value()); - } - } - - @Nested - class handleCustomException { - - @ParameterizedTest - @MethodSource("customExceptionProvider") - void 커스텀_예외의_상태코드와_메시지를_담은_응답객체를_반환한다(DTException dtException) throws Exception { - int expectedCode = dtException.getHttpStatus().value(); - String expectedMessage = dtException.getMessage(); - doThrow(dtException) - .when(testController) - .testMethod(); - - ErrorResponse actualResponse = RestAssured.given().log().all() - .contentType(ContentType.JSON) - .when() - .get("/test") - .then() - .statusCode(expectedCode) - .extract() - .as(ErrorResponse.class); - - assertThat(actualResponse.message()).isEqualTo(expectedMessage); - } - - static Stream customExceptionProvider() { - return Stream.of( - new DTBadRequestException("badRequestException"), - new DTUnauthorizedException("unAuthorizedException"), - new DTServerErrorException("serverErrorException"), - new DTNotFoundException("notFoundException") - ); - } - } -} diff --git a/src/test/java/com/debatetimer/controller/exception/handler/TestController.java b/src/test/java/com/debatetimer/controller/exception/handler/TestController.java deleted file mode 100644 index cab8f23..0000000 --- a/src/test/java/com/debatetimer/controller/exception/handler/TestController.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.debatetimer.controller.exception.handler; - -import org.springframework.context.annotation.Profile; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; - -@Profile("test") -@RestController -public class TestController { - - @GetMapping("/test") - void testMethod() throws Exception { - } -} From 5e0e68a7b6aaecc965d22a4fab9fcaf32fff6b4f Mon Sep 17 00:00:00 2001 From: coli-geonwoo Date: Mon, 23 Dec 2024 01:37:42 +0900 Subject: [PATCH 10/20] =?UTF-8?q?refactor:=20errorCode=EB=A1=9C=20?= =?UTF-8?q?=ED=83=80=EC=9E=85=EA=B3=84=EC=B8=B5=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../exception/custom/DTBadRequestException.java | 10 ---------- .../custom/DTClientErrorException.java | 10 ++++++++++ .../custom/DTServerErrorException.java | 6 +++--- .../exception/errorcode/ClientErrorCode.java | 17 +++++++++++++++++ .../exception/errorcode/ServerErrorCode.java | 17 +++++++++++++++++ 5 files changed, 47 insertions(+), 13 deletions(-) delete mode 100644 src/main/java/com/debatetimer/controller/exception/custom/DTBadRequestException.java create mode 100644 src/main/java/com/debatetimer/controller/exception/custom/DTClientErrorException.java create mode 100644 src/main/java/com/debatetimer/controller/exception/errorcode/ClientErrorCode.java create mode 100644 src/main/java/com/debatetimer/controller/exception/errorcode/ServerErrorCode.java diff --git a/src/main/java/com/debatetimer/controller/exception/custom/DTBadRequestException.java b/src/main/java/com/debatetimer/controller/exception/custom/DTBadRequestException.java deleted file mode 100644 index 44ee875..0000000 --- a/src/main/java/com/debatetimer/controller/exception/custom/DTBadRequestException.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.debatetimer.controller.exception.custom; - -import org.springframework.http.HttpStatus; - -public class DTBadRequestException extends DTException { - - public DTBadRequestException(String message) { - super(message, HttpStatus.BAD_REQUEST); - } -} diff --git a/src/main/java/com/debatetimer/controller/exception/custom/DTClientErrorException.java b/src/main/java/com/debatetimer/controller/exception/custom/DTClientErrorException.java new file mode 100644 index 0000000..f9dd78a --- /dev/null +++ b/src/main/java/com/debatetimer/controller/exception/custom/DTClientErrorException.java @@ -0,0 +1,10 @@ +package com.debatetimer.controller.exception.custom; + +import com.debatetimer.controller.exception.errorcode.ClientErrorCode; + +public class DTClientErrorException extends DTException { + + public DTClientErrorException(ClientErrorCode clientErrorCode) { + super(clientErrorCode.getMessage(), clientErrorCode.getStatus()); + } +} diff --git a/src/main/java/com/debatetimer/controller/exception/custom/DTServerErrorException.java b/src/main/java/com/debatetimer/controller/exception/custom/DTServerErrorException.java index 79ae934..3a46ac8 100644 --- a/src/main/java/com/debatetimer/controller/exception/custom/DTServerErrorException.java +++ b/src/main/java/com/debatetimer/controller/exception/custom/DTServerErrorException.java @@ -1,10 +1,10 @@ package com.debatetimer.controller.exception.custom; -import org.springframework.http.HttpStatus; +import com.debatetimer.controller.exception.errorcode.ServerErrorCode; public class DTServerErrorException extends DTException { - public DTServerErrorException(String message) { - super(message, HttpStatus.INTERNAL_SERVER_ERROR); + public DTServerErrorException(ServerErrorCode serverErrorCode) { + super(serverErrorCode.getMessage(), serverErrorCode.getStatus()); } } diff --git a/src/main/java/com/debatetimer/controller/exception/errorcode/ClientErrorCode.java b/src/main/java/com/debatetimer/controller/exception/errorcode/ClientErrorCode.java new file mode 100644 index 0000000..4e835ae --- /dev/null +++ b/src/main/java/com/debatetimer/controller/exception/errorcode/ClientErrorCode.java @@ -0,0 +1,17 @@ +package com.debatetimer.controller.exception.errorcode; + +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +public enum ClientErrorCode { + ; + + private final HttpStatus status; + private final String message; + + ClientErrorCode(HttpStatus status, String message) { + this.status = status; + this.message = message; + } +} diff --git a/src/main/java/com/debatetimer/controller/exception/errorcode/ServerErrorCode.java b/src/main/java/com/debatetimer/controller/exception/errorcode/ServerErrorCode.java new file mode 100644 index 0000000..a34c47a --- /dev/null +++ b/src/main/java/com/debatetimer/controller/exception/errorcode/ServerErrorCode.java @@ -0,0 +1,17 @@ +package com.debatetimer.controller.exception.errorcode; + +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +public enum ServerErrorCode { + ; + + private final HttpStatus status; + private final String message; + + ServerErrorCode(HttpStatus status, String message) { + this.status = status; + this.message = message; + } +} From a41fa9dd6f41557ac73c49e52e1eeca537c29139 Mon Sep 17 00:00:00 2001 From: coli-geonwoo Date: Mon, 23 Dec 2024 01:40:10 +0900 Subject: [PATCH 11/20] =?UTF-8?q?refactor:=20=EC=97=90=EB=9F=AC=20?= =?UTF-8?q?=ED=95=B8=EB=93=A4=EB=A7=81=20=EC=84=B8=EB=B6=84=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../handler/GlobalExceptionHandler.java | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/debatetimer/controller/exception/handler/GlobalExceptionHandler.java b/src/main/java/com/debatetimer/controller/exception/handler/GlobalExceptionHandler.java index e61af7f..fb2992d 100644 --- a/src/main/java/com/debatetimer/controller/exception/handler/GlobalExceptionHandler.java +++ b/src/main/java/com/debatetimer/controller/exception/handler/GlobalExceptionHandler.java @@ -1,12 +1,12 @@ package com.debatetimer.controller.exception.handler; import com.debatetimer.controller.exception.ErrorResponse; -import com.debatetimer.controller.exception.custom.DTException; +import com.debatetimer.controller.exception.custom.DTClientErrorException; +import com.debatetimer.controller.exception.custom.DTServerErrorException; import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; import org.springframework.context.support.DefaultMessageSourceResolvable; import org.springframework.http.HttpStatus; -import org.springframework.http.ProblemDetail; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ControllerAdvice; @@ -18,7 +18,8 @@ public class GlobalExceptionHandler { @ExceptionHandler(MethodArgumentNotValidException.class) - public ResponseEntity handleMethodArgumentNotValidException(MethodArgumentNotValidException exception) { + public ResponseEntity handleMethodArgumentNotValidException( + MethodArgumentNotValidException exception) { String exceptionMessage = exception.getAllErrors().stream() .map(DefaultMessageSourceResolvable::getDefaultMessage) .collect(Collectors.joining(" | ")); @@ -31,16 +32,22 @@ public ResponseEntity handleNoResourceFoundException(NoResourceFo return toResponse(HttpStatus.NOT_FOUND, exception.getMessage()); } - @ExceptionHandler(DTException.class) - public ResponseEntity handleCustomException(DTException exception) { + @ExceptionHandler(DTClientErrorException.class) + public ResponseEntity handleClientException(DTServerErrorException exception) { + log.warn("message: {}", exception.getMessage()); + return toResponse(exception.getHttpStatus(), exception.getMessage()); + } + + @ExceptionHandler(DTServerErrorException.class) + public ResponseEntity handleServerException(DTServerErrorException exception) { log.warn("message: {}", exception.getMessage()); return toResponse(exception.getHttpStatus(), exception.getMessage()); } @ExceptionHandler(Exception.class) - public ProblemDetail handleException(Exception exception) { + public ResponseEntity handleException(Exception exception) { log.error("exception: {}", exception); - return ProblemDetail.forStatusAndDetail(HttpStatus.INTERNAL_SERVER_ERROR, "서버 에러"); + return toResponse(HttpStatus.INTERNAL_SERVER_ERROR, "서버 내부 오류"); } private ResponseEntity toResponse(HttpStatus status, String message) { From 280135f80f22dfc45261bce7a6f63af8f3cf96e7 Mon Sep 17 00:00:00 2001 From: coli-geonwoo Date: Mon, 23 Dec 2024 02:20:51 +0900 Subject: [PATCH 12/20] =?UTF-8?q?refactor:=20=ED=95=B8=EB=93=A4=EB=A7=81?= =?UTF-8?q?=20=EC=97=90=EB=9F=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../handler/GlobalExceptionHandler.java | 49 ++++++++++++++++++- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/debatetimer/controller/exception/handler/GlobalExceptionHandler.java b/src/main/java/com/debatetimer/controller/exception/handler/GlobalExceptionHandler.java index fb2992d..0902813 100644 --- a/src/main/java/com/debatetimer/controller/exception/handler/GlobalExceptionHandler.java +++ b/src/main/java/com/debatetimer/controller/exception/handler/GlobalExceptionHandler.java @@ -3,18 +3,24 @@ import com.debatetimer.controller.exception.ErrorResponse; import com.debatetimer.controller.exception.custom.DTClientErrorException; import com.debatetimer.controller.exception.custom.DTServerErrorException; +import jakarta.validation.ConstraintViolationException; import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; +import org.apache.catalina.connector.ClientAbortException; import org.springframework.context.support.DefaultMessageSourceResolvable; 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; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; import org.springframework.web.servlet.resource.NoResourceFoundException; @Slf4j -@ControllerAdvice +@RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(MethodArgumentNotValidException.class) @@ -27,6 +33,45 @@ public ResponseEntity handleMethodArgumentNotValidException( return toResponse(HttpStatus.BAD_REQUEST, exception.getMessage()); } + @ExceptionHandler(BindException.class) + public ResponseEntity handleBindingException(BindException exception) { + log.warn("message: {}", exception.getMessage()); + return toResponse(HttpStatus.BAD_REQUEST, exception.getMessage()); + } + + @ExceptionHandler(ConstraintViolationException.class) + public ResponseEntity handleConstraintViolationException(ConstraintViolationException exception) { + log.warn("message: {}", exception.getMessage()); + return toResponse(HttpStatus.BAD_REQUEST, exception.getMessage()); + } + + @ExceptionHandler(MethodArgumentTypeMismatchException.class) + public ResponseEntity handleMethodArgumentTypeMismatchException( + MethodArgumentTypeMismatchException exception) { + log.warn("message: {}", exception.getMessage()); + return toResponse(HttpStatus.BAD_REQUEST, exception.getMessage()); + } + + @ExceptionHandler(ClientAbortException.class) + public ResponseEntity handleClientAbortException(ClientAbortException exception) { + log.warn("message: {}", exception.getMessage()); + return toResponse(HttpStatus.BAD_REQUEST, ""); + } + + @ExceptionHandler(HttpRequestMethodNotSupportedException.class) + public ResponseEntity handleHttpRequestMethodNotSupportedException( + HttpRequestMethodNotSupportedException exception) { + log.warn("message: {}", exception.getMessage()); + return toResponse(HttpStatus.METHOD_NOT_ALLOWED, ""); + } + + @ExceptionHandler() + public ResponseEntity handleHttpMediaTypeNotSupportedException( + HttpMediaTypeNotSupportedException exception) { + log.warn("message: {}", exception.getMessage()); + return toResponse(HttpStatus.UNSUPPORTED_MEDIA_TYPE, ""); + } + @ExceptionHandler(NoResourceFoundException.class) public ResponseEntity handleNoResourceFoundException(NoResourceFoundException exception) { return toResponse(HttpStatus.NOT_FOUND, exception.getMessage()); From 0caddbce786aadc7e4733f0c160438ef88840adb Mon Sep 17 00:00:00 2001 From: coli-geonwoo Date: Mon, 23 Dec 2024 02:43:08 +0900 Subject: [PATCH 13/20] =?UTF-8?q?refactor:=20ErrorCode=20=EC=9D=B8?= =?UTF-8?q?=ED=84=B0=ED=8E=98=EC=9D=B4=EC=8A=A4=EB=A1=9C=20=EC=B6=94?= =?UTF-8?q?=EC=83=81=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../exception/errorcode/ClientErrorCode.java | 10 +++++++++- .../controller/exception/errorcode/ErrorCode.java | 9 +++++++++ .../exception/errorcode/ServerErrorCode.java | 3 ++- 3 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/debatetimer/controller/exception/errorcode/ErrorCode.java diff --git a/src/main/java/com/debatetimer/controller/exception/errorcode/ClientErrorCode.java b/src/main/java/com/debatetimer/controller/exception/errorcode/ClientErrorCode.java index 4e835ae..d696ea6 100644 --- a/src/main/java/com/debatetimer/controller/exception/errorcode/ClientErrorCode.java +++ b/src/main/java/com/debatetimer/controller/exception/errorcode/ClientErrorCode.java @@ -4,7 +4,15 @@ import org.springframework.http.HttpStatus; @Getter -public enum ClientErrorCode { +public enum ClientErrorCode implements ErrorCode { + + FIELD_ERROR(HttpStatus.BAD_REQUEST, "입력이 잘못되었습니다."), + URL_PARAMETER_ERROR(HttpStatus.BAD_REQUEST, "입력이 잘못되었습니다."), + METHOD_ARGUMENT_TYPE_MISMATCH(HttpStatus.BAD_REQUEST, "입력한 값의 타입이 잘못되었습니다."), + NO_RESOURCE_FOUND(HttpStatus.NOT_FOUND, "요청한 리소스를 찾을 수 없습니다."), + METHOD_NOT_SUPPORTED(HttpStatus.METHOD_NOT_ALLOWED, "허용되지 않은 메서드입니다."), + MEDIA_TYPE_NOT_SUPPORTED(HttpStatus.UNSUPPORTED_MEDIA_TYPE, "허용되지 않은 미디어 타입입니다."), + ALREADY_DISCONNECTED(HttpStatus.BAD_REQUEST, "이미 클라이언트에서 요청이 종료되었습니다."), ; private final HttpStatus status; diff --git a/src/main/java/com/debatetimer/controller/exception/errorcode/ErrorCode.java b/src/main/java/com/debatetimer/controller/exception/errorcode/ErrorCode.java new file mode 100644 index 0000000..bf2186f --- /dev/null +++ b/src/main/java/com/debatetimer/controller/exception/errorcode/ErrorCode.java @@ -0,0 +1,9 @@ +package com.debatetimer.controller.exception.errorcode; + +import org.springframework.http.HttpStatus; + +public interface ErrorCode { + + HttpStatus getStatus(); + String getMessage(); +} diff --git a/src/main/java/com/debatetimer/controller/exception/errorcode/ServerErrorCode.java b/src/main/java/com/debatetimer/controller/exception/errorcode/ServerErrorCode.java index a34c47a..cb6182c 100644 --- a/src/main/java/com/debatetimer/controller/exception/errorcode/ServerErrorCode.java +++ b/src/main/java/com/debatetimer/controller/exception/errorcode/ServerErrorCode.java @@ -4,7 +4,8 @@ import org.springframework.http.HttpStatus; @Getter -public enum ServerErrorCode { +public enum ServerErrorCode implements ErrorCode { + INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "서버 오류가 발생했습니다. 관리자에게 문의하세요."), ; private final HttpStatus status; From 0133f9924edd7a6e8dcb543f381cc4f4761109bb Mon Sep 17 00:00:00 2001 From: coli-geonwoo Date: Mon, 23 Dec 2024 02:44:30 +0900 Subject: [PATCH 14/20] =?UTF-8?q?refactor:=20clientErrorCode=20=EB=B0=98?= =?UTF-8?q?=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../handler/GlobalExceptionHandler.java | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/debatetimer/controller/exception/handler/GlobalExceptionHandler.java b/src/main/java/com/debatetimer/controller/exception/handler/GlobalExceptionHandler.java index 0902813..2a48131 100644 --- a/src/main/java/com/debatetimer/controller/exception/handler/GlobalExceptionHandler.java +++ b/src/main/java/com/debatetimer/controller/exception/handler/GlobalExceptionHandler.java @@ -3,6 +3,9 @@ import com.debatetimer.controller.exception.ErrorResponse; import com.debatetimer.controller.exception.custom.DTClientErrorException; import com.debatetimer.controller.exception.custom.DTServerErrorException; +import com.debatetimer.controller.exception.errorcode.ClientErrorCode; +import com.debatetimer.controller.exception.errorcode.ErrorCode; +import com.debatetimer.controller.exception.errorcode.ServerErrorCode; import jakarta.validation.ConstraintViolationException; import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; @@ -36,45 +39,45 @@ public ResponseEntity handleMethodArgumentNotValidException( @ExceptionHandler(BindException.class) public ResponseEntity handleBindingException(BindException exception) { log.warn("message: {}", exception.getMessage()); - return toResponse(HttpStatus.BAD_REQUEST, exception.getMessage()); + return toResponse(ClientErrorCode.FIELD_ERROR); } @ExceptionHandler(ConstraintViolationException.class) public ResponseEntity handleConstraintViolationException(ConstraintViolationException exception) { log.warn("message: {}", exception.getMessage()); - return toResponse(HttpStatus.BAD_REQUEST, exception.getMessage()); + return toResponse(ClientErrorCode.URL_PARAMETER_ERROR); } @ExceptionHandler(MethodArgumentTypeMismatchException.class) public ResponseEntity handleMethodArgumentTypeMismatchException( MethodArgumentTypeMismatchException exception) { log.warn("message: {}", exception.getMessage()); - return toResponse(HttpStatus.BAD_REQUEST, exception.getMessage()); + return toResponse(ClientErrorCode.METHOD_ARGUMENT_TYPE_MISMATCH); } @ExceptionHandler(ClientAbortException.class) public ResponseEntity handleClientAbortException(ClientAbortException exception) { log.warn("message: {}", exception.getMessage()); - return toResponse(HttpStatus.BAD_REQUEST, ""); + return toResponse(ClientErrorCode.ALREADY_DISCONNECTED); } @ExceptionHandler(HttpRequestMethodNotSupportedException.class) public ResponseEntity handleHttpRequestMethodNotSupportedException( HttpRequestMethodNotSupportedException exception) { log.warn("message: {}", exception.getMessage()); - return toResponse(HttpStatus.METHOD_NOT_ALLOWED, ""); + return toResponse(ClientErrorCode.METHOD_NOT_SUPPORTED); } @ExceptionHandler() public ResponseEntity handleHttpMediaTypeNotSupportedException( HttpMediaTypeNotSupportedException exception) { log.warn("message: {}", exception.getMessage()); - return toResponse(HttpStatus.UNSUPPORTED_MEDIA_TYPE, ""); + return toResponse(ClientErrorCode.MEDIA_TYPE_NOT_SUPPORTED); } @ExceptionHandler(NoResourceFoundException.class) public ResponseEntity handleNoResourceFoundException(NoResourceFoundException exception) { - return toResponse(HttpStatus.NOT_FOUND, exception.getMessage()); + return toResponse(ClientErrorCode.FIELD_ERROR); } @ExceptionHandler(DTClientErrorException.class) @@ -92,12 +95,16 @@ public ResponseEntity handleServerException(DTServerErrorExceptio @ExceptionHandler(Exception.class) public ResponseEntity handleException(Exception exception) { log.error("exception: {}", exception); - return toResponse(HttpStatus.INTERNAL_SERVER_ERROR, "서버 내부 오류"); + return toResponse(ServerErrorCode.INTERNAL_SERVER_ERROR); + } + + private ResponseEntity toResponse(ErrorCode errorCode) { + return toResponse(errorCode.getStatus(), errorCode.getMessage()); } - private ResponseEntity toResponse(HttpStatus status, String message) { + private ResponseEntity toResponse(HttpStatus httpStatus, String message) { ErrorResponse errorResponse = new ErrorResponse(message); - return ResponseEntity.status(status) + return ResponseEntity.status(httpStatus) .body(errorResponse); } } From 18302a8bbc771af03d8ce35aa30952d7584591b3 Mon Sep 17 00:00:00 2001 From: coli-geonwoo Date: Mon, 23 Dec 2024 15:28:18 +0900 Subject: [PATCH 15/20] =?UTF-8?q?refactor:=20BindException=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=ED=86=B5=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../handler/GlobalExceptionHandler.java | 23 +++++-------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/src/main/java/com/debatetimer/controller/exception/handler/GlobalExceptionHandler.java b/src/main/java/com/debatetimer/controller/exception/handler/GlobalExceptionHandler.java index 2a48131..6b6036f 100644 --- a/src/main/java/com/debatetimer/controller/exception/handler/GlobalExceptionHandler.java +++ b/src/main/java/com/debatetimer/controller/exception/handler/GlobalExceptionHandler.java @@ -7,16 +7,13 @@ import com.debatetimer.controller.exception.errorcode.ErrorCode; import com.debatetimer.controller.exception.errorcode.ServerErrorCode; import jakarta.validation.ConstraintViolationException; -import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; import org.apache.catalina.connector.ClientAbortException; -import org.springframework.context.support.DefaultMessageSourceResolvable; 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.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; @@ -26,16 +23,6 @@ @RestControllerAdvice public class GlobalExceptionHandler { - @ExceptionHandler(MethodArgumentNotValidException.class) - public ResponseEntity handleMethodArgumentNotValidException( - MethodArgumentNotValidException exception) { - String exceptionMessage = exception.getAllErrors().stream() - .map(DefaultMessageSourceResolvable::getDefaultMessage) - .collect(Collectors.joining(" | ")); - log.warn("message: {}", exceptionMessage); - return toResponse(HttpStatus.BAD_REQUEST, exception.getMessage()); - } - @ExceptionHandler(BindException.class) public ResponseEntity handleBindingException(BindException exception) { log.warn("message: {}", exception.getMessage()); @@ -63,21 +50,23 @@ public ResponseEntity handleClientAbortException(ClientAbortExcep @ExceptionHandler(HttpRequestMethodNotSupportedException.class) public ResponseEntity handleHttpRequestMethodNotSupportedException( - HttpRequestMethodNotSupportedException exception) { + HttpRequestMethodNotSupportedException exception + ) { log.warn("message: {}", exception.getMessage()); return toResponse(ClientErrorCode.METHOD_NOT_SUPPORTED); } - @ExceptionHandler() + @ExceptionHandler(HttpMediaTypeNotSupportedException.class) public ResponseEntity handleHttpMediaTypeNotSupportedException( - HttpMediaTypeNotSupportedException exception) { + HttpMediaTypeNotSupportedException exception + ) { log.warn("message: {}", exception.getMessage()); return toResponse(ClientErrorCode.MEDIA_TYPE_NOT_SUPPORTED); } @ExceptionHandler(NoResourceFoundException.class) public ResponseEntity handleNoResourceFoundException(NoResourceFoundException exception) { - return toResponse(ClientErrorCode.FIELD_ERROR); + return toResponse(ClientErrorCode.NO_RESOURCE_FOUND); } @ExceptionHandler(DTClientErrorException.class) From 44bdd8c3df5e3248f6ffbf3394922cea3c829236 Mon Sep 17 00:00:00 2001 From: coli-geonwoo Date: Wed, 25 Dec 2024 01:47:44 +0900 Subject: [PATCH 16/20] =?UTF-8?q?style:=20=EA=B0=9C=ED=96=89=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/debatetimer/controller/exception/ErrorResponse.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/debatetimer/controller/exception/ErrorResponse.java b/src/main/java/com/debatetimer/controller/exception/ErrorResponse.java index 0f9f939..09defd9 100644 --- a/src/main/java/com/debatetimer/controller/exception/ErrorResponse.java +++ b/src/main/java/com/debatetimer/controller/exception/ErrorResponse.java @@ -5,6 +5,7 @@ @Schema(description = "예외 객체") public record ErrorResponse( + @Schema(description = "사용자에게 보여줄 예외 메시지") @NotBlank String message From 948f59c4d123d1e1261df8a56929df83c31b0407 Mon Sep 17 00:00:00 2001 From: coli-geonwoo Date: Fri, 27 Dec 2024 20:13:43 +0900 Subject: [PATCH 17/20] =?UTF-8?q?style:=20=EA=B0=9C=ED=96=89=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/exception/errorcode/ServerErrorCode.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/debatetimer/controller/exception/errorcode/ServerErrorCode.java b/src/main/java/com/debatetimer/controller/exception/errorcode/ServerErrorCode.java index cb6182c..c62d9b7 100644 --- a/src/main/java/com/debatetimer/controller/exception/errorcode/ServerErrorCode.java +++ b/src/main/java/com/debatetimer/controller/exception/errorcode/ServerErrorCode.java @@ -5,6 +5,7 @@ @Getter public enum ServerErrorCode implements ErrorCode { + INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "서버 오류가 발생했습니다. 관리자에게 문의하세요."), ; From b48032ddad89e2b118ad5ce283c98038b562b747 Mon Sep 17 00:00:00 2001 From: coli-geonwoo Date: Fri, 27 Dec 2024 20:39:17 +0900 Subject: [PATCH 18/20] =?UTF-8?q?fix:=20=EC=9E=98=EB=AA=BB=EB=90=9C=20erro?= =?UTF-8?q?r=20=EA=B0=9D=EC=B2=B4=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/exception/handler/GlobalExceptionHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/debatetimer/controller/exception/handler/GlobalExceptionHandler.java b/src/main/java/com/debatetimer/controller/exception/handler/GlobalExceptionHandler.java index 6b6036f..2c3258c 100644 --- a/src/main/java/com/debatetimer/controller/exception/handler/GlobalExceptionHandler.java +++ b/src/main/java/com/debatetimer/controller/exception/handler/GlobalExceptionHandler.java @@ -70,7 +70,7 @@ public ResponseEntity handleNoResourceFoundException(NoResourceFo } @ExceptionHandler(DTClientErrorException.class) - public ResponseEntity handleClientException(DTServerErrorException exception) { + public ResponseEntity handleClientException(DTClientErrorException exception) { log.warn("message: {}", exception.getMessage()); return toResponse(exception.getHttpStatus(), exception.getMessage()); } From 7a1d24dabdf6e6b577ee857d7a6e149858ff5b98 Mon Sep 17 00:00:00 2001 From: coli-geonwoo Date: Fri, 27 Dec 2024 20:49:00 +0900 Subject: [PATCH 19/20] =?UTF-8?q?style:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20import=EB=AC=B8=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../debatetimer/controller/exception/errorcode/ErrorCode.java | 1 + .../java/com/debatetimer/swagger/annotation/ErrorCode400.java | 1 - .../java/com/debatetimer/swagger/annotation/ErrorCode401.java | 1 - .../java/com/debatetimer/swagger/annotation/ErrorCode404.java | 1 - .../java/com/debatetimer/swagger/annotation/ErrorCode500.java | 1 - 5 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/main/java/com/debatetimer/controller/exception/errorcode/ErrorCode.java b/src/main/java/com/debatetimer/controller/exception/errorcode/ErrorCode.java index bf2186f..d23c7ad 100644 --- a/src/main/java/com/debatetimer/controller/exception/errorcode/ErrorCode.java +++ b/src/main/java/com/debatetimer/controller/exception/errorcode/ErrorCode.java @@ -5,5 +5,6 @@ public interface ErrorCode { HttpStatus getStatus(); + String getMessage(); } diff --git a/src/main/java/com/debatetimer/swagger/annotation/ErrorCode400.java b/src/main/java/com/debatetimer/swagger/annotation/ErrorCode400.java index 81c912d..19df8cf 100644 --- a/src/main/java/com/debatetimer/swagger/annotation/ErrorCode400.java +++ b/src/main/java/com/debatetimer/swagger/annotation/ErrorCode400.java @@ -9,7 +9,6 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.springframework.core.annotation.AliasFor; -import org.springframework.http.ProblemDetail; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) diff --git a/src/main/java/com/debatetimer/swagger/annotation/ErrorCode401.java b/src/main/java/com/debatetimer/swagger/annotation/ErrorCode401.java index 3e6e063..104f606 100644 --- a/src/main/java/com/debatetimer/swagger/annotation/ErrorCode401.java +++ b/src/main/java/com/debatetimer/swagger/annotation/ErrorCode401.java @@ -9,7 +9,6 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.springframework.core.annotation.AliasFor; -import org.springframework.http.ProblemDetail; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) diff --git a/src/main/java/com/debatetimer/swagger/annotation/ErrorCode404.java b/src/main/java/com/debatetimer/swagger/annotation/ErrorCode404.java index 6c5726b..3cbd4f0 100644 --- a/src/main/java/com/debatetimer/swagger/annotation/ErrorCode404.java +++ b/src/main/java/com/debatetimer/swagger/annotation/ErrorCode404.java @@ -9,7 +9,6 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.springframework.core.annotation.AliasFor; -import org.springframework.http.ProblemDetail; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) diff --git a/src/main/java/com/debatetimer/swagger/annotation/ErrorCode500.java b/src/main/java/com/debatetimer/swagger/annotation/ErrorCode500.java index b2d94a3..280f73f 100644 --- a/src/main/java/com/debatetimer/swagger/annotation/ErrorCode500.java +++ b/src/main/java/com/debatetimer/swagger/annotation/ErrorCode500.java @@ -9,7 +9,6 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.springframework.core.annotation.AliasFor; -import org.springframework.http.ProblemDetail; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) From 2a5a26824f2ad710f7b48af310b2f2dd6d95dd10 Mon Sep 17 00:00:00 2001 From: coli-geonwoo Date: Fri, 27 Dec 2024 20:52:51 +0900 Subject: [PATCH 20/20] =?UTF-8?q?style:=20=EA=B0=9C=ED=96=89=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/debatetimer/controller/exception/ErrorResponse.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/com/debatetimer/controller/exception/ErrorResponse.java b/src/main/java/com/debatetimer/controller/exception/ErrorResponse.java index 09defd9..0f9f939 100644 --- a/src/main/java/com/debatetimer/controller/exception/ErrorResponse.java +++ b/src/main/java/com/debatetimer/controller/exception/ErrorResponse.java @@ -5,7 +5,6 @@ @Schema(description = "예외 객체") public record ErrorResponse( - @Schema(description = "사용자에게 보여줄 예외 메시지") @NotBlank String message