From d4e36e73543f27cac9837cf93068936c669d48b1 Mon Sep 17 00:00:00 2001 From: Savosin Denis Date: Tue, 3 Jun 2025 15:28:03 +0700 Subject: [PATCH] move controllers to sub-project, fix dependencies --- build.gradle.kts | 37 +++--- edge-rest/build.gradle.kts | 7 ++ .../demo/edgerest}/ExceptionHandler.kt | 36 ++++-- .../controllers/CustomerController.kt | 51 ++++++++ .../controllers/GreetingController.kt | 17 +++ .../edgerest/controllers/NekoController.kt | 36 ++++++ .../edgerest/controllers/ProductController.kt | 87 ++++++++++++++ .../dannecron/demo/edgerest/WebTestConfig.kt | 17 +++ .../controllers/CustomerControllerTest.kt | 27 +++-- .../controllers/GreetingControllerTest.kt | 25 +++- .../controllers/NekoControllerTest.kt | 87 ++++++++++++++ .../controllers/ProductControllerTest.kt | 27 +++-- settings.gradle.kts | 2 + .../github/dannecron/demo/config/AppConfig.kt | 16 --- .../http/controllers/CustomerController.kt | 30 ----- .../http/controllers/GreetingController.kt | 20 ---- .../demo/http/controllers/NekoController.kt | 26 ----- .../http/controllers/ProductController.kt | 110 ------------------ .../demo/http/exceptions/NotFoundException.kt | 3 - .../http/exceptions/UnprocessableException.kt | 3 - .../demo/http/responses/BadRequestResponse.kt | 5 - .../demo/http/responses/BaseResponse.kt | 5 - .../demo/http/responses/NotFoundResponse.kt | 3 - .../http/responses/UnprocessableResponse.kt | 10 -- .../http/responses/neko/ImagesResponse.kt | 15 --- .../com/github/dannecron/demo/BaseUnitTest.kt | 8 -- .../http/controllers/NekoControllerTest.kt | 53 --------- 27 files changed, 401 insertions(+), 362 deletions(-) create mode 100644 edge-rest/build.gradle.kts rename {src/main/kotlin/com/github/dannecron/demo/http/exceptions => edge-rest/src/main/kotlin/com/github/dannecron/demo/edgerest}/ExceptionHandler.kt (52%) create mode 100644 edge-rest/src/main/kotlin/com/github/dannecron/demo/edgerest/controllers/CustomerController.kt create mode 100644 edge-rest/src/main/kotlin/com/github/dannecron/demo/edgerest/controllers/GreetingController.kt create mode 100644 edge-rest/src/main/kotlin/com/github/dannecron/demo/edgerest/controllers/NekoController.kt create mode 100644 edge-rest/src/main/kotlin/com/github/dannecron/demo/edgerest/controllers/ProductController.kt create mode 100644 edge-rest/src/test/kotlin/com/github/dannecron/demo/edgerest/WebTestConfig.kt rename {src/test/kotlin/com/github/dannecron/demo/http => edge-rest/src/test/kotlin/com/github/dannecron/demo/edgerest}/controllers/CustomerControllerTest.kt (84%) rename {src/test/kotlin/com/github/dannecron/demo/http => edge-rest/src/test/kotlin/com/github/dannecron/demo/edgerest}/controllers/GreetingControllerTest.kt (57%) create mode 100644 edge-rest/src/test/kotlin/com/github/dannecron/demo/edgerest/controllers/NekoControllerTest.kt rename {src/test/kotlin/com/github/dannecron/demo/http => edge-rest/src/test/kotlin/com/github/dannecron/demo/edgerest}/controllers/ProductControllerTest.kt (90%) delete mode 100644 src/main/kotlin/com/github/dannecron/demo/http/controllers/CustomerController.kt delete mode 100644 src/main/kotlin/com/github/dannecron/demo/http/controllers/GreetingController.kt delete mode 100644 src/main/kotlin/com/github/dannecron/demo/http/controllers/NekoController.kt delete mode 100644 src/main/kotlin/com/github/dannecron/demo/http/controllers/ProductController.kt delete mode 100644 src/main/kotlin/com/github/dannecron/demo/http/exceptions/NotFoundException.kt delete mode 100644 src/main/kotlin/com/github/dannecron/demo/http/exceptions/UnprocessableException.kt delete mode 100644 src/main/kotlin/com/github/dannecron/demo/http/responses/BadRequestResponse.kt delete mode 100644 src/main/kotlin/com/github/dannecron/demo/http/responses/BaseResponse.kt delete mode 100644 src/main/kotlin/com/github/dannecron/demo/http/responses/NotFoundResponse.kt delete mode 100644 src/main/kotlin/com/github/dannecron/demo/http/responses/UnprocessableResponse.kt delete mode 100644 src/main/kotlin/com/github/dannecron/demo/http/responses/neko/ImagesResponse.kt delete mode 100644 src/test/kotlin/com/github/dannecron/demo/BaseUnitTest.kt delete mode 100644 src/test/kotlin/com/github/dannecron/demo/http/controllers/NekoControllerTest.kt diff --git a/build.gradle.kts b/build.gradle.kts index 201d38e..54771e7 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -50,11 +50,11 @@ allprojects { implementation(rootProject.libs.kotlin.reflect) implementation(rootProject.libs.kotlinx.serialization.json) implementation(rootProject.libs.logback.encoder) - implementation(rootProject.libs.spring.aspects) + implementation(rootProject.libs.springFramework.aspects) testImplementation(rootProject.libs.kotlin.test.junit) testImplementation(rootProject.libs.mockito.kotlin) - testImplementation(rootProject.libs.spring.boot.starter.test) + testImplementation(rootProject.libs.springBoot.starter.test) } tasks.test { @@ -81,34 +81,23 @@ dependencies { implementation(project(":edge-contracts")) implementation(project(":db")) implementation(project(":edge-producing")) + implementation(project(":edge-integration")) implementation(project(":core")) implementation(project(":edge-consuming")) + implementation(project(":edge-rest")) - implementation(libs.jackson.datatype.jsr) - implementation(libs.jackson.module.kotlin) - implementation(libs.ktor.client.cio) - implementation(libs.ktor.client.core) - implementation(libs.postgres) - implementation(libs.spring.boot.starter.jdbc) - implementation(libs.spring.boot.starter.mustache) - implementation(libs.spring.boot.starter.validation) - implementation(libs.spring.boot.starter.web) - implementation(libs.spring.cloud.starter.streamKafka) - implementation(libs.spring.cloud.stream) - implementation(libs.spring.doc.openapi.starter) + implementation(libs.springBoot.starter.mustache) + implementation(libs.springBoot.starter.web) - testImplementation(libs.ktor.client.mock) - testImplementation(libs.spring.cloud.streamTestBinder) - testImplementation(libs.testcontainers) - testImplementation(libs.testcontainers.junit.jupiter) + developmentOnly(libs.springBoot.devtools) - developmentOnly(libs.spring.boot.devtools) - - kover(project(":edge-consuming")) - kover(project(":core")) - kover(project(":edge-producing")) - kover(project(":db")) kover(project(":edge-contracts")) + kover(project(":db")) + kover(project(":edge-producing")) + kover(project(":edge-integration")) + kover(project(":core")) + kover(project(":edge-consuming")) + kover(project(":edge-rest")) } tasks.bootJar { diff --git a/edge-rest/build.gradle.kts b/edge-rest/build.gradle.kts new file mode 100644 index 0000000..b6bf86d --- /dev/null +++ b/edge-rest/build.gradle.kts @@ -0,0 +1,7 @@ +dependencies { + implementation(project(":edge-contracts")) + implementation(project(":core")) + + implementation(rootProject.libs.springBoot.starter.web) + implementation(rootProject.libs.springData.commons) +} diff --git a/src/main/kotlin/com/github/dannecron/demo/http/exceptions/ExceptionHandler.kt b/edge-rest/src/main/kotlin/com/github/dannecron/demo/edgerest/ExceptionHandler.kt similarity index 52% rename from src/main/kotlin/com/github/dannecron/demo/http/exceptions/ExceptionHandler.kt rename to edge-rest/src/main/kotlin/com/github/dannecron/demo/edgerest/ExceptionHandler.kt index 868d7ba..d387683 100644 --- a/src/main/kotlin/com/github/dannecron/demo/http/exceptions/ExceptionHandler.kt +++ b/edge-rest/src/main/kotlin/com/github/dannecron/demo/edgerest/ExceptionHandler.kt @@ -1,9 +1,13 @@ -package com.github.dannecron.demo.http.exceptions +package com.github.dannecron.demo.edgerest -import com.github.dannecron.demo.http.responses.BadRequestResponse -import com.github.dannecron.demo.http.responses.BaseResponse -import com.github.dannecron.demo.http.responses.NotFoundResponse -import com.github.dannecron.demo.http.responses.UnprocessableResponse +import com.github.dannecron.demo.core.utils.LoggerDelegate +import com.github.dannecron.demo.edgecontracts.api.exceptions.NotFoundException +import com.github.dannecron.demo.edgecontracts.api.exceptions.UnprocessableException +import com.github.dannecron.demo.edgecontracts.api.model.ResponseStatusModel +import com.github.dannecron.demo.edgecontracts.api.response.common.BadRequestResponse +import com.github.dannecron.demo.edgecontracts.api.response.common.BaseResponse +import com.github.dannecron.demo.edgecontracts.api.response.common.NotFoundResponse +import com.github.dannecron.demo.edgecontracts.api.response.common.UnprocessableResponse import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity import org.springframework.http.converter.HttpMessageNotReadableException @@ -14,12 +18,16 @@ import org.springframework.web.bind.annotation.ResponseStatus @ControllerAdvice class ExceptionHandler { + private val logger by LoggerDelegate() + /* 4xx status codes */ // 400 @ExceptionHandler(HttpMessageNotReadableException::class) @ResponseStatus(HttpStatus.BAD_REQUEST) - fun handleMessageNotReadable(exception: HttpMessageNotReadableException): ResponseEntity = ResponseEntity( + fun handleMessageNotReadable( + exception: HttpMessageNotReadableException, + ): ResponseEntity = ResponseEntity( BadRequestResponse(exception.message.toString()), HttpStatus.BAD_REQUEST, ) @@ -27,19 +35,21 @@ class ExceptionHandler { // 404 @ExceptionHandler(NotFoundException::class) @ResponseStatus(HttpStatus.NOT_FOUND) - fun handleNotFound(): ResponseEntity = ResponseEntity(NotFoundResponse(), HttpStatus.NOT_FOUND) + fun handleNotFound(): ResponseEntity = ResponseEntity(NotFoundResponse(), HttpStatus.NOT_FOUND) // 422 @ExceptionHandler(UnprocessableException::class) @ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY) - fun handleUnprocessable(exception: UnprocessableException): ResponseEntity = ResponseEntity( + fun handleUnprocessable(exception: UnprocessableException): ResponseEntity = ResponseEntity( UnprocessableResponse(exception.message), HttpStatus.UNPROCESSABLE_ENTITY, ) @ExceptionHandler(MethodArgumentNotValidException::class) @ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY) - fun handleMethodArgumentNotValid(exception: MethodArgumentNotValidException): ResponseEntity = ResponseEntity( + fun handleMethodArgumentNotValid( + exception: MethodArgumentNotValidException, + ): ResponseEntity = ResponseEntity( UnprocessableResponse(exception.javaClass.name, exception.allErrors), HttpStatus.UNPROCESSABLE_ENTITY, ) @@ -47,8 +57,10 @@ class ExceptionHandler { // 500 @ExceptionHandler(RuntimeException::class) @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) - fun handleUnexpectedRuntimeException(exception: RuntimeException): ResponseEntity = ResponseEntity( - BaseResponse(com.github.dannecron.demo.http.responses.ResponseStatus.INTERNAL_ERROR), + fun handleUnexpectedRuntimeException(exception: RuntimeException): ResponseEntity = ResponseEntity( + BaseResponse(ResponseStatusModel.INTERNAL_ERROR), HttpStatus.INTERNAL_SERVER_ERROR, - ) + ).also { + logger.error("internal server error", exception) + } } diff --git a/edge-rest/src/main/kotlin/com/github/dannecron/demo/edgerest/controllers/CustomerController.kt b/edge-rest/src/main/kotlin/com/github/dannecron/demo/edgerest/controllers/CustomerController.kt new file mode 100644 index 0000000..35d6596 --- /dev/null +++ b/edge-rest/src/main/kotlin/com/github/dannecron/demo/edgerest/controllers/CustomerController.kt @@ -0,0 +1,51 @@ +package com.github.dannecron.demo.edgerest.controllers + +import com.github.dannecron.demo.core.dto.City +import com.github.dannecron.demo.core.dto.Customer +import com.github.dannecron.demo.core.dto.view.CustomerExtended +import com.github.dannecron.demo.core.services.customer.CustomerService +import com.github.dannecron.demo.edgecontracts.api.CustomerApi +import com.github.dannecron.demo.edgecontracts.api.exceptions.NotFoundException +import com.github.dannecron.demo.edgecontracts.api.model.CityApiModel +import com.github.dannecron.demo.edgecontracts.api.model.CustomerApiModel +import com.github.dannecron.demo.edgecontracts.api.response.GetCustomerResponse +import org.springframework.http.HttpStatus +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.RestController +import java.util.UUID + +@RestController +class CustomerController( + private val customerService: CustomerService, +) : CustomerApi { + + @Throws(NotFoundException::class) + override fun getCustomer(guid: UUID): ResponseEntity { + val customerExtended = customerService.findByGuid(guid) ?: throw NotFoundException(null) + + return ResponseEntity(customerExtended.toResponse(), HttpStatus.OK) + } + + private fun CustomerExtended.toResponse() = GetCustomerResponse( + customer = customer.toApiModel(), + city = city?.toApiModel() + ) + + private fun Customer.toApiModel() = CustomerApiModel( + id = id, + guid = guid, + name = name, + cityId = cityId, + createdAt = createdAt, + updatedAt = updatedAt, + ) + + private fun City.toApiModel() = CityApiModel( + id = id, + guid = guid, + name = name, + createdAt = createdAt, + updatedAt = updatedAt, + deletedAt = deletedAt, + ) +} diff --git a/edge-rest/src/main/kotlin/com/github/dannecron/demo/edgerest/controllers/GreetingController.kt b/edge-rest/src/main/kotlin/com/github/dannecron/demo/edgerest/controllers/GreetingController.kt new file mode 100644 index 0000000..46d64f2 --- /dev/null +++ b/edge-rest/src/main/kotlin/com/github/dannecron/demo/edgerest/controllers/GreetingController.kt @@ -0,0 +1,17 @@ +package com.github.dannecron.demo.edgerest.controllers + +import com.github.dannecron.demo.edgecontracts.api.GreetingApi +import org.springframework.web.bind.annotation.ResponseBody +import org.springframework.web.bind.annotation.RestController + +@RestController +class GreetingController() : GreetingApi { + override fun greet(): String { + return "Hello World!" + } + + @ResponseBody + override fun exampleHtml(): String { + TODO("Not yet implemented") + } +} diff --git a/edge-rest/src/main/kotlin/com/github/dannecron/demo/edgerest/controllers/NekoController.kt b/edge-rest/src/main/kotlin/com/github/dannecron/demo/edgerest/controllers/NekoController.kt new file mode 100644 index 0000000..c4553fc --- /dev/null +++ b/edge-rest/src/main/kotlin/com/github/dannecron/demo/edgerest/controllers/NekoController.kt @@ -0,0 +1,36 @@ +package com.github.dannecron.demo.edgerest.controllers + +import com.github.dannecron.demo.core.dto.neko.ImageDto +import com.github.dannecron.demo.core.services.neko.NekoService +import com.github.dannecron.demo.edgecontracts.api.NekoApi +import com.github.dannecron.demo.edgecontracts.api.model.NekoImageApiModel +import com.github.dannecron.demo.edgecontracts.api.response.GetNekoImagesResponse +import org.springframework.http.HttpStatus +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.RestController + +@RestController +class NekoController( + private val nekoService: NekoService +) : NekoApi { + + override fun categories(): ResponseEntity> = + ResponseEntity(nekoService.getCategories(), HttpStatus.OK) + + override fun images(category: String, imagesCount: Int): ResponseEntity = + ResponseEntity( + GetNekoImagesResponse( + nekoService.getImages(category, imagesCount).map { it.toApiModel() } + ), + HttpStatus.OK, + ) + + private fun ImageDto.toApiModel() = NekoImageApiModel( + url = url, + animeName = animeName, + artistHref = artistHref, + artistName = artistName, + sourceUrl = sourceUrl, + isGif = animeName == null + ) +} diff --git a/edge-rest/src/main/kotlin/com/github/dannecron/demo/edgerest/controllers/ProductController.kt b/edge-rest/src/main/kotlin/com/github/dannecron/demo/edgerest/controllers/ProductController.kt new file mode 100644 index 0000000..90214ef --- /dev/null +++ b/edge-rest/src/main/kotlin/com/github/dannecron/demo/edgerest/controllers/ProductController.kt @@ -0,0 +1,87 @@ +package com.github.dannecron.demo.edgerest.controllers + +import com.github.dannecron.demo.core.dto.Product +import com.github.dannecron.demo.core.exceptions.AlreadyDeletedException +import com.github.dannecron.demo.core.exceptions.InvalidDataException +import com.github.dannecron.demo.core.exceptions.ProductNotFoundException +import com.github.dannecron.demo.core.services.product.ProductService +import com.github.dannecron.demo.edgecontracts.api.ProductApi +import com.github.dannecron.demo.edgecontracts.api.exceptions.NotFoundException +import com.github.dannecron.demo.edgecontracts.api.exceptions.UnprocessableException +import com.github.dannecron.demo.edgecontracts.api.model.ProductApiModel +import com.github.dannecron.demo.edgecontracts.api.request.CreateProductRequest +import com.github.dannecron.demo.edgecontracts.api.response.common.BaseResponse +import com.github.dannecron.demo.edgecontracts.api.response.common.makeOkResponse +import com.github.dannecron.demo.edgecontracts.api.response.page.PageResponse +import org.springframework.data.domain.Pageable +import org.springframework.http.HttpStatus +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.RestController +import java.util.UUID + +@RestController +class ProductController( + private val productService: ProductService, +) : ProductApi { + override fun getProducts(pageable: Pageable): ResponseEntity> { + val products = productService.findAll(pageable) + + return ResponseEntity( + PageResponse(products.map { it.toApiModel() }), + HttpStatus.OK, + ) + } + + @Throws(NotFoundException::class) + override fun getProduct(guid: UUID): ResponseEntity { + val product = productService.findByGuid(guid = guid) ?: throw NotFoundException(null) + + return ResponseEntity(product.toApiModel(), HttpStatus.OK) + } + + override fun createProduct(product: CreateProductRequest): ResponseEntity { + val saved = productService.create( + product.name, + product.price, + product.description, + ) + + return ResponseEntity(saved.toApiModel(), HttpStatus.CREATED) + } + + @Throws(NotFoundException::class, UnprocessableException::class) + override fun deleteProduct(guid: UUID): ResponseEntity { + try { + productService.delete(guid) + return ResponseEntity(makeOkResponse(), HttpStatus.OK) + } catch (ex: ProductNotFoundException) { + throw NotFoundException(ex) + } catch (ex: AlreadyDeletedException) { + throw UnprocessableException("product already deleted", ex) + } + } + + @Throws(NotFoundException::class) + override fun sendProduct(guid: UUID, topic: String?): ResponseEntity { + try { + productService.send(guid, topic) + + return ResponseEntity(makeOkResponse(), HttpStatus.OK) + } catch (_: InvalidDataException) { + throw UnprocessableException("cannot sync product") + } + } + + private fun Product.toApiModel() = ProductApiModel( + id = id, + guid = guid, + name = name, + description = description, + price = price, + createdAt = createdAt, + updatedAt = updatedAt, + deletedAt = deletedAt, + priceDouble = getPriceDouble(), + isDeleted = isDeleted(), + ) +} diff --git a/edge-rest/src/test/kotlin/com/github/dannecron/demo/edgerest/WebTestConfig.kt b/edge-rest/src/test/kotlin/com/github/dannecron/demo/edgerest/WebTestConfig.kt new file mode 100644 index 0000000..440387c --- /dev/null +++ b/edge-rest/src/test/kotlin/com/github/dannecron/demo/edgerest/WebTestConfig.kt @@ -0,0 +1,17 @@ +package com.github.dannecron.demo.edgerest + +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.databind.SerializationFeature +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule +import org.springframework.boot.test.context.TestConfiguration +import org.springframework.context.annotation.Bean + +@TestConfiguration +class WebTestConfig { + + @Bean + fun objectMapper(): ObjectMapper = ObjectMapper().apply { + registerModules(JavaTimeModule()) + configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false) + } +} diff --git a/src/test/kotlin/com/github/dannecron/demo/http/controllers/CustomerControllerTest.kt b/edge-rest/src/test/kotlin/com/github/dannecron/demo/edgerest/controllers/CustomerControllerTest.kt similarity index 84% rename from src/test/kotlin/com/github/dannecron/demo/http/controllers/CustomerControllerTest.kt rename to edge-rest/src/test/kotlin/com/github/dannecron/demo/edgerest/controllers/CustomerControllerTest.kt index b9cd24f..1507e53 100644 --- a/src/test/kotlin/com/github/dannecron/demo/http/controllers/CustomerControllerTest.kt +++ b/edge-rest/src/test/kotlin/com/github/dannecron/demo/edgerest/controllers/CustomerControllerTest.kt @@ -1,18 +1,21 @@ -package com.github.dannecron.demo.http.controllers +package com.github.dannecron.demo.edgerest.controllers -import com.github.dannecron.demo.BaseUnitTest import com.github.dannecron.demo.core.dto.City import com.github.dannecron.demo.core.dto.Customer import com.github.dannecron.demo.core.dto.view.CustomerExtended import com.github.dannecron.demo.core.services.customer.CustomerService -import com.github.dannecron.demo.http.responses.ResponseStatus +import com.github.dannecron.demo.edgecontracts.api.model.ResponseStatusModel +import com.github.dannecron.demo.edgerest.ExceptionHandler +import com.github.dannecron.demo.edgerest.WebTestConfig import org.mockito.kotlin.doReturn import org.mockito.kotlin.eq import org.mockito.kotlin.whenever import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest import org.springframework.boot.test.mock.mockito.MockBean import org.springframework.http.MediaType +import org.springframework.test.context.ContextConfiguration import org.springframework.test.web.servlet.MockMvc import org.springframework.test.web.servlet.get import java.time.OffsetDateTime @@ -20,9 +23,19 @@ import java.util.UUID import kotlin.test.Test @WebMvcTest(CustomerController::class) -class CustomerControllerTest( - @Autowired val mockMvc: MockMvc, -): BaseUnitTest() { +@AutoConfigureMockMvc +@ContextConfiguration( + classes = [ + WebTestConfig::class, + CustomerController::class, + ExceptionHandler::class, + ] +) +class CustomerControllerTest { + + @Autowired + private lateinit var mockMvc: MockMvc + @MockBean private lateinit var customerService: CustomerService @@ -101,6 +114,6 @@ class CustomerControllerTest( mockMvc.get("/api/customer/$customerGuid") .andExpect { status { isNotFound() } } .andExpect { content { contentType(MediaType.APPLICATION_JSON) } } - .andExpect { jsonPath("\$.status") { value(ResponseStatus.NOT_FOUND.status) } } + .andExpect { jsonPath("\$.status") { value(ResponseStatusModel.NOT_FOUND.status) } } } } diff --git a/src/test/kotlin/com/github/dannecron/demo/http/controllers/GreetingControllerTest.kt b/edge-rest/src/test/kotlin/com/github/dannecron/demo/edgerest/controllers/GreetingControllerTest.kt similarity index 57% rename from src/test/kotlin/com/github/dannecron/demo/http/controllers/GreetingControllerTest.kt rename to edge-rest/src/test/kotlin/com/github/dannecron/demo/edgerest/controllers/GreetingControllerTest.kt index 2bb93fa..98b2dc5 100644 --- a/src/test/kotlin/com/github/dannecron/demo/http/controllers/GreetingControllerTest.kt +++ b/edge-rest/src/test/kotlin/com/github/dannecron/demo/edgerest/controllers/GreetingControllerTest.kt @@ -1,15 +1,31 @@ -package com.github.dannecron.demo.http.controllers +package com.github.dannecron.demo.edgerest.controllers -import com.github.dannecron.demo.BaseUnitTest +import com.github.dannecron.demo.edgerest.ExceptionHandler +import com.github.dannecron.demo.edgerest.WebTestConfig import org.hamcrest.core.StringContains import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest +import org.springframework.test.context.ContextConfiguration import org.springframework.test.web.servlet.MockMvc import org.springframework.test.web.servlet.get +import kotlin.test.Ignore import kotlin.test.Test -@WebMvcTest(com.github.dannecron.demo.http.controllers.GreetingController::class) -class GreetingControllerTest(@Autowired val mockMvc: MockMvc): BaseUnitTest() { +@WebMvcTest(GreetingController::class) +@AutoConfigureMockMvc +@ContextConfiguration( + classes = [ + WebTestConfig::class, + GreetingController::class, + ExceptionHandler::class, + ] +) +class GreetingControllerTest { + + @Autowired + private lateinit var mockMvc: MockMvc + @Test fun greetings_shouldSeeGreetingMessage() { mockMvc.get("/greeting") @@ -19,6 +35,7 @@ class GreetingControllerTest(@Autowired val mockMvc: MockMvc): BaseUnitTest() { } @Test + @Ignore fun exampleHtml_shouldSeeRenderedHtml() { mockMvc.get("/example/html") .andExpect { status { isOk() } } diff --git a/edge-rest/src/test/kotlin/com/github/dannecron/demo/edgerest/controllers/NekoControllerTest.kt b/edge-rest/src/test/kotlin/com/github/dannecron/demo/edgerest/controllers/NekoControllerTest.kt new file mode 100644 index 0000000..724c9eb --- /dev/null +++ b/edge-rest/src/test/kotlin/com/github/dannecron/demo/edgerest/controllers/NekoControllerTest.kt @@ -0,0 +1,87 @@ +package com.github.dannecron.demo.edgerest.controllers + +import com.github.dannecron.demo.core.dto.neko.ImageDto +import com.github.dannecron.demo.core.exceptions.neko.IntegrationException +import com.github.dannecron.demo.core.services.neko.NekoService +import com.github.dannecron.demo.edgerest.ExceptionHandler +import com.github.dannecron.demo.edgerest.WebTestConfig +import org.mockito.kotlin.times +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest +import org.springframework.boot.test.mock.mockito.MockBean +import org.springframework.http.MediaType +import org.springframework.test.context.ContextConfiguration +import org.springframework.test.web.servlet.MockMvc +import org.springframework.test.web.servlet.get +import kotlin.test.Test + +@WebMvcTest(NekoController::class) +@AutoConfigureMockMvc +@ContextConfiguration( + classes = [ + WebTestConfig::class, + NekoController::class, + ExceptionHandler::class, + ] +) +class NekoControllerTest { + + @Autowired + private lateinit var mockMvc: MockMvc + + @MockBean + private lateinit var nekoService: NekoService + + @Test + fun `getCategories - success`() { + whenever(nekoService.getCategories()).thenReturn(setOf("cat1", "cat2")) + + mockMvc.get("/api/neko/categories") + .andExpect { status { isOk() } } + .andExpect { content { contentType(MediaType.APPLICATION_JSON) } } + .andExpect { content { string("""["cat1","cat2"]""") } } + + verify(nekoService, times(1)).getCategories() + } + + @Test + fun `getCategories - fail - integration error`() { + whenever(nekoService.getCategories()).thenThrow(IntegrationException("some error")) + + mockMvc.get("/api/neko/categories") + .andExpect { status { isInternalServerError() } } + .andExpect { content { contentType(MediaType.APPLICATION_JSON) } } + .andExpect { jsonPath("$.status") { value("internal") } } + + verify(nekoService, times(1)).getCategories() + } + + @Test + fun `getImages - success`() { + val category = "some" + val animeName = "boku no pico" + whenever(nekoService.getImages(category = category, amount = 1)) + .thenReturn( + listOf( + ImageDto( + "http://localhost", + animeName = animeName, + artistHref = null, + artistName = null, + sourceUrl = null, + ) + ) + ) + + mockMvc.get("/api/neko/images/$category") + .andExpect { status { isOk() } } + .andExpect { content { contentType(MediaType.APPLICATION_JSON) } } + .andExpect { jsonPath("\$.images[0].animeName") { value(animeName) } } + .andExpect { jsonPath("\$.images[0].artistName") { value(null) } } + + verify(nekoService, times(1)).getImages(category = category, amount = 1) + } +} diff --git a/src/test/kotlin/com/github/dannecron/demo/http/controllers/ProductControllerTest.kt b/edge-rest/src/test/kotlin/com/github/dannecron/demo/edgerest/controllers/ProductControllerTest.kt similarity index 90% rename from src/test/kotlin/com/github/dannecron/demo/http/controllers/ProductControllerTest.kt rename to edge-rest/src/test/kotlin/com/github/dannecron/demo/edgerest/controllers/ProductControllerTest.kt index d21b2cc..def62b1 100644 --- a/src/test/kotlin/com/github/dannecron/demo/http/controllers/ProductControllerTest.kt +++ b/edge-rest/src/test/kotlin/com/github/dannecron/demo/edgerest/controllers/ProductControllerTest.kt @@ -1,9 +1,10 @@ -package com.github.dannecron.demo.http.controllers +package com.github.dannecron.demo.edgerest.controllers -import com.github.dannecron.demo.BaseUnitTest import com.github.dannecron.demo.core.dto.Product import com.github.dannecron.demo.core.services.product.ProductService -import com.github.dannecron.demo.http.responses.ResponseStatus +import com.github.dannecron.demo.edgecontracts.api.model.ResponseStatusModel +import com.github.dannecron.demo.edgerest.ExceptionHandler +import com.github.dannecron.demo.edgerest.WebTestConfig import org.hamcrest.Matchers.contains import org.hamcrest.Matchers.nullValue import org.junit.jupiter.api.Test @@ -14,12 +15,14 @@ import org.mockito.kotlin.verify import org.mockito.kotlin.verifyNoInteractions import org.mockito.kotlin.whenever import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest import org.springframework.boot.test.mock.mockito.MockBean import org.springframework.data.domain.PageImpl import org.springframework.data.domain.PageRequest import org.springframework.data.domain.Sort import org.springframework.http.MediaType +import org.springframework.test.context.ContextConfiguration import org.springframework.test.web.servlet.MockMvc import org.springframework.test.web.servlet.delete import org.springframework.test.web.servlet.get @@ -30,7 +33,15 @@ import java.time.format.DateTimeFormatter import java.util.UUID @WebMvcTest(ProductController::class) -class ProductControllerTest: BaseUnitTest() { +@AutoConfigureMockMvc +@ContextConfiguration( + classes = [ + WebTestConfig::class, + ProductController::class, + ExceptionHandler::class, + ] +) +class ProductControllerTest { @Autowired private lateinit var mockMvc: MockMvc @@ -77,7 +88,7 @@ class ProductControllerTest: BaseUnitTest() { mockMvc.get("/api/product/$guid") .andExpect { status { isNotFound() } } .andExpect { content { contentType(MediaType.APPLICATION_JSON) } } - .andExpect { jsonPath("\$.status") { value(ResponseStatus.NOT_FOUND.status) } } + .andExpect { jsonPath("\$.status") { value(ResponseStatusModel.NOT_FOUND.status) } } verify(productService, times(1)).findByGuid(guid) } @@ -132,7 +143,7 @@ class ProductControllerTest: BaseUnitTest() { } .andExpect { status { isBadRequest() } } .andExpect { content { contentType(MediaType.APPLICATION_JSON) } } - .andExpect { jsonPath("\$.status") { value(ResponseStatus.BAD_REQUEST.status) } } + .andExpect { jsonPath("\$.status") { value(ResponseStatusModel.BAD_REQUEST.status) } } .andExpect { jsonPath("\$.cause") { contains("name") } } verifyNoInteractions(productService) @@ -148,7 +159,7 @@ class ProductControllerTest: BaseUnitTest() { } .andExpect { status { isUnprocessableEntity() } } .andExpect { content { contentType(MediaType.APPLICATION_JSON) } } - .andExpect { jsonPath("\$.status") { value(ResponseStatus.UNPROCESSABLE.status) } } + .andExpect { jsonPath("\$.status") { value(ResponseStatusModel.UNPROCESSABLE.status) } } .andExpect { jsonPath("\$.cause") { value(MethodArgumentNotValidException::class.qualifiedName) } } verifyNoInteractions(productService) @@ -163,7 +174,7 @@ class ProductControllerTest: BaseUnitTest() { mockMvc.delete("/api/product/${guid}") .andExpect { status { isOk() } } .andExpect { content { contentType(MediaType.APPLICATION_JSON) } } - .andExpect { jsonPath("\$.status") { value(ResponseStatus.OK.status) } } + .andExpect { jsonPath("\$.status") { value(ResponseStatusModel.OK.status) } } verify(productService, times(1)).delete(guid) } diff --git a/settings.gradle.kts b/settings.gradle.kts index 9361e15..bfb4e74 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -7,3 +7,5 @@ include("core") include("edge-consuming") include("edge-producing") include("edge-contracts") +include("edge-rest") +include("edge-integration") diff --git a/src/main/kotlin/com/github/dannecron/demo/config/AppConfig.kt b/src/main/kotlin/com/github/dannecron/demo/config/AppConfig.kt index c0562db..269b90e 100644 --- a/src/main/kotlin/com/github/dannecron/demo/config/AppConfig.kt +++ b/src/main/kotlin/com/github/dannecron/demo/config/AppConfig.kt @@ -3,16 +3,12 @@ package com.github.dannecron.demo.config import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.SerializationFeature import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule -import io.ktor.client.engine.HttpClientEngine -import io.ktor.client.engine.cio.CIO import io.micrometer.observation.ObservationRegistry import io.micrometer.observation.aop.ObservedAspect import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter import org.springframework.beans.factory.annotation.Value import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration -import com.github.dannecron.demo.services.neko.Client as NekoClient -import com.github.dannecron.demo.services.neko.ClientImpl as NekoClientImpl @Configuration class AppConfig { @@ -30,17 +26,5 @@ class AppConfig { @Bean fun observedAspect(observationRegistry: ObservationRegistry) = ObservedAspect(observationRegistry) - - @Bean - fun httpClientEngine(): HttpClientEngine = CIO.create() - - @Bean - fun nekoClient( - httpClientEngine: HttpClientEngine, - @Value("\${neko.baseUrl}") baseUrl: String, - ): NekoClient = NekoClientImpl( - engine = httpClientEngine, - baseUrl = baseUrl, - ) } diff --git a/src/main/kotlin/com/github/dannecron/demo/http/controllers/CustomerController.kt b/src/main/kotlin/com/github/dannecron/demo/http/controllers/CustomerController.kt deleted file mode 100644 index a372987..0000000 --- a/src/main/kotlin/com/github/dannecron/demo/http/controllers/CustomerController.kt +++ /dev/null @@ -1,30 +0,0 @@ -package com.github.dannecron.demo.http.controllers - -import com.github.dannecron.demo.core.services.customer.CustomerService -import com.github.dannecron.demo.http.exceptions.NotFoundException -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.http.HttpStatus -import org.springframework.http.MediaType -import org.springframework.http.ResponseEntity -import org.springframework.web.bind.annotation.GetMapping -import org.springframework.web.bind.annotation.PathVariable -import org.springframework.web.bind.annotation.RequestMapping -import org.springframework.web.bind.annotation.RestController -import java.util.* - -@RestController -@RequestMapping(value = ["/api/customer"], produces = [MediaType.APPLICATION_JSON_VALUE]) -class CustomerController( - @Autowired - private val customerService: CustomerService, -) { - @GetMapping("/{guid}") - @Throws(NotFoundException::class) - fun getCustomer( - @PathVariable guid: UUID, - ): ResponseEntity { - val customer = customerService.findByGuid(guid) ?: throw NotFoundException() - - return ResponseEntity(customer, HttpStatus.OK) - } -} diff --git a/src/main/kotlin/com/github/dannecron/demo/http/controllers/GreetingController.kt b/src/main/kotlin/com/github/dannecron/demo/http/controllers/GreetingController.kt deleted file mode 100644 index 17538b8..0000000 --- a/src/main/kotlin/com/github/dannecron/demo/http/controllers/GreetingController.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.github.dannecron.demo.http.controllers - -import com.github.dannecron.demo.providers.html.renderProductTable -import org.springframework.web.bind.annotation.GetMapping -import org.springframework.web.bind.annotation.ResponseBody -import org.springframework.web.bind.annotation.RestController - -@RestController -class GreetingController { - @GetMapping("/greeting") - fun greet(): String { - return "Hello World!" - } - - @GetMapping(value = ["/example/html"], produces = ["text/html"]) - @ResponseBody - fun exampleHtml(): String { - return renderProductTable() - } -} diff --git a/src/main/kotlin/com/github/dannecron/demo/http/controllers/NekoController.kt b/src/main/kotlin/com/github/dannecron/demo/http/controllers/NekoController.kt deleted file mode 100644 index ee0ad0a..0000000 --- a/src/main/kotlin/com/github/dannecron/demo/http/controllers/NekoController.kt +++ /dev/null @@ -1,26 +0,0 @@ -package com.github.dannecron.demo.http.controllers - -import com.github.dannecron.demo.http.responses.neko.ImagesResponse -import com.github.dannecron.demo.services.neko.Client -import org.springframework.http.HttpStatus -import org.springframework.http.MediaType -import org.springframework.http.ResponseEntity -import org.springframework.web.bind.annotation.* - -@RestController -@RequestMapping(value = ["/api/neko"], produces = [MediaType.APPLICATION_JSON_VALUE]) -class NekoController( - private val nekoClient: Client, -) { - @GetMapping("/categories") - fun categories(): ResponseEntity = ResponseEntity(nekoClient.getCategories(), HttpStatus.OK) - - @GetMapping("/images/{category}") - fun images( - @PathVariable category: String, - @RequestParam imagesCount: Int = 1, - ): ResponseEntity = ResponseEntity( - ImagesResponse(baseImages = nekoClient.getImages(category = category, amount = imagesCount)), - HttpStatus.OK, - ) -} diff --git a/src/main/kotlin/com/github/dannecron/demo/http/controllers/ProductController.kt b/src/main/kotlin/com/github/dannecron/demo/http/controllers/ProductController.kt deleted file mode 100644 index b27d18f..0000000 --- a/src/main/kotlin/com/github/dannecron/demo/http/controllers/ProductController.kt +++ /dev/null @@ -1,110 +0,0 @@ -package com.github.dannecron.demo.http.controllers - -import com.github.dannecron.demo.core.dto.Product -import com.github.dannecron.demo.core.exceptions.AlreadyDeletedException -import com.github.dannecron.demo.core.exceptions.InvalidDataException -import com.github.dannecron.demo.core.exceptions.ProductNotFoundException -import com.github.dannecron.demo.core.services.product.ProductService -import com.github.dannecron.demo.http.exceptions.NotFoundException -import com.github.dannecron.demo.http.exceptions.UnprocessableException -import com.github.dannecron.demo.http.requests.CreateProductRequest -import com.github.dannecron.demo.http.responses.NotFoundResponse -import com.github.dannecron.demo.http.responses.makeOkResponse -import com.github.dannecron.demo.http.responses.page.PageResponse -import io.swagger.v3.oas.annotations.media.Content -import io.swagger.v3.oas.annotations.media.Schema -import io.swagger.v3.oas.annotations.responses.ApiResponse -import io.swagger.v3.oas.annotations.responses.ApiResponses -import jakarta.validation.Valid -import org.springdoc.core.annotations.ParameterObject -import org.springframework.data.domain.Pageable -import org.springframework.http.HttpStatus -import org.springframework.http.MediaType -import org.springframework.http.ResponseEntity -import org.springframework.web.bind.annotation.* -import java.util.* - -@RestController -@RequestMapping(value = ["/api/product"], produces = [MediaType.APPLICATION_JSON_VALUE]) -class ProductController( - private val productService: ProductService, -) { - @GetMapping("/{guid}") - @Throws(NotFoundException::class) - @ApiResponses(value = [ - ApiResponse(responseCode = "200", content = [ - Content(mediaType = "application/json", schema = Schema(implementation = Product::class)), - ]), - ApiResponse(responseCode = "404", content = [ - Content(mediaType = "application/json", schema = Schema(implementation = NotFoundResponse::class)) - ]) - ]) - fun getProduct( - @PathVariable guid: UUID, - ): ResponseEntity { - val product = productService.findByGuid(guid = guid) ?: throw NotFoundException() - - return ResponseEntity(product, HttpStatus.OK) - } - - @GetMapping("") - @ApiResponses(value = [ - ApiResponse(responseCode = "200", content = [ - Content(mediaType = "application/json", schema = Schema(implementation = PageResponse::class)), - ]), - ]) - fun getProducts( - @ParameterObject pageable: Pageable, - ): ResponseEntity { - val products = productService.findAll(pageable) - - return ResponseEntity( - PageResponse(products), - HttpStatus.OK, - ) - } - - @PostMapping("/{guid}/sync") - @Throws(NotFoundException::class) - fun syncProductToKafka( - @PathVariable guid: UUID, - @RequestParam(required = false) topic: String? - ): ResponseEntity { - try { - productService.send(guid, topic) - } catch (_: InvalidDataException) { - throw UnprocessableException("cannot sync product to kafka") - } - - return ResponseEntity(makeOkResponse(), HttpStatus.OK) - } - - @PostMapping(value = [""], consumes = [MediaType.APPLICATION_JSON_VALUE]) - fun createProduct( - @Valid @RequestBody product: CreateProductRequest, - ): ResponseEntity { - val saved = productService.create( - product.name, - product.price, - product.description, - ) - - return ResponseEntity(saved, HttpStatus.CREATED) - } - - @DeleteMapping("/{guid}") - @Throws(NotFoundException::class, UnprocessableException::class) - fun deleteProduct( - @PathVariable guid: UUID, - ): ResponseEntity { - try { - productService.delete(guid) - } catch (_: ProductNotFoundException) { - throw NotFoundException() - } catch (_: AlreadyDeletedException) { - throw UnprocessableException("product already deleted") - } - - return ResponseEntity(makeOkResponse(), HttpStatus.OK) - } -} diff --git a/src/main/kotlin/com/github/dannecron/demo/http/exceptions/NotFoundException.kt b/src/main/kotlin/com/github/dannecron/demo/http/exceptions/NotFoundException.kt deleted file mode 100644 index c6cb577..0000000 --- a/src/main/kotlin/com/github/dannecron/demo/http/exceptions/NotFoundException.kt +++ /dev/null @@ -1,3 +0,0 @@ -package com.github.dannecron.demo.http.exceptions - -class NotFoundException: RuntimeException() diff --git a/src/main/kotlin/com/github/dannecron/demo/http/exceptions/UnprocessableException.kt b/src/main/kotlin/com/github/dannecron/demo/http/exceptions/UnprocessableException.kt deleted file mode 100644 index b1693a6..0000000 --- a/src/main/kotlin/com/github/dannecron/demo/http/exceptions/UnprocessableException.kt +++ /dev/null @@ -1,3 +0,0 @@ -package com.github.dannecron.demo.http.exceptions - -class UnprocessableException(override val message: String): RuntimeException(message) diff --git a/src/main/kotlin/com/github/dannecron/demo/http/responses/BadRequestResponse.kt b/src/main/kotlin/com/github/dannecron/demo/http/responses/BadRequestResponse.kt deleted file mode 100644 index 25958d8..0000000 --- a/src/main/kotlin/com/github/dannecron/demo/http/responses/BadRequestResponse.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.github.dannecron.demo.http.responses - -data class BadRequestResponse( - val cause: String, -): BaseResponse(status = ResponseStatus.BAD_REQUEST) diff --git a/src/main/kotlin/com/github/dannecron/demo/http/responses/BaseResponse.kt b/src/main/kotlin/com/github/dannecron/demo/http/responses/BaseResponse.kt deleted file mode 100644 index e36d6c1..0000000 --- a/src/main/kotlin/com/github/dannecron/demo/http/responses/BaseResponse.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.github.dannecron.demo.http.responses - -open class BaseResponse(val status: ResponseStatus) - -fun makeOkResponse(): BaseResponse = BaseResponse(status = ResponseStatus.OK) diff --git a/src/main/kotlin/com/github/dannecron/demo/http/responses/NotFoundResponse.kt b/src/main/kotlin/com/github/dannecron/demo/http/responses/NotFoundResponse.kt deleted file mode 100644 index 0a449d7..0000000 --- a/src/main/kotlin/com/github/dannecron/demo/http/responses/NotFoundResponse.kt +++ /dev/null @@ -1,3 +0,0 @@ -package com.github.dannecron.demo.http.responses - -class NotFoundResponse: BaseResponse(ResponseStatus.NOT_FOUND) diff --git a/src/main/kotlin/com/github/dannecron/demo/http/responses/UnprocessableResponse.kt b/src/main/kotlin/com/github/dannecron/demo/http/responses/UnprocessableResponse.kt deleted file mode 100644 index 1627026..0000000 --- a/src/main/kotlin/com/github/dannecron/demo/http/responses/UnprocessableResponse.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.github.dannecron.demo.http.responses - -import org.springframework.validation.ObjectError - -class UnprocessableResponse( - val cause: String, - val errors: List? -): BaseResponse(status = ResponseStatus.UNPROCESSABLE) { - constructor(cause: String): this(cause, null) -} diff --git a/src/main/kotlin/com/github/dannecron/demo/http/responses/neko/ImagesResponse.kt b/src/main/kotlin/com/github/dannecron/demo/http/responses/neko/ImagesResponse.kt deleted file mode 100644 index c5e4705..0000000 --- a/src/main/kotlin/com/github/dannecron/demo/http/responses/neko/ImagesResponse.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.github.dannecron.demo.http.responses.neko - -import kotlinx.serialization.Serializable -import com.github.dannecron.demo.services.neko.dto.ImagesResponse as BaseResponse - -@Serializable -data class ImagesResponse( - val images: List, -) { - constructor(baseImages: BaseResponse): this( - images = baseImages.results.map { - Image(it.url, it.animeName, it.artistHref, it.artistName, it.sourceUrl) - } - ) -} diff --git a/src/test/kotlin/com/github/dannecron/demo/BaseUnitTest.kt b/src/test/kotlin/com/github/dannecron/demo/BaseUnitTest.kt deleted file mode 100644 index 3ca23d5..0000000 --- a/src/test/kotlin/com/github/dannecron/demo/BaseUnitTest.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.github.dannecron.demo - -import org.springframework.boot.test.context.TestConfiguration - -open class BaseUnitTest { - @TestConfiguration - class TestConfig -} diff --git a/src/test/kotlin/com/github/dannecron/demo/http/controllers/NekoControllerTest.kt b/src/test/kotlin/com/github/dannecron/demo/http/controllers/NekoControllerTest.kt deleted file mode 100644 index e22eb82..0000000 --- a/src/test/kotlin/com/github/dannecron/demo/http/controllers/NekoControllerTest.kt +++ /dev/null @@ -1,53 +0,0 @@ -package com.github.dannecron.demo.http.controllers - -import com.github.dannecron.demo.BaseUnitTest -import com.github.dannecron.demo.services.neko.Client -import com.github.dannecron.demo.services.neko.dto.Image -import com.github.dannecron.demo.services.neko.dto.ImagesResponse -import org.mockito.kotlin.doReturn -import org.mockito.kotlin.whenever -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest -import org.springframework.boot.test.mock.mockito.MockBean -import org.springframework.http.MediaType -import org.springframework.test.web.servlet.MockMvc -import org.springframework.test.web.servlet.get -import kotlin.test.Test - -@WebMvcTest(NekoController::class) -class NekoControllerTest( - @Autowired val mockMvc: MockMvc, -): BaseUnitTest() { - @MockBean - private lateinit var nekoClient: Client - - @Test - fun categories_success() { - whenever(nekoClient.getCategories()) doReturn setOf("cat1", "cat2") - - mockMvc.get("/api/neko/categories") - .andExpect { status { isOk() } } - .andExpect { content { contentType(MediaType.APPLICATION_JSON) } } - .andExpect { content { string("""["cat1","cat2"]""") } } - } - - @Test - fun images_success() { - val category = "some" - val animeName = "boku no pico" - whenever(nekoClient.getImages(category = category, amount = 1)) doReturn ImagesResponse( - results = listOf( - Image( - "http://localhost", - animeName = animeName, - ) - ) - ) - - mockMvc.get("/api/neko/images/$category") - .andExpect { status { isOk() } } - .andExpect { content { contentType(MediaType.APPLICATION_JSON) } } - .andExpect { jsonPath("\$.images[0].animeName") { value(animeName) } } - .andExpect { jsonPath("\$.images[0].artistName") { value(null) } } - } -}