mirror of
https://github.com/Dannecron/spring-boot-demo.git
synced 2025-12-25 16:22:35 +03:00
add send method to product service
This commit is contained in:
@@ -3,11 +3,10 @@ version = "single-version"
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(project(":db"))
|
implementation(project(":db"))
|
||||||
|
implementation(project(":edge-producing"))
|
||||||
|
|
||||||
implementation(rootProject.libs.spring.boot.starter.actuator)
|
implementation(rootProject.libs.spring.boot.starter.actuator)
|
||||||
implementation(rootProject.libs.spring.boot.starter.jdbc)
|
implementation(rootProject.libs.spring.boot.starter.jdbc)
|
||||||
implementation(rootProject.libs.spring.boot.starter.validation)
|
|
||||||
implementation(rootProject.libs.json.schema.validator)
|
|
||||||
|
|
||||||
testImplementation(rootProject.libs.spring.boot.starter.actuatorAutoconfigure)
|
testImplementation(rootProject.libs.spring.boot.starter.actuatorAutoconfigure)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package com.github.dannecron.demo.core.exceptions
|
||||||
|
|
||||||
|
class InvalidDataException(
|
||||||
|
override val message: String,
|
||||||
|
override val cause: Throwable?,
|
||||||
|
) : RuntimeException(message, cause)
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
package com.github.dannecron.demo.core.exceptions
|
package com.github.dannecron.demo.core.exceptions
|
||||||
|
|
||||||
class ProductNotFoundException: ModelNotFoundException("product")
|
class ProductNotFoundException: ModelNotFoundException("json-schemas/kafka/product")
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package com.github.dannecron.demo.core.services.product
|
|||||||
|
|
||||||
import com.github.dannecron.demo.core.dto.Product
|
import com.github.dannecron.demo.core.dto.Product
|
||||||
import com.github.dannecron.demo.core.exceptions.AlreadyDeletedException
|
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.exceptions.ProductNotFoundException
|
||||||
import org.springframework.data.domain.Page
|
import org.springframework.data.domain.Page
|
||||||
import org.springframework.data.domain.Pageable
|
import org.springframework.data.domain.Pageable
|
||||||
@@ -16,4 +17,7 @@ interface ProductService {
|
|||||||
|
|
||||||
@Throws(ProductNotFoundException::class, AlreadyDeletedException::class)
|
@Throws(ProductNotFoundException::class, AlreadyDeletedException::class)
|
||||||
fun delete(guid: UUID): Product
|
fun delete(guid: UUID): Product
|
||||||
|
|
||||||
|
@Throws(ProductNotFoundException::class, InvalidDataException::class)
|
||||||
|
fun send(guid: UUID, topic: String?)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,21 +2,26 @@ package com.github.dannecron.demo.core.services.product
|
|||||||
|
|
||||||
import com.github.dannecron.demo.core.dto.Product
|
import com.github.dannecron.demo.core.dto.Product
|
||||||
import com.github.dannecron.demo.core.exceptions.AlreadyDeletedException
|
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.exceptions.ProductNotFoundException
|
||||||
import com.github.dannecron.demo.core.services.generation.CommonGenerator
|
import com.github.dannecron.demo.core.services.generation.CommonGenerator
|
||||||
import com.github.dannecron.demo.core.utils.LoggerDelegate
|
import com.github.dannecron.demo.core.utils.LoggerDelegate
|
||||||
import com.github.dannecron.demo.db.entity.ProductEntity
|
import com.github.dannecron.demo.db.entity.ProductEntity
|
||||||
import com.github.dannecron.demo.db.repository.ProductRepository
|
import com.github.dannecron.demo.db.repository.ProductRepository
|
||||||
|
import com.github.dannecron.demo.edgeproducing.dto.ProductDto
|
||||||
|
import com.github.dannecron.demo.edgeproducing.producer.ProductProducer
|
||||||
import net.logstash.logback.marker.Markers
|
import net.logstash.logback.marker.Markers
|
||||||
import org.springframework.data.domain.Page
|
import org.springframework.data.domain.Page
|
||||||
import org.springframework.data.domain.Pageable
|
import org.springframework.data.domain.Pageable
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
|
import java.time.format.DateTimeFormatter
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
class ProductServiceImpl(
|
class ProductServiceImpl(
|
||||||
private val productRepository: ProductRepository,
|
private val productRepository: ProductRepository,
|
||||||
private val commonGenerator: CommonGenerator,
|
private val commonGenerator: CommonGenerator,
|
||||||
|
private val productProducer: ProductProducer,
|
||||||
): ProductService {
|
): ProductService {
|
||||||
private val logger by LoggerDelegate()
|
private val logger by LoggerDelegate()
|
||||||
|
|
||||||
@@ -58,6 +63,13 @@ class ProductServiceImpl(
|
|||||||
.toCore()
|
.toCore()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Throws(ProductNotFoundException::class, InvalidDataException::class)
|
||||||
|
override fun send(guid: UUID, topic: String?) {
|
||||||
|
val product = findByGuid(guid) ?: throw ProductNotFoundException()
|
||||||
|
|
||||||
|
productProducer.produceProductSync(product.toProducingDto())
|
||||||
|
}
|
||||||
|
|
||||||
private fun ProductEntity.toCore() = Product(
|
private fun ProductEntity.toCore() = Product(
|
||||||
id = id!!,
|
id = id!!,
|
||||||
guid = guid,
|
guid = guid,
|
||||||
@@ -79,4 +91,15 @@ class ProductServiceImpl(
|
|||||||
updatedAt = updatedAt,
|
updatedAt = updatedAt,
|
||||||
deletedAt = deletedAt,
|
deletedAt = deletedAt,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
private fun Product.toProducingDto() = ProductDto(
|
||||||
|
id = id,
|
||||||
|
guid = guid.toString(),
|
||||||
|
name = name,
|
||||||
|
description = description,
|
||||||
|
price = price,
|
||||||
|
createdAt = createdAt.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME),
|
||||||
|
updatedAt = updatedAt?.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME),
|
||||||
|
deletedAt = deletedAt?.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import com.github.dannecron.demo.core.exceptions.ProductNotFoundException
|
|||||||
import com.github.dannecron.demo.core.services.generation.CommonGenerator
|
import com.github.dannecron.demo.core.services.generation.CommonGenerator
|
||||||
import com.github.dannecron.demo.db.entity.ProductEntity
|
import com.github.dannecron.demo.db.entity.ProductEntity
|
||||||
import com.github.dannecron.demo.db.repository.ProductRepository
|
import com.github.dannecron.demo.db.repository.ProductRepository
|
||||||
|
import com.github.dannecron.demo.edgeproducing.dto.ProductDto
|
||||||
|
import com.github.dannecron.demo.edgeproducing.producer.ProductProducer
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import org.junit.jupiter.api.assertThrows
|
import org.junit.jupiter.api.assertThrows
|
||||||
import org.mockito.kotlin.any
|
import org.mockito.kotlin.any
|
||||||
@@ -14,8 +16,10 @@ import org.mockito.kotlin.mock
|
|||||||
import org.mockito.kotlin.never
|
import org.mockito.kotlin.never
|
||||||
import org.mockito.kotlin.times
|
import org.mockito.kotlin.times
|
||||||
import org.mockito.kotlin.verify
|
import org.mockito.kotlin.verify
|
||||||
|
import org.mockito.kotlin.verifyNoInteractions
|
||||||
import org.mockito.kotlin.whenever
|
import org.mockito.kotlin.whenever
|
||||||
import java.time.OffsetDateTime
|
import java.time.OffsetDateTime
|
||||||
|
import java.time.format.DateTimeFormatter
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
@@ -24,6 +28,7 @@ class ProductServiceImplTest {
|
|||||||
private val mockCurrentTime = OffsetDateTime.now()
|
private val mockCurrentTime = OffsetDateTime.now()
|
||||||
|
|
||||||
private val productRepository: ProductRepository = mock()
|
private val productRepository: ProductRepository = mock()
|
||||||
|
private val productProducer: ProductProducer = mock()
|
||||||
private val commonGenerator: CommonGenerator = mock {
|
private val commonGenerator: CommonGenerator = mock {
|
||||||
on { generateUUID() } doReturn mockGuid
|
on { generateUUID() } doReturn mockGuid
|
||||||
on { generateCurrentTime() } doReturn mockCurrentTime
|
on { generateCurrentTime() } doReturn mockCurrentTime
|
||||||
@@ -32,6 +37,7 @@ class ProductServiceImplTest {
|
|||||||
private val productService = ProductServiceImpl(
|
private val productService = ProductServiceImpl(
|
||||||
productRepository = productRepository,
|
productRepository = productRepository,
|
||||||
commonGenerator = commonGenerator,
|
commonGenerator = commonGenerator,
|
||||||
|
productProducer = productProducer,
|
||||||
)
|
)
|
||||||
|
|
||||||
private val guid = UUID.randomUUID()
|
private val guid = UUID.randomUUID()
|
||||||
@@ -55,6 +61,16 @@ class ProductServiceImplTest {
|
|||||||
updatedAt = mockCurrentTime.minusHours(2),
|
updatedAt = mockCurrentTime.minusHours(2),
|
||||||
deletedAt = null,
|
deletedAt = null,
|
||||||
)
|
)
|
||||||
|
private val producingProductDto = ProductDto(
|
||||||
|
id = 123,
|
||||||
|
guid = guid.toString(),
|
||||||
|
name = "name",
|
||||||
|
description = "description",
|
||||||
|
price = 10050,
|
||||||
|
createdAt = mockCurrentTime.minusDays(1).format(DateTimeFormatter.ISO_OFFSET_DATE_TIME),
|
||||||
|
updatedAt = mockCurrentTime.minusHours(2).format(DateTimeFormatter.ISO_OFFSET_DATE_TIME),
|
||||||
|
deletedAt = null,
|
||||||
|
)
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun create() {
|
fun create() {
|
||||||
@@ -127,4 +143,30 @@ class ProductServiceImplTest {
|
|||||||
verify(productRepository, times(1)).findByGuid(guid)
|
verify(productRepository, times(1)).findByGuid(guid)
|
||||||
verify(productRepository, never()).save(any())
|
verify(productRepository, never()).save(any())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `send - success`() {
|
||||||
|
val topic = "some-topic"
|
||||||
|
|
||||||
|
whenever(productRepository.findByGuid(any())).thenReturn(productEntity)
|
||||||
|
|
||||||
|
productService.send(guid, topic)
|
||||||
|
|
||||||
|
verify(productRepository, times(1)).findByGuid(guid)
|
||||||
|
verify(productProducer, times(1)).produceProductSync(producingProductDto)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `send - not found`() {
|
||||||
|
val topic = "some-topic"
|
||||||
|
|
||||||
|
whenever(productRepository.findByGuid(any())).thenReturn(null)
|
||||||
|
|
||||||
|
assertThrows<ProductNotFoundException> {
|
||||||
|
productService.send(guid, topic)
|
||||||
|
}
|
||||||
|
|
||||||
|
verify(productRepository, times(1)).findByGuid(guid)
|
||||||
|
verifyNoInteractions(productProducer)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package com.github.dannecron.demo.http.controllers
|
|||||||
|
|
||||||
import com.github.dannecron.demo.core.dto.Product
|
import com.github.dannecron.demo.core.dto.Product
|
||||||
import com.github.dannecron.demo.core.exceptions.AlreadyDeletedException
|
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.exceptions.ProductNotFoundException
|
||||||
import com.github.dannecron.demo.core.services.product.ProductService
|
import com.github.dannecron.demo.core.services.product.ProductService
|
||||||
import com.github.dannecron.demo.http.exceptions.NotFoundException
|
import com.github.dannecron.demo.http.exceptions.NotFoundException
|
||||||
@@ -10,8 +11,6 @@ import com.github.dannecron.demo.http.requests.CreateProductRequest
|
|||||||
import com.github.dannecron.demo.http.responses.NotFoundResponse
|
import com.github.dannecron.demo.http.responses.NotFoundResponse
|
||||||
import com.github.dannecron.demo.http.responses.makeOkResponse
|
import com.github.dannecron.demo.http.responses.makeOkResponse
|
||||||
import com.github.dannecron.demo.http.responses.page.PageResponse
|
import com.github.dannecron.demo.http.responses.page.PageResponse
|
||||||
import com.github.dannecron.demo.services.ProductSyncService
|
|
||||||
import com.github.dannecron.demo.services.kafka.exceptions.InvalidArgumentException
|
|
||||||
import io.swagger.v3.oas.annotations.media.Content
|
import io.swagger.v3.oas.annotations.media.Content
|
||||||
import io.swagger.v3.oas.annotations.media.Schema
|
import io.swagger.v3.oas.annotations.media.Schema
|
||||||
import io.swagger.v3.oas.annotations.responses.ApiResponse
|
import io.swagger.v3.oas.annotations.responses.ApiResponse
|
||||||
@@ -29,7 +28,6 @@ import java.util.*
|
|||||||
@RequestMapping(value = ["/api/product"], produces = [MediaType.APPLICATION_JSON_VALUE])
|
@RequestMapping(value = ["/api/product"], produces = [MediaType.APPLICATION_JSON_VALUE])
|
||||||
class ProductController(
|
class ProductController(
|
||||||
private val productService: ProductService,
|
private val productService: ProductService,
|
||||||
private val productSyncService: ProductSyncService,
|
|
||||||
) {
|
) {
|
||||||
@GetMapping("/{guid}")
|
@GetMapping("/{guid}")
|
||||||
@Throws(NotFoundException::class)
|
@Throws(NotFoundException::class)
|
||||||
@@ -73,8 +71,8 @@ class ProductController(
|
|||||||
@RequestParam(required = false) topic: String?
|
@RequestParam(required = false) topic: String?
|
||||||
): ResponseEntity<Any> {
|
): ResponseEntity<Any> {
|
||||||
try {
|
try {
|
||||||
productSyncService.syncToKafka(guid, topic)
|
productService.send(guid, topic)
|
||||||
} catch (_: InvalidArgumentException) {
|
} catch (_: InvalidDataException) {
|
||||||
throw UnprocessableException("cannot sync product to kafka")
|
throw UnprocessableException("cannot sync product to kafka")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import com.github.dannecron.demo.BaseUnitTest
|
|||||||
import com.github.dannecron.demo.core.dto.Product
|
import com.github.dannecron.demo.core.dto.Product
|
||||||
import com.github.dannecron.demo.core.services.product.ProductService
|
import com.github.dannecron.demo.core.services.product.ProductService
|
||||||
import com.github.dannecron.demo.http.responses.ResponseStatus
|
import com.github.dannecron.demo.http.responses.ResponseStatus
|
||||||
import com.github.dannecron.demo.services.ProductSyncService
|
|
||||||
import org.hamcrest.Matchers.contains
|
import org.hamcrest.Matchers.contains
|
||||||
import org.hamcrest.Matchers.nullValue
|
import org.hamcrest.Matchers.nullValue
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
@@ -39,9 +38,6 @@ class ProductControllerTest: BaseUnitTest() {
|
|||||||
@MockBean
|
@MockBean
|
||||||
private lateinit var productService: ProductService
|
private lateinit var productService: ProductService
|
||||||
|
|
||||||
@MockBean
|
|
||||||
private lateinit var productSyncService: ProductSyncService
|
|
||||||
|
|
||||||
private val guid = UUID.randomUUID()
|
private val guid = UUID.randomUUID()
|
||||||
private val now = OffsetDateTime.now()
|
private val now = OffsetDateTime.now()
|
||||||
private val productId = 12L
|
private val productId = 12L
|
||||||
@@ -72,7 +68,6 @@ class ProductControllerTest: BaseUnitTest() {
|
|||||||
.andExpect { jsonPath("\$.updatedAt") { value(nullValue()) } }
|
.andExpect { jsonPath("\$.updatedAt") { value(nullValue()) } }
|
||||||
|
|
||||||
verify(productService, times(1)).findByGuid(guid)
|
verify(productService, times(1)).findByGuid(guid)
|
||||||
verifyNoInteractions(productSyncService)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -85,7 +80,6 @@ class ProductControllerTest: BaseUnitTest() {
|
|||||||
.andExpect { jsonPath("\$.status") { value(ResponseStatus.NOT_FOUND.status) } }
|
.andExpect { jsonPath("\$.status") { value(ResponseStatus.NOT_FOUND.status) } }
|
||||||
|
|
||||||
verify(productService, times(1)).findByGuid(guid)
|
verify(productService, times(1)).findByGuid(guid)
|
||||||
verifyNoInteractions(productSyncService)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -109,7 +103,6 @@ class ProductControllerTest: BaseUnitTest() {
|
|||||||
.andExpect { jsonPath("\$.data[0].isDeleted") { value(false) } }
|
.andExpect { jsonPath("\$.data[0].isDeleted") { value(false) } }
|
||||||
|
|
||||||
verify(productService, times(1)).findAll(pageRequest)
|
verify(productService, times(1)).findAll(pageRequest)
|
||||||
verifyNoInteractions(productSyncService)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -127,7 +120,6 @@ class ProductControllerTest: BaseUnitTest() {
|
|||||||
.andExpect { jsonPath("\$.id") { value(productId) } }
|
.andExpect { jsonPath("\$.id") { value(productId) } }
|
||||||
|
|
||||||
verify(productService, times(1)).create(productName, productPrice, null)
|
verify(productService, times(1)).create(productName, productPrice, null)
|
||||||
verifyNoInteractions(productSyncService)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -144,7 +136,6 @@ class ProductControllerTest: BaseUnitTest() {
|
|||||||
.andExpect { jsonPath("\$.cause") { contains("name") } }
|
.andExpect { jsonPath("\$.cause") { contains("name") } }
|
||||||
|
|
||||||
verifyNoInteractions(productService)
|
verifyNoInteractions(productService)
|
||||||
verifyNoInteractions(productSyncService)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -161,7 +152,6 @@ class ProductControllerTest: BaseUnitTest() {
|
|||||||
.andExpect { jsonPath("\$.cause") { value(MethodArgumentNotValidException::class.qualifiedName) } }
|
.andExpect { jsonPath("\$.cause") { value(MethodArgumentNotValidException::class.qualifiedName) } }
|
||||||
|
|
||||||
verifyNoInteractions(productService)
|
verifyNoInteractions(productService)
|
||||||
verifyNoInteractions(productSyncService)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -176,6 +166,5 @@ class ProductControllerTest: BaseUnitTest() {
|
|||||||
.andExpect { jsonPath("\$.status") { value(ResponseStatus.OK.status) } }
|
.andExpect { jsonPath("\$.status") { value(ResponseStatus.OK.status) } }
|
||||||
|
|
||||||
verify(productService, times(1)).delete(guid)
|
verify(productService, times(1)).delete(guid)
|
||||||
verifyNoInteractions(productSyncService)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user