From 6247eedb9a11702cb3abae0afbfcab4b9b6fe884 Mon Sep 17 00:00:00 2001 From: Denis Savosin Date: Wed, 25 Sep 2024 16:55:24 +0700 Subject: [PATCH] add global exception handler for controllers add new not found exception, add new test for ShopController --- .../demo/controllers/ShopController.kt | 11 ++--- .../demo/exceptions/ExceptionHandler.kt | 13 ++++++ .../demo/exceptions/NotFoundException.kt | 3 ++ .../example/demo/responses/BaseResponse.kt | 5 +++ .../example/demo/responses/ResponseStatus.kt | 7 +++ .../demo/controllers/ShopControllerTest.kt | 45 ++++++++++++++++--- 6 files changed, 69 insertions(+), 15 deletions(-) create mode 100644 src/main/kotlin/com/example/demo/exceptions/ExceptionHandler.kt create mode 100644 src/main/kotlin/com/example/demo/exceptions/NotFoundException.kt create mode 100644 src/main/kotlin/com/example/demo/responses/BaseResponse.kt create mode 100644 src/main/kotlin/com/example/demo/responses/ResponseStatus.kt diff --git a/src/main/kotlin/com/example/demo/controllers/ShopController.kt b/src/main/kotlin/com/example/demo/controllers/ShopController.kt index ebf15ac..f77741a 100644 --- a/src/main/kotlin/com/example/demo/controllers/ShopController.kt +++ b/src/main/kotlin/com/example/demo/controllers/ShopController.kt @@ -1,11 +1,11 @@ package com.example.demo.controllers +import com.example.demo.exceptions.NotFoundException import com.example.demo.provider.ShopProvider import jakarta.servlet.http.HttpServletResponse import kotlinx.serialization.json.Json import kotlinx.serialization.json.encodeToJsonElement import org.springframework.beans.factory.annotation.Autowired -import org.springframework.http.HttpStatus import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.ResponseBody import org.springframework.web.bind.annotation.RestController @@ -16,14 +16,9 @@ class ShopController ( ) { @GetMapping(value = ["/shop/common-info"], produces = ["application/json"]) @ResponseBody + @Throws(NotFoundException::class) fun commonInfo(response: HttpServletResponse): String { - val shop = shopProvider.getRandomShop() - - if (shop == null) { - response.status = HttpStatus.NOT_FOUND.value() - - return "not found" - } + val shop = shopProvider.getRandomShop() ?: throw NotFoundException() return Json.encodeToJsonElement(value = mapOf( "customers" to mapOf( diff --git a/src/main/kotlin/com/example/demo/exceptions/ExceptionHandler.kt b/src/main/kotlin/com/example/demo/exceptions/ExceptionHandler.kt new file mode 100644 index 0000000..1ea752a --- /dev/null +++ b/src/main/kotlin/com/example/demo/exceptions/ExceptionHandler.kt @@ -0,0 +1,13 @@ +package com.example.demo.exceptions + +import com.example.demo.responses.makeNotFound +import org.springframework.http.HttpStatus +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.ControllerAdvice +import org.springframework.web.bind.annotation.ExceptionHandler + +@ControllerAdvice +class ExceptionHandler { + @ExceptionHandler(NotFoundException::class) + fun handleNotFound(): ResponseEntity = ResponseEntity(makeNotFound(), HttpStatus.NOT_FOUND) +} \ No newline at end of file diff --git a/src/main/kotlin/com/example/demo/exceptions/NotFoundException.kt b/src/main/kotlin/com/example/demo/exceptions/NotFoundException.kt new file mode 100644 index 0000000..ec49227 --- /dev/null +++ b/src/main/kotlin/com/example/demo/exceptions/NotFoundException.kt @@ -0,0 +1,3 @@ +package com.example.demo.exceptions + +class NotFoundException: RuntimeException() \ No newline at end of file diff --git a/src/main/kotlin/com/example/demo/responses/BaseResponse.kt b/src/main/kotlin/com/example/demo/responses/BaseResponse.kt new file mode 100644 index 0000000..8942bde --- /dev/null +++ b/src/main/kotlin/com/example/demo/responses/BaseResponse.kt @@ -0,0 +1,5 @@ +package com.example.demo.responses + +class BaseResponse(val status: ResponseStatus) + +fun makeNotFound(): BaseResponse = BaseResponse(status = ResponseStatus.NOT_FOUND) \ No newline at end of file diff --git a/src/main/kotlin/com/example/demo/responses/ResponseStatus.kt b/src/main/kotlin/com/example/demo/responses/ResponseStatus.kt new file mode 100644 index 0000000..8da1b1a --- /dev/null +++ b/src/main/kotlin/com/example/demo/responses/ResponseStatus.kt @@ -0,0 +1,7 @@ +package com.example.demo.responses + +import com.fasterxml.jackson.annotation.JsonValue + +enum class ResponseStatus(@JsonValue val status: String) { + NOT_FOUND("not found"); +} \ No newline at end of file diff --git a/src/test/kotlin/com/example/demo/controllers/ShopControllerTest.kt b/src/test/kotlin/com/example/demo/controllers/ShopControllerTest.kt index b01b8e2..301d867 100644 --- a/src/test/kotlin/com/example/demo/controllers/ShopControllerTest.kt +++ b/src/test/kotlin/com/example/demo/controllers/ShopControllerTest.kt @@ -11,6 +11,8 @@ import org.springframework.test.web.servlet.MockMvc import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get import org.springframework.test.web.servlet.result.MockMvcResultMatchers.content import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status +import java.time.OffsetDateTime +import java.util.* import kotlin.test.Test @WebMvcTest(ShopController::class) @@ -20,30 +22,35 @@ class ShopControllerTest(@Autowired val mockMvc: MockMvc) { @Test fun commonInfo_shouldSeeSuccessResponseJson() { + val productOne = makeProduct(id = 1, name = "one", price = 11.2) + val productTwo = makeProduct(id = 2, name = "two", price = 13.2) + val productThree = makeProduct(id = 3, name = "three", price = 15.2) + val productFour = makeProduct(id = 4, name = "four", price = 14.2) + val shopMock = Shop(name="shop", customers= listOf( Customer( name = "cus-one", city = City(name= "city-one"), orders = listOf( - Order(products = listOf(Product(name = "one", price = 11.2)), isDelivered = false), - Order(products = listOf(Product(name = "two", price = 13.2)), isDelivered = false), - Order(products = listOf(Product(name = "three", price = 15.2)), isDelivered = true), + Order(products = listOf(productOne), isDelivered = false), + Order(products = listOf(productTwo), isDelivered = false), + Order(products = listOf(productThree), isDelivered = true), ) ), Customer( name = "cus-two", city = City(name= "city-two"), orders = listOf( - Order(products = listOf(Product(name = "one", price = 12.2)), isDelivered = false), - Order(products = listOf(Product(name = "two", price = 13.2)), isDelivered = true), - Order(products = listOf(Product(name = "four", price = 14.2)), isDelivered = true), + Order(products = listOf(productOne), isDelivered = false), + Order(products = listOf(productTwo), isDelivered = true), + Order(products = listOf(productFour), isDelivered = true), ) ), )) val expectedJson: String = """{ |"customers": {"withMoreUndeliveredOrdersThanDelivered": ["cus-one"]}, - |"products": {"orderedByAllCustomers": ["two"]} + |"products": {"orderedByAllCustomers": ["one", "two"]} |}""".trimMargin() whenever( @@ -55,4 +62,28 @@ class ShopControllerTest(@Autowired val mockMvc: MockMvc) { .andExpect(content().contentType("application/json")) .andExpect(content().json(expectedJson)) } + + @Test + fun commonInfo_shouldSeeNotFoundResponse() { + whenever( + shopProvider.getRandomShop() + ) doReturn (null) + + mockMvc.perform(get("/shop/common-info")) + .andExpect(status().isNotFound) + .andExpect(content().contentType("application/json")) + .andExpect(content().json("""{"status":"not found"}""")) + } + + private fun makeProduct(id: Long, name: String, price: Double): Product { + return Product( + id = id, + guid = UUID.randomUUID(), + name = name, + description = null, + price = (price * 100).toInt(), + createdAt = OffsetDateTime.now(), + updatedAt = null, + ) + } } \ No newline at end of file