From f6ed057ece9cc049ffd4bad98b28f43c22038015 Mon Sep 17 00:00:00 2001 From: Alexander Buntakov Date: Mon, 7 Jun 2021 20:06:50 +0300 Subject: [PATCH] add exception-handler-spring-web --- README.md | 4 + build.gradle.kts | 3 + exception-handler-spring-web/build.gradle.kts | 16 +++ .../handler/dto/ExceptionResolverResult.kt | 31 +++++ .../spring/EnableSpringExceptionHandler.kt | 8 ++ .../spring/advices/ExceptionHandlerAdvice.kt | 36 ++++++ .../ExceptionHandlerConfiguration.kt | 31 +++++ ...DefaultExceptionResponseBodyCreatorImpl.kt | 16 +++ .../creators/ExceptionResponseBodyCreator.kt | 9 ++ .../handler/spring/logger/FallbackLogger.kt | 11 ++ .../exception/handler/spring/logger/Logger.kt | 9 ++ .../spring/resolvers/ExceptionResolver.kt | 9 ++ .../resolvers/FallbackExceptionResolver.kt | 16 +++ .../exception/handler/TestApplication.kt | 8 ++ .../advices/ExceptionHandlerAdviceMvcTest.kt | 116 ++++++++++++++++++ .../advices/ExceptionHandlerAdviceTest.kt | 88 +++++++++++++ .../IllegalStateExceptionResolver1.kt | 23 ++++ .../IllegalStateExceptionResolver2.kt | 24 ++++ settings.gradle.kts | 1 + 19 files changed, 459 insertions(+) create mode 100644 exception-handler-spring-web/build.gradle.kts create mode 100644 exception-handler-spring-web/src/main/kotlin/ru/touchin/exception/handler/dto/ExceptionResolverResult.kt create mode 100644 exception-handler-spring-web/src/main/kotlin/ru/touchin/exception/handler/spring/EnableSpringExceptionHandler.kt create mode 100644 exception-handler-spring-web/src/main/kotlin/ru/touchin/exception/handler/spring/advices/ExceptionHandlerAdvice.kt create mode 100644 exception-handler-spring-web/src/main/kotlin/ru/touchin/exception/handler/spring/configurations/ExceptionHandlerConfiguration.kt create mode 100644 exception-handler-spring-web/src/main/kotlin/ru/touchin/exception/handler/spring/creators/DefaultExceptionResponseBodyCreatorImpl.kt create mode 100644 exception-handler-spring-web/src/main/kotlin/ru/touchin/exception/handler/spring/creators/ExceptionResponseBodyCreator.kt create mode 100644 exception-handler-spring-web/src/main/kotlin/ru/touchin/exception/handler/spring/logger/FallbackLogger.kt create mode 100644 exception-handler-spring-web/src/main/kotlin/ru/touchin/exception/handler/spring/logger/Logger.kt create mode 100644 exception-handler-spring-web/src/main/kotlin/ru/touchin/exception/handler/spring/resolvers/ExceptionResolver.kt create mode 100644 exception-handler-spring-web/src/main/kotlin/ru/touchin/exception/handler/spring/resolvers/FallbackExceptionResolver.kt create mode 100644 exception-handler-spring-web/src/test/kotlin/ru/touchin/exception/handler/TestApplication.kt create mode 100644 exception-handler-spring-web/src/test/kotlin/ru/touchin/exception/handler/spring/advices/ExceptionHandlerAdviceMvcTest.kt create mode 100644 exception-handler-spring-web/src/test/kotlin/ru/touchin/exception/handler/spring/advices/ExceptionHandlerAdviceTest.kt create mode 100644 exception-handler-spring-web/src/test/kotlin/ru/touchin/exception/handler/spring/resolvers/IllegalStateExceptionResolver1.kt create mode 100644 exception-handler-spring-web/src/test/kotlin/ru/touchin/exception/handler/spring/resolvers/IllegalStateExceptionResolver2.kt diff --git a/README.md b/README.md index 7b8f1bb..aada39f 100644 --- a/README.md +++ b/README.md @@ -71,3 +71,7 @@ ## logger-spring-web Interceptor для логирования запросов/ответов. + +## exception-handler-spring-web + +Перехватывает ошибки сервера, определяет код ошибки и возвращает их в правильный `response` diff --git a/build.gradle.kts b/build.gradle.kts index 68f5434..e593edb 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -56,6 +56,9 @@ subprojects { dependency("org.junit.jupiter:junit-jupiter-api:5.4.2") dependency("org.junit.jupiter:junit-jupiter-params:5.4.2") dependency("org.junit.jupiter:junit-jupiter-engine:5.4.2") + + dependency("com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0") + dependency("org.mockito:mockito-inline:2.13.0") } } diff --git a/exception-handler-spring-web/build.gradle.kts b/exception-handler-spring-web/build.gradle.kts new file mode 100644 index 0000000..edfda0f --- /dev/null +++ b/exception-handler-spring-web/build.gradle.kts @@ -0,0 +1,16 @@ +plugins { + id("kotlin") + id("kotlin-spring") + id("maven-publish") +} + +dependencies { + api(project(":common-spring-web")) + + implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") + + implementation("org.springframework.boot:spring-boot-starter-web") + + testImplementation("org.springframework.boot:spring-boot-starter-test") + testImplementation("com.nhaarman.mockitokotlin2:mockito-kotlin") +} diff --git a/exception-handler-spring-web/src/main/kotlin/ru/touchin/exception/handler/dto/ExceptionResolverResult.kt b/exception-handler-spring-web/src/main/kotlin/ru/touchin/exception/handler/dto/ExceptionResolverResult.kt new file mode 100644 index 0000000..58eec60 --- /dev/null +++ b/exception-handler-spring-web/src/main/kotlin/ru/touchin/exception/handler/dto/ExceptionResolverResult.kt @@ -0,0 +1,31 @@ +package ru.touchin.exception.handler.dto + +import org.springframework.http.HttpStatus +import ru.touchin.common.spring.web.dto.ApiError +import ru.touchin.common.spring.web.dto.DefaultApiError + +data class ExceptionResolverResult( + val apiError: ApiError, + val status: HttpStatus, + val exception: Exception?, +) { + + companion object { + fun createInternalError(errorMessage: String?): ExceptionResolverResult { + return ExceptionResolverResult( + apiError = DefaultApiError.createFailure(errorMessage), + status = HttpStatus.INTERNAL_SERVER_ERROR, + exception = null + ) + } + + fun createInternalError(exception: Exception?): ExceptionResolverResult { + return ExceptionResolverResult( + apiError = DefaultApiError.createFailure(exception?.message), + status = HttpStatus.INTERNAL_SERVER_ERROR, + exception = exception + ) + } + } + +} diff --git a/exception-handler-spring-web/src/main/kotlin/ru/touchin/exception/handler/spring/EnableSpringExceptionHandler.kt b/exception-handler-spring-web/src/main/kotlin/ru/touchin/exception/handler/spring/EnableSpringExceptionHandler.kt new file mode 100644 index 0000000..1a22252 --- /dev/null +++ b/exception-handler-spring-web/src/main/kotlin/ru/touchin/exception/handler/spring/EnableSpringExceptionHandler.kt @@ -0,0 +1,8 @@ +@file:Suppress("unused") +package ru.touchin.exception.handler.spring + +import org.springframework.context.annotation.Import +import ru.touchin.exception.handler.spring.configurations.ExceptionHandlerConfiguration + +@Import(value = [ExceptionHandlerConfiguration::class]) +annotation class EnableSpringExceptionHandler diff --git a/exception-handler-spring-web/src/main/kotlin/ru/touchin/exception/handler/spring/advices/ExceptionHandlerAdvice.kt b/exception-handler-spring-web/src/main/kotlin/ru/touchin/exception/handler/spring/advices/ExceptionHandlerAdvice.kt new file mode 100644 index 0000000..9f050d8 --- /dev/null +++ b/exception-handler-spring-web/src/main/kotlin/ru/touchin/exception/handler/spring/advices/ExceptionHandlerAdvice.kt @@ -0,0 +1,36 @@ +package ru.touchin.exception.handler.spring.advices + +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.ExceptionHandler +import org.springframework.web.bind.annotation.RestControllerAdvice +import ru.touchin.exception.handler.dto.ExceptionResolverResult +import ru.touchin.exception.handler.spring.creators.ExceptionResponseBodyCreator +import ru.touchin.exception.handler.spring.logger.Logger +import ru.touchin.exception.handler.spring.resolvers.ExceptionResolver + +@RestControllerAdvice +class ExceptionHandlerAdvice( + exceptionResolversList: List, + private val logger: Logger, + private val exceptionResponseBodyCreator: ExceptionResponseBodyCreator, +) { + + private val exceptionResolvers = exceptionResolversList.asSequence() + + @ExceptionHandler(Exception::class) + fun handleException( + exception: Exception, + ): ResponseEntity { + val result: ExceptionResolverResult = exceptionResolvers + .mapNotNull { it.invoke(exception) } + .firstOrNull() + ?: ExceptionResolverResult.createInternalError("Unexpected exception occurred: $exception") + + logger.log(this::class.java, result) + + val body = exceptionResponseBodyCreator(result.apiError) + + return ResponseEntity(body, result.status) + } + +} diff --git a/exception-handler-spring-web/src/main/kotlin/ru/touchin/exception/handler/spring/configurations/ExceptionHandlerConfiguration.kt b/exception-handler-spring-web/src/main/kotlin/ru/touchin/exception/handler/spring/configurations/ExceptionHandlerConfiguration.kt new file mode 100644 index 0000000..9aa4c2f --- /dev/null +++ b/exception-handler-spring-web/src/main/kotlin/ru/touchin/exception/handler/spring/configurations/ExceptionHandlerConfiguration.kt @@ -0,0 +1,31 @@ +package ru.touchin.exception.handler.spring.configurations + +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.ComponentScan +import org.springframework.context.annotation.Configuration +import ru.touchin.exception.handler.spring.creators.DefaultExceptionResponseBodyCreatorImpl +import ru.touchin.exception.handler.spring.creators.ExceptionResponseBodyCreator +import ru.touchin.exception.handler.spring.logger.FallbackLogger +import ru.touchin.exception.handler.spring.logger.Logger + +@Configuration +@ComponentScan( + "ru.touchin.exception.handler.spring.advices", + "ru.touchin.exception.handler.spring.resolvers", +) +class ExceptionHandlerConfiguration { + + @Bean + @ConditionalOnMissingBean + fun exceptionResponseBodyCreator(): ExceptionResponseBodyCreator { + return DefaultExceptionResponseBodyCreatorImpl() + } + + @Bean + @ConditionalOnMissingBean + fun logger(): Logger { + return FallbackLogger() + } + +} diff --git a/exception-handler-spring-web/src/main/kotlin/ru/touchin/exception/handler/spring/creators/DefaultExceptionResponseBodyCreatorImpl.kt b/exception-handler-spring-web/src/main/kotlin/ru/touchin/exception/handler/spring/creators/DefaultExceptionResponseBodyCreatorImpl.kt new file mode 100644 index 0000000..c3dbce3 --- /dev/null +++ b/exception-handler-spring-web/src/main/kotlin/ru/touchin/exception/handler/spring/creators/DefaultExceptionResponseBodyCreatorImpl.kt @@ -0,0 +1,16 @@ +@file:Suppress("unused") +package ru.touchin.exception.handler.spring.creators + +import ru.touchin.common.spring.web.dto.ApiError +import ru.touchin.common.spring.web.dto.DefaultApiError + +class DefaultExceptionResponseBodyCreatorImpl : ExceptionResponseBodyCreator { + + override fun invoke(apiError: ApiError): Any { + return DefaultApiError( + errorCode = apiError.errorCode, + errorMessage = apiError.errorMessage + ) + } + +} diff --git a/exception-handler-spring-web/src/main/kotlin/ru/touchin/exception/handler/spring/creators/ExceptionResponseBodyCreator.kt b/exception-handler-spring-web/src/main/kotlin/ru/touchin/exception/handler/spring/creators/ExceptionResponseBodyCreator.kt new file mode 100644 index 0000000..3429c79 --- /dev/null +++ b/exception-handler-spring-web/src/main/kotlin/ru/touchin/exception/handler/spring/creators/ExceptionResponseBodyCreator.kt @@ -0,0 +1,9 @@ +package ru.touchin.exception.handler.spring.creators + +import ru.touchin.common.spring.web.dto.ApiError + +interface ExceptionResponseBodyCreator { + + operator fun invoke(apiError: ApiError): Any? + +} diff --git a/exception-handler-spring-web/src/main/kotlin/ru/touchin/exception/handler/spring/logger/FallbackLogger.kt b/exception-handler-spring-web/src/main/kotlin/ru/touchin/exception/handler/spring/logger/FallbackLogger.kt new file mode 100644 index 0000000..b66be98 --- /dev/null +++ b/exception-handler-spring-web/src/main/kotlin/ru/touchin/exception/handler/spring/logger/FallbackLogger.kt @@ -0,0 +1,11 @@ +package ru.touchin.exception.handler.spring.logger + +import ru.touchin.exception.handler.dto.ExceptionResolverResult + +class FallbackLogger : Logger { + + override fun log(clazz: Class<*>, exceptionResolverResult: ExceptionResolverResult) { + // do nothing + } + +} diff --git a/exception-handler-spring-web/src/main/kotlin/ru/touchin/exception/handler/spring/logger/Logger.kt b/exception-handler-spring-web/src/main/kotlin/ru/touchin/exception/handler/spring/logger/Logger.kt new file mode 100644 index 0000000..3382688 --- /dev/null +++ b/exception-handler-spring-web/src/main/kotlin/ru/touchin/exception/handler/spring/logger/Logger.kt @@ -0,0 +1,9 @@ +package ru.touchin.exception.handler.spring.logger + +import ru.touchin.exception.handler.dto.ExceptionResolverResult + +interface Logger { + + fun log(clazz: Class<*>, exceptionResolverResult: ExceptionResolverResult) + +} diff --git a/exception-handler-spring-web/src/main/kotlin/ru/touchin/exception/handler/spring/resolvers/ExceptionResolver.kt b/exception-handler-spring-web/src/main/kotlin/ru/touchin/exception/handler/spring/resolvers/ExceptionResolver.kt new file mode 100644 index 0000000..ea8b4b1 --- /dev/null +++ b/exception-handler-spring-web/src/main/kotlin/ru/touchin/exception/handler/spring/resolvers/ExceptionResolver.kt @@ -0,0 +1,9 @@ +package ru.touchin.exception.handler.spring.resolvers + +import ru.touchin.exception.handler.dto.ExceptionResolverResult + +interface ExceptionResolver { + + operator fun invoke(exception: Exception): ExceptionResolverResult? + +} diff --git a/exception-handler-spring-web/src/main/kotlin/ru/touchin/exception/handler/spring/resolvers/FallbackExceptionResolver.kt b/exception-handler-spring-web/src/main/kotlin/ru/touchin/exception/handler/spring/resolvers/FallbackExceptionResolver.kt new file mode 100644 index 0000000..1f79434 --- /dev/null +++ b/exception-handler-spring-web/src/main/kotlin/ru/touchin/exception/handler/spring/resolvers/FallbackExceptionResolver.kt @@ -0,0 +1,16 @@ +package ru.touchin.exception.handler.spring.resolvers + +import org.springframework.core.Ordered +import org.springframework.core.annotation.Order +import org.springframework.stereotype.Component +import ru.touchin.exception.handler.dto.ExceptionResolverResult + +@Order(Ordered.LOWEST_PRECEDENCE) +@Component +class FallbackExceptionResolver : ExceptionResolver { + + override fun invoke(exception: Exception): ExceptionResolverResult { + return ExceptionResolverResult.createInternalError(exception) + } + +} diff --git a/exception-handler-spring-web/src/test/kotlin/ru/touchin/exception/handler/TestApplication.kt b/exception-handler-spring-web/src/test/kotlin/ru/touchin/exception/handler/TestApplication.kt new file mode 100644 index 0000000..d4163e7 --- /dev/null +++ b/exception-handler-spring-web/src/test/kotlin/ru/touchin/exception/handler/TestApplication.kt @@ -0,0 +1,8 @@ +package ru.touchin.exception.handler + +import org.springframework.boot.autoconfigure.SpringBootApplication +import ru.touchin.exception.handler.spring.EnableSpringExceptionHandler + +@SpringBootApplication +@EnableSpringExceptionHandler +class TestApplication diff --git a/exception-handler-spring-web/src/test/kotlin/ru/touchin/exception/handler/spring/advices/ExceptionHandlerAdviceMvcTest.kt b/exception-handler-spring-web/src/test/kotlin/ru/touchin/exception/handler/spring/advices/ExceptionHandlerAdviceMvcTest.kt new file mode 100644 index 0000000..da95b91 --- /dev/null +++ b/exception-handler-spring-web/src/test/kotlin/ru/touchin/exception/handler/spring/advices/ExceptionHandlerAdviceMvcTest.kt @@ -0,0 +1,116 @@ +package ru.touchin.exception.handler.spring.advices + +import com.nhaarman.mockitokotlin2.any +import com.nhaarman.mockitokotlin2.atLeastOnce +import com.nhaarman.mockitokotlin2.mock +import com.nhaarman.mockitokotlin2.never +import com.nhaarman.mockitokotlin2.verify +import org.hamcrest.Matchers.`is` +import org.hamcrest.Matchers.nullValue +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Test +import org.mockito.Mockito.spy +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.http.MediaType +import org.springframework.test.context.ActiveProfiles +import org.springframework.test.web.servlet.MockMvc +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get +import org.springframework.test.web.servlet.result.MockMvcResultHandlers.print +import org.springframework.test.web.servlet.result.MockMvcResultMatchers.content +import org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath +import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController +import ru.touchin.exception.handler.spring.EnableSpringExceptionHandler +import ru.touchin.exception.handler.spring.creators.ExceptionResponseBodyCreator +import ru.touchin.exception.handler.spring.logger.Logger +import ru.touchin.exception.handler.spring.resolvers.FallbackExceptionResolver +import ru.touchin.exception.handler.spring.resolvers.IllegalStateExceptionResolver1 +import ru.touchin.exception.handler.spring.resolvers.IllegalStateExceptionResolver2 + +@RestController +@RequestMapping("/api/errors") +class ErrorController { + + + @GetMapping("/runtime") + fun runtimeError() { + throw RuntimeException("my runtime error") + } + + @GetMapping("/illegal") + fun illegalError() { + throw IllegalStateException("my illegal error") + } + +} + +@ActiveProfiles("test") +@SpringBootTest +@AutoConfigureMockMvc +internal class ExceptionHandlerAdviceMvcTest { + + @Autowired + lateinit var mockMvc: MockMvc + + @Autowired + lateinit var logger: Logger + + @Test + @DisplayName("Тест должен проходить") + fun shouldBeWork() { + assertTrue(true, "Not passed") + } + + @Test + @DisplayName("Должна вернуться ошибка InternalServerError с кодом -1") + fun shouldGetInternalServerError() { + mockMvc + .perform(get("/api/errors/runtime")) + .andDo(print()) + .andExpect(status().isInternalServerError) + .andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(jsonPath("$.errorCode", `is`(-1))) + .andExpect(jsonPath("$.errorMessage", `is`("my runtime error"))) + } + + @Test + @DisplayName("Должна вернуться ошибка BadRequest с кодом -2") + fun shouldGetBadRequest() { + mockMvc + .perform(get("/api/errors/illegal")) + .andDo(print()) + .andExpect(status().isBadRequest) + .andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(jsonPath("$.errorCode", `is`(-2))) + .andExpect(jsonPath("$.errorMessage", `is`("my illegal error"))) + } + + @Test + @DisplayName("Должен отработать только первый ExceptionResolver") + fun shouldBeCorrectOrder() { + val resolver1 = spy(IllegalStateExceptionResolver1::class.java) + val resolver2 = spy(IllegalStateExceptionResolver2::class.java) + val resolver3 = spy(FallbackExceptionResolver::class.java) + + val resolvers = listOf(resolver1, resolver2, resolver3) + val exceptionResponseBodyCreator: ExceptionResponseBodyCreator = mock { } + + val exceptionHandlerAdvice = ExceptionHandlerAdvice( + exceptionResolversList = resolvers, + exceptionResponseBodyCreator = exceptionResponseBodyCreator, + logger = logger + ) + + exceptionHandlerAdvice.handleException(IllegalStateException("error")) + + verify(resolver1, atLeastOnce()).invoke(any()) + verify(resolver2, never()).invoke(any()) + verify(resolver3, never()).invoke(any()) + } + +} diff --git a/exception-handler-spring-web/src/test/kotlin/ru/touchin/exception/handler/spring/advices/ExceptionHandlerAdviceTest.kt b/exception-handler-spring-web/src/test/kotlin/ru/touchin/exception/handler/spring/advices/ExceptionHandlerAdviceTest.kt new file mode 100644 index 0000000..7396a53 --- /dev/null +++ b/exception-handler-spring-web/src/test/kotlin/ru/touchin/exception/handler/spring/advices/ExceptionHandlerAdviceTest.kt @@ -0,0 +1,88 @@ +package ru.touchin.exception.handler.spring.advices + +import com.nhaarman.mockitokotlin2.any +import com.nhaarman.mockitokotlin2.mock +import com.nhaarman.mockitokotlin2.never +import com.nhaarman.mockitokotlin2.only +import com.nhaarman.mockitokotlin2.verify +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Test +import org.mockito.Mockito.spy +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.test.context.ActiveProfiles +import ru.touchin.exception.handler.spring.creators.ExceptionResponseBodyCreator +import ru.touchin.exception.handler.spring.logger.Logger +import ru.touchin.exception.handler.spring.resolvers.FallbackExceptionResolver +import ru.touchin.exception.handler.spring.resolvers.IllegalStateExceptionResolver1 +import ru.touchin.exception.handler.spring.resolvers.IllegalStateExceptionResolver2 + +@ActiveProfiles("test") +@SpringBootTest +internal class ExceptionHandlerAdviceTest { + + private lateinit var resolver1: IllegalStateExceptionResolver1 + private lateinit var resolver2: IllegalStateExceptionResolver2 + private lateinit var resolver3: FallbackExceptionResolver + + private lateinit var exceptionHandlerAdvice: ExceptionHandlerAdvice + + @Autowired + lateinit var logger: Logger + + @BeforeEach + fun setUp() { + resolver1 = spy(IllegalStateExceptionResolver1::class.java) + resolver2 = spy(IllegalStateExceptionResolver2::class.java) + resolver3 = spy(FallbackExceptionResolver::class.java) + + val resolvers = listOf(resolver1, resolver2, resolver3) + val exceptionResponseBodyCreator: ExceptionResponseBodyCreator = mock { } + + exceptionHandlerAdvice = ExceptionHandlerAdvice( + exceptionResolversList = resolvers, + exceptionResponseBodyCreator = exceptionResponseBodyCreator, + logger = logger, + ) + } + + @Test + @DisplayName("Должен отработать только первый ExceptionResolver") + fun shouldBeExecuteOnlyResolver1() { + exceptionHandlerAdvice.handleException(IllegalStateException("error")) + + verify(resolver1, only()).invoke(any()) + verify(resolver2, never()).invoke(any()) + verify(resolver3, never()).invoke(any()) + } + + @Test + @DisplayName("Должны отработать все resolvers и поймать ошибку в FallbackResolver") + fun shouldBeExecuteAllResolversAndResolvedInFallback() { + exceptionHandlerAdvice.handleException(RuntimeException("error")) + + verify(resolver1, only()).invoke(any()) + verify(resolver2, only()).invoke(any()) + verify(resolver3, only()).invoke(any()) + } + + @Test + @DisplayName("Должны отработать все resolvers и НЕ поймать ошибку в FallbackResolver") + fun shouldVBeExecuteAllResolversWithoutResolve() { + val resolvers = listOf(resolver1, resolver2) + val exceptionResponseBodyCreator: ExceptionResponseBodyCreator = mock { } + + exceptionHandlerAdvice = ExceptionHandlerAdvice( + exceptionResolversList = resolvers, + exceptionResponseBodyCreator = exceptionResponseBodyCreator, + logger = logger, + ) + + exceptionHandlerAdvice.handleException(RuntimeException("error")) + + verify(resolver1, only()).invoke(any()) + verify(resolver2, only()).invoke(any()) + } + +} diff --git a/exception-handler-spring-web/src/test/kotlin/ru/touchin/exception/handler/spring/resolvers/IllegalStateExceptionResolver1.kt b/exception-handler-spring-web/src/test/kotlin/ru/touchin/exception/handler/spring/resolvers/IllegalStateExceptionResolver1.kt new file mode 100644 index 0000000..ed30b02 --- /dev/null +++ b/exception-handler-spring-web/src/test/kotlin/ru/touchin/exception/handler/spring/resolvers/IllegalStateExceptionResolver1.kt @@ -0,0 +1,23 @@ +package ru.touchin.exception.handler.spring.resolvers + +import org.springframework.core.annotation.Order +import org.springframework.http.HttpStatus +import org.springframework.stereotype.Component +import ru.touchin.common.spring.web.dto.DefaultApiError +import ru.touchin.exception.handler.dto.ExceptionResolverResult + +@Component +@Order(1) +class IllegalStateExceptionResolver1: ExceptionResolver { + + override fun invoke(exception: Exception): ExceptionResolverResult? { + return (exception as? IllegalStateException)?.let { + ExceptionResolverResult( + apiError = DefaultApiError(errorCode = -2, errorMessage = exception.message), + status = HttpStatus.BAD_REQUEST, + exception = exception, + ) + } + } + +} diff --git a/exception-handler-spring-web/src/test/kotlin/ru/touchin/exception/handler/spring/resolvers/IllegalStateExceptionResolver2.kt b/exception-handler-spring-web/src/test/kotlin/ru/touchin/exception/handler/spring/resolvers/IllegalStateExceptionResolver2.kt new file mode 100644 index 0000000..11de7c6 --- /dev/null +++ b/exception-handler-spring-web/src/test/kotlin/ru/touchin/exception/handler/spring/resolvers/IllegalStateExceptionResolver2.kt @@ -0,0 +1,24 @@ +package ru.touchin.exception.handler.spring.resolvers + +import org.springframework.core.annotation.Order +import org.springframework.http.HttpStatus +import org.springframework.stereotype.Component +import ru.touchin.common.spring.web.dto.DefaultApiError +import ru.touchin.exception.handler.dto.ExceptionResolverResult +import java.lang.IllegalStateException + +@Component +@Order(2) +class IllegalStateExceptionResolver2: ExceptionResolver { + + override fun invoke(exception: Exception): ExceptionResolverResult? { + return (exception as? IllegalStateException)?.let { + ExceptionResolverResult( + apiError = DefaultApiError(errorCode = -2, errorMessage = "Should not be executed"), + status = HttpStatus.BAD_GATEWAY, + exception = exception, + ) + } + } + +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 7c6792a..d019e22 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -28,3 +28,4 @@ include("common-spring-test-jpa") include("logger") include("logger-spring") include("logger-spring-web") +include("exception-handler-spring-web")