move controllers to sub-project, fix dependencies

This commit is contained in:
Savosin Denis
2025-06-03 15:28:03 +07:00
parent 7e64c57a5a
commit d4e36e7354
27 changed files with 401 additions and 362 deletions

View File

@@ -0,0 +1,7 @@
dependencies {
implementation(project(":edge-contracts"))
implementation(project(":core"))
implementation(rootProject.libs.springBoot.starter.web)
implementation(rootProject.libs.springData.commons)
}

View File

@@ -0,0 +1,66 @@
package com.github.dannecron.demo.edgerest
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
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.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<BadRequestResponse> = ResponseEntity(
BadRequestResponse(exception.message.toString()),
HttpStatus.BAD_REQUEST,
)
// 404
@ExceptionHandler(NotFoundException::class)
@ResponseStatus(HttpStatus.NOT_FOUND)
fun handleNotFound(): ResponseEntity<NotFoundResponse> = ResponseEntity(NotFoundResponse(), HttpStatus.NOT_FOUND)
// 422
@ExceptionHandler(UnprocessableException::class)
@ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY)
fun handleUnprocessable(exception: UnprocessableException): ResponseEntity<UnprocessableResponse> = ResponseEntity(
UnprocessableResponse(exception.message),
HttpStatus.UNPROCESSABLE_ENTITY,
)
@ExceptionHandler(MethodArgumentNotValidException::class)
@ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY)
fun handleMethodArgumentNotValid(
exception: MethodArgumentNotValidException,
): ResponseEntity<UnprocessableResponse> = ResponseEntity(
UnprocessableResponse(exception.javaClass.name, exception.allErrors),
HttpStatus.UNPROCESSABLE_ENTITY,
)
// 500
@ExceptionHandler(RuntimeException::class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
fun handleUnexpectedRuntimeException(exception: RuntimeException): ResponseEntity<BaseResponse> = ResponseEntity(
BaseResponse(ResponseStatusModel.INTERNAL_ERROR),
HttpStatus.INTERNAL_SERVER_ERROR,
).also {
logger.error("internal server error", exception)
}
}

View File

@@ -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<GetCustomerResponse> {
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,
)
}

View File

@@ -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")
}
}

View File

@@ -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<Set<String>> =
ResponseEntity(nekoService.getCategories(), HttpStatus.OK)
override fun images(category: String, imagesCount: Int): ResponseEntity<GetNekoImagesResponse> =
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
)
}

View File

@@ -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<PageResponse<ProductApiModel>> {
val products = productService.findAll(pageable)
return ResponseEntity(
PageResponse(products.map { it.toApiModel() }),
HttpStatus.OK,
)
}
@Throws(NotFoundException::class)
override fun getProduct(guid: UUID): ResponseEntity<ProductApiModel> {
val product = productService.findByGuid(guid = guid) ?: throw NotFoundException(null)
return ResponseEntity(product.toApiModel(), HttpStatus.OK)
}
override fun createProduct(product: CreateProductRequest): ResponseEntity<ProductApiModel> {
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<BaseResponse> {
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<BaseResponse> {
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(),
)
}

View File

@@ -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)
}
}

View File

@@ -0,0 +1,119 @@
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.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
import java.util.UUID
import kotlin.test.Test
@WebMvcTest(CustomerController::class)
@AutoConfigureMockMvc
@ContextConfiguration(
classes = [
WebTestConfig::class,
CustomerController::class,
ExceptionHandler::class,
]
)
class CustomerControllerTest {
@Autowired
private lateinit var mockMvc: MockMvc
@MockBean
private lateinit var customerService: CustomerService
@Test
fun getCustomer_successWithCity() {
val customerId = 22L
val cityId = 11L
val customerGuid = UUID.randomUUID()
val customerExtended = CustomerExtended(
customer = Customer(
id = customerId,
guid = customerGuid,
name = "Test Person",
cityId = cityId,
createdAt = OffsetDateTime.now().minusHours(1),
updatedAt = OffsetDateTime.now(),
),
city = City(
id = cityId,
guid = UUID.randomUUID(),
name = "Test City",
createdAt = OffsetDateTime.now().minusWeeks(1),
updatedAt = null,
deletedAt = null
)
)
whenever(customerService.findByGuid(
eq(customerGuid),
)) doReturn customerExtended
mockMvc.get("/api/customer/$customerGuid")
.andExpect { status { isOk() } }
.andExpect { content { contentType(MediaType.APPLICATION_JSON) } }
.andExpect { jsonPath("\$.customer.id") { value(customerId) } }
.andExpect { jsonPath("\$.customer.cityId") { value(cityId) } }
.andExpect { jsonPath("\$.city.id") { value(cityId) } }
}
@Test
fun getCustomer_successNoCity() {
val customerId = 22L
val customerGuid = UUID.randomUUID()
val customerExtended = CustomerExtended(
customer = Customer(
id = customerId,
guid = customerGuid,
name = "Test Person",
cityId = null,
createdAt = OffsetDateTime.now().minusHours(1),
updatedAt = OffsetDateTime.now(),
),
city = null,
)
whenever(customerService.findByGuid(
eq(customerGuid),
)) doReturn customerExtended
mockMvc.get("/api/customer/$customerGuid")
.andExpect { status { isOk() } }
.andExpect { content { contentType(MediaType.APPLICATION_JSON) } }
.andExpect { jsonPath("\$.customer.id") { value(customerId) } }
.andExpect { jsonPath("\$.customer.cityId") { value(null) } }
.andExpect { jsonPath("\$.city") { value(null) } }
}
@Test
fun getCustomer_successNotFound() {
val customerGuid = UUID.randomUUID()
whenever(customerService.findByGuid(
eq(customerGuid),
)) doReturn null
mockMvc.get("/api/customer/$customerGuid")
.andExpect { status { isNotFound() } }
.andExpect { content { contentType(MediaType.APPLICATION_JSON) } }
.andExpect { jsonPath("\$.status") { value(ResponseStatusModel.NOT_FOUND.status) } }
}
}

View File

@@ -0,0 +1,45 @@
package com.github.dannecron.demo.edgerest.controllers
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(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")
.andExpect { status { isOk() } }
.andExpect { content { contentType("text/plain;charset=UTF-8") } }
.andExpect { content { string("Hello World!") } }
}
@Test
@Ignore
fun exampleHtml_shouldSeeRenderedHtml() {
mockMvc.get("/example/html")
.andExpect { status { isOk() } }
.andExpect { content { contentType("text/html;charset=UTF-8") } }
.andExpect { content { string(StringContains("Product")) } }
}
}

View File

@@ -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)
}
}

View File

@@ -0,0 +1,181 @@
package com.github.dannecron.demo.edgerest.controllers
import com.github.dannecron.demo.core.dto.Product
import com.github.dannecron.demo.core.services.product.ProductService
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
import org.mockito.kotlin.any
import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.times
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
import org.springframework.test.web.servlet.post
import org.springframework.web.bind.MethodArgumentNotValidException
import java.time.OffsetDateTime
import java.time.format.DateTimeFormatter
import java.util.UUID
@WebMvcTest(ProductController::class)
@AutoConfigureMockMvc
@ContextConfiguration(
classes = [
WebTestConfig::class,
ProductController::class,
ExceptionHandler::class,
]
)
class ProductControllerTest {
@Autowired
private lateinit var mockMvc: MockMvc
@MockBean
private lateinit var productService: ProductService
private val guid = UUID.randomUUID()
private val now = OffsetDateTime.now()
private val productId = 12L
private val productName = "new-product"
private val productPrice = 20123L
private val product = Product(
id = productId,
guid = guid,
name = productName,
description = null,
price = productPrice,
createdAt = now,
updatedAt = null,
deletedAt = null,
)
@Test
fun `getProduct - 200`() {
whenever(productService.findByGuid(any())).thenReturn(product)
mockMvc.get("/api/product/$guid")
.andExpect { status { isOk() } }
.andExpect { content { contentType(MediaType.APPLICATION_JSON) } }
.andExpect { jsonPath("\$.id") { value(product.id.toString()) } }
.andExpect { jsonPath("\$.guid") { value(guid.toString()) } }
.andExpect { jsonPath("\$.name") { value(productName) } }
.andExpect { jsonPath("\$.createdAt") { value(now.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)) } }
.andExpect { jsonPath("\$.updatedAt") { value(nullValue()) } }
verify(productService, times(1)).findByGuid(guid)
}
@Test
fun `getProduct - 404`() {
whenever(productService.findByGuid(any())).thenReturn(null)
mockMvc.get("/api/product/$guid")
.andExpect { status { isNotFound() } }
.andExpect { content { contentType(MediaType.APPLICATION_JSON) } }
.andExpect { jsonPath("\$.status") { value(ResponseStatusModel.NOT_FOUND.status) } }
verify(productService, times(1)).findByGuid(guid)
}
@Test
fun `getProducts - 200`() {
val pageRequest = PageRequest.of(1, 2, Sort.by(Sort.Direction.DESC, "createdAt"))
whenever(productService.findAll(any()))
.thenReturn(PageImpl(listOf(product)))
mockMvc.get("/api/product?page=1&size=2&sort=createdAt,desc")
.andExpect { status { isOk() } }
.andExpect { content { contentType(MediaType.APPLICATION_JSON) } }
.andExpect { jsonPath("\$.meta.total") { value(1) } }
.andExpect { jsonPath("\$.meta.pages") { value(1) } }
.andExpect { jsonPath("\$.data") { isArray() } }
.andExpect { jsonPath("\$.data[0].id") { value(productId) } }
.andExpect { jsonPath("\$.data[0].name") { value(productName) } }
.andExpect { jsonPath("\$.data[0].description") { value(null) } }
.andExpect { jsonPath("\$.data[0].createdAt") { value(now.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)) } }
.andExpect { jsonPath("\$.data[0].priceDouble") { value(201.23) } }
.andExpect { jsonPath("\$.data[0].isDeleted") { value(false) } }
verify(productService, times(1)).findAll(pageRequest)
}
@Test
fun `createProduct - 200`() {
val reqBody = """{"name":"$productName","description":null,"price":$productPrice}"""
whenever(productService.create(any(), any(), anyOrNull())).thenReturn(product)
mockMvc.post("/api/product") {
contentType = MediaType.APPLICATION_JSON
content = reqBody
}
.andExpect { status { isCreated() } }
.andExpect { content { contentType(MediaType.APPLICATION_JSON) } }
.andExpect { jsonPath("\$.id") { value(productId) } }
verify(productService, times(1)).create(productName, productPrice, null)
}
@Test
fun `createProduct - 400 - no name param`() {
val reqBody = """{"description":null,"price":$productPrice}"""
mockMvc.post("/api/product") {
contentType = MediaType.APPLICATION_JSON
content = reqBody
}
.andExpect { status { isBadRequest() } }
.andExpect { content { contentType(MediaType.APPLICATION_JSON) } }
.andExpect { jsonPath("\$.status") { value(ResponseStatusModel.BAD_REQUEST.status) } }
.andExpect { jsonPath("\$.cause") { contains("name") } }
verifyNoInteractions(productService)
}
@Test
fun `createProduct - 400 - empty name param`() {
val reqBody = """{"name":"","description":null,"price":$productPrice}"""
mockMvc.post("/api/product") {
contentType = MediaType.APPLICATION_JSON
content = reqBody
}
.andExpect { status { isUnprocessableEntity() } }
.andExpect { content { contentType(MediaType.APPLICATION_JSON) } }
.andExpect { jsonPath("\$.status") { value(ResponseStatusModel.UNPROCESSABLE.status) } }
.andExpect { jsonPath("\$.cause") { value(MethodArgumentNotValidException::class.qualifiedName) } }
verifyNoInteractions(productService)
}
@Test
fun `deleteProduct - 200`() {
val deletedProduct = product.copy(deletedAt = now)
whenever(productService.delete(any())).thenReturn(deletedProduct)
mockMvc.delete("/api/product/${guid}")
.andExpect { status { isOk() } }
.andExpect { content { contentType(MediaType.APPLICATION_JSON) } }
.andExpect { jsonPath("\$.status") { value(ResponseStatusModel.OK.status) } }
verify(productService, times(1)).delete(guid)
}
}