mirror of
https://github.com/Dannecron/spring-boot-demo.git
synced 2025-12-25 16:22:35 +03:00
move and refactor city and product services to core
This commit is contained in:
@@ -2,6 +2,9 @@ group = "com.github.dannecron.demo"
|
|||||||
version = "single-version"
|
version = "single-version"
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
implementation(project(":db"))
|
||||||
|
|
||||||
|
implementation(rootProject.libs.spring.boot.starter.jdbc)
|
||||||
implementation(rootProject.libs.spring.boot.starter.validation)
|
implementation(rootProject.libs.spring.boot.starter.validation)
|
||||||
implementation(rootProject.libs.json.schema.validator)
|
implementation(rootProject.libs.json.schema.validator)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.github.dannecron.demo.core.dto
|
||||||
|
|
||||||
|
import java.time.OffsetDateTime
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
data class City(
|
||||||
|
val id: Long,
|
||||||
|
val guid: UUID,
|
||||||
|
val name: String,
|
||||||
|
val createdAt: OffsetDateTime,
|
||||||
|
val updatedAt: OffsetDateTime?,
|
||||||
|
val deletedAt: OffsetDateTime?,
|
||||||
|
) {
|
||||||
|
fun isDeleted(): Boolean = deletedAt != null
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package com.github.dannecron.demo.core.dto
|
||||||
|
|
||||||
|
import java.time.OffsetDateTime
|
||||||
|
|
||||||
|
data class CityCreate(
|
||||||
|
val guid: String,
|
||||||
|
val name: String,
|
||||||
|
val createdAt: OffsetDateTime,
|
||||||
|
val updatedAt: OffsetDateTime?,
|
||||||
|
val deletedAt: OffsetDateTime?,
|
||||||
|
)
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package com.github.dannecron.demo.core.dto
|
||||||
|
|
||||||
|
import java.time.OffsetDateTime
|
||||||
|
import java.util.UUID
|
||||||
|
import kotlin.math.pow
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
data class Product(
|
||||||
|
val id: Long,
|
||||||
|
val guid: UUID,
|
||||||
|
val name: String,
|
||||||
|
val description: String?,
|
||||||
|
val price: Long,
|
||||||
|
val createdAt: OffsetDateTime,
|
||||||
|
val updatedAt: OffsetDateTime?,
|
||||||
|
val deletedAt: OffsetDateTime?,
|
||||||
|
) {
|
||||||
|
fun getPriceDouble(): Double = (price.toDouble() / 100).roundTo(2)
|
||||||
|
|
||||||
|
fun isDeleted(): Boolean = deletedAt != null
|
||||||
|
|
||||||
|
private fun Double.roundTo(numFractionDigits: Int): Double {
|
||||||
|
val factor = 10.0.pow(numFractionDigits.toDouble())
|
||||||
|
return (this * factor).roundToInt() / factor
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
package com.github.dannecron.demo.core.exceptions
|
||||||
|
|
||||||
|
class AlreadyDeletedException: RuntimeException()
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
package com.github.dannecron.demo.core.exceptions
|
||||||
|
|
||||||
|
class CityNotFoundException: ModelNotFoundException("city")
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
package com.github.dannecron.demo.core.exceptions
|
||||||
|
|
||||||
|
open class ModelNotFoundException(entityName: String): RuntimeException("$entityName not found")
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
package com.github.dannecron.demo.core.exceptions
|
||||||
|
|
||||||
|
class ProductNotFoundException: ModelNotFoundException("product")
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package com.github.dannecron.demo.core.services.city
|
||||||
|
|
||||||
|
import com.github.dannecron.demo.core.dto.City
|
||||||
|
import com.github.dannecron.demo.core.dto.CityCreate
|
||||||
|
import com.github.dannecron.demo.core.exceptions.AlreadyDeletedException
|
||||||
|
import com.github.dannecron.demo.core.exceptions.CityNotFoundException
|
||||||
|
import org.springframework.stereotype.Service
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
@Service
|
||||||
|
interface CityService {
|
||||||
|
fun findByGuid(guid: UUID): City?
|
||||||
|
|
||||||
|
fun create(name: String): City
|
||||||
|
fun create(cityCreate: CityCreate): City
|
||||||
|
|
||||||
|
@Throws(CityNotFoundException::class, AlreadyDeletedException::class)
|
||||||
|
fun delete(guid: UUID): City
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
package com.github.dannecron.demo.core.services.city
|
||||||
|
|
||||||
|
import com.github.dannecron.demo.core.dto.City
|
||||||
|
import com.github.dannecron.demo.core.dto.CityCreate
|
||||||
|
import com.github.dannecron.demo.core.exceptions.AlreadyDeletedException
|
||||||
|
import com.github.dannecron.demo.core.exceptions.CityNotFoundException
|
||||||
|
import com.github.dannecron.demo.core.services.generation.CommonGenerator
|
||||||
|
import com.github.dannecron.demo.db.entity.CityEntity
|
||||||
|
import com.github.dannecron.demo.db.repository.CityRepository
|
||||||
|
import org.springframework.stereotype.Service
|
||||||
|
import java.time.OffsetDateTime
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
@Service
|
||||||
|
class CityServiceImpl(
|
||||||
|
private val cityRepository: CityRepository,
|
||||||
|
private val commonGenerator: CommonGenerator,
|
||||||
|
): CityService {
|
||||||
|
override fun findByGuid(guid: UUID): City? = cityRepository.findByGuid(guid)?.toCore()
|
||||||
|
|
||||||
|
override fun create(name: String): City = CityEntity(
|
||||||
|
id = null,
|
||||||
|
guid = commonGenerator.generateUUID(),
|
||||||
|
name = name,
|
||||||
|
createdAt = commonGenerator.generateCurrentTime(),
|
||||||
|
updatedAt = null,
|
||||||
|
deletedAt = null,
|
||||||
|
).let(cityRepository::save).toCore()
|
||||||
|
|
||||||
|
override fun create(cityCreate: CityCreate): City = CityEntity(
|
||||||
|
id = null,
|
||||||
|
guid = UUID.fromString(cityCreate.guid),
|
||||||
|
name = cityCreate.name,
|
||||||
|
createdAt = cityCreate.createdAt,
|
||||||
|
updatedAt = cityCreate.updatedAt,
|
||||||
|
deletedAt = cityCreate.deletedAt,
|
||||||
|
).let(cityRepository::save).toCore()
|
||||||
|
|
||||||
|
@Throws(CityNotFoundException::class, AlreadyDeletedException::class)
|
||||||
|
override fun delete(guid: UUID): City {
|
||||||
|
val city = findByGuid(guid) ?: throw CityNotFoundException()
|
||||||
|
|
||||||
|
if (city.isDeleted()) {
|
||||||
|
throw AlreadyDeletedException()
|
||||||
|
}
|
||||||
|
|
||||||
|
return cityRepository.save(
|
||||||
|
city.copy(
|
||||||
|
deletedAt = OffsetDateTime.now(),
|
||||||
|
).toEntity()
|
||||||
|
).toCore()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun CityEntity.toCore() = City(
|
||||||
|
id = id!!,
|
||||||
|
guid = guid,
|
||||||
|
name = name,
|
||||||
|
createdAt = createdAt,
|
||||||
|
updatedAt = updatedAt,
|
||||||
|
deletedAt = deletedAt,
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun City.toEntity() = CityEntity(
|
||||||
|
id = id,
|
||||||
|
guid = guid,
|
||||||
|
name = name,
|
||||||
|
createdAt = createdAt,
|
||||||
|
updatedAt = updatedAt,
|
||||||
|
deletedAt = deletedAt,
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package com.github.dannecron.demo.core.services.product
|
||||||
|
|
||||||
|
import com.github.dannecron.demo.core.dto.Product
|
||||||
|
import com.github.dannecron.demo.core.exceptions.AlreadyDeletedException
|
||||||
|
import com.github.dannecron.demo.core.exceptions.ProductNotFoundException
|
||||||
|
import org.springframework.data.domain.Page
|
||||||
|
import org.springframework.data.domain.Pageable
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
interface ProductService {
|
||||||
|
fun findByGuid(guid: UUID): Product?
|
||||||
|
|
||||||
|
fun findAll(pageable: Pageable): Page<Product>
|
||||||
|
|
||||||
|
fun create(name: String, price: Long, description: String?): Product
|
||||||
|
|
||||||
|
@Throws(ProductNotFoundException::class, AlreadyDeletedException::class)
|
||||||
|
fun delete(guid: UUID): Product
|
||||||
|
}
|
||||||
@@ -1,30 +1,26 @@
|
|||||||
package com.github.dannecron.demo.services.database.product
|
package com.github.dannecron.demo.core.services.product
|
||||||
|
|
||||||
|
import com.github.dannecron.demo.core.dto.Product
|
||||||
|
import com.github.dannecron.demo.core.exceptions.AlreadyDeletedException
|
||||||
|
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.Product
|
import com.github.dannecron.demo.core.utils.LoggerDelegate
|
||||||
|
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.services.database.exceptions.AlreadyDeletedException
|
|
||||||
import com.github.dannecron.demo.services.database.exceptions.ProductNotFoundException
|
|
||||||
import com.github.dannecron.demo.services.kafka.Producer
|
|
||||||
import com.github.dannecron.demo.services.kafka.dto.ProductDto
|
|
||||||
import com.github.dannecron.demo.services.kafka.exceptions.InvalidArgumentException
|
|
||||||
import com.github.dannecron.demo.utils.LoggerDelegate
|
|
||||||
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 producer: Producer,
|
|
||||||
private val commonGenerator: CommonGenerator,
|
private val commonGenerator: CommonGenerator,
|
||||||
): ProductService {
|
): ProductService {
|
||||||
private val logger by LoggerDelegate()
|
private val logger by LoggerDelegate()
|
||||||
|
|
||||||
override fun findByGuid(guid: UUID): Product? = productRepository.findByGuid(guid)
|
override fun findByGuid(guid: UUID): Product? = productRepository.findByGuid(guid)?.toCore()
|
||||||
.also {
|
.also {
|
||||||
logger.debug(
|
logger.debug(
|
||||||
Markers.appendEntries(mapOf("guid" to guid, "idResult" to it?.id)),
|
Markers.appendEntries(mapOf("guid" to guid, "idResult" to it?.id)),
|
||||||
@@ -33,9 +29,10 @@ class ProductServiceImpl(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun findAll(pageable: Pageable): Page<Product> = productRepository.findAll(pageable)
|
override fun findAll(pageable: Pageable): Page<Product> = productRepository.findAll(pageable)
|
||||||
|
.map { it.toCore() }
|
||||||
|
|
||||||
override fun create(name: String, price: Long, description: String?): Product {
|
override fun create(name: String, price: Long, description: String?): Product =
|
||||||
val product = Product(
|
ProductEntity(
|
||||||
id = null,
|
id = null,
|
||||||
guid = commonGenerator.generateUUID(),
|
guid = commonGenerator.generateUUID(),
|
||||||
name = name,
|
name = name,
|
||||||
@@ -44,11 +41,9 @@ class ProductServiceImpl(
|
|||||||
createdAt = commonGenerator.generateCurrentTime(),
|
createdAt = commonGenerator.generateCurrentTime(),
|
||||||
updatedAt = null,
|
updatedAt = null,
|
||||||
deletedAt = null,
|
deletedAt = null,
|
||||||
)
|
).let(productRepository::save).toCore()
|
||||||
|
|
||||||
return productRepository.save(product)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
@Throws(ProductNotFoundException::class, AlreadyDeletedException::class)
|
||||||
override fun delete(guid: UUID): Product {
|
override fun delete(guid: UUID): Product {
|
||||||
val product = findByGuid(guid) ?: throw ProductNotFoundException()
|
val product = findByGuid(guid) ?: throw ProductNotFoundException()
|
||||||
|
|
||||||
@@ -56,27 +51,32 @@ class ProductServiceImpl(
|
|||||||
throw AlreadyDeletedException()
|
throw AlreadyDeletedException()
|
||||||
}
|
}
|
||||||
|
|
||||||
val deletedProduct = product.copy(
|
return product.copy(
|
||||||
deletedAt = commonGenerator.generateCurrentTime(),
|
deletedAt = commonGenerator.generateCurrentTime(),
|
||||||
)
|
).toEntity()
|
||||||
|
.let(productRepository::save)
|
||||||
return productRepository.save(deletedProduct)
|
.toCore()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun syncToKafka(guid: UUID, topic: String?) {
|
private fun ProductEntity.toCore() = Product(
|
||||||
val product = findByGuid(guid) ?: throw ProductNotFoundException()
|
id = id!!,
|
||||||
|
guid = guid,
|
||||||
producer.produceProductSync(product.toKafkaDto())
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Product.toKafkaDto() = ProductDto(
|
|
||||||
id = id ?: throw InvalidArgumentException("product.id"),
|
|
||||||
guid = guid.toString(),
|
|
||||||
name = name,
|
name = name,
|
||||||
description = description,
|
description = description,
|
||||||
price = price,
|
price = price,
|
||||||
createdAt = createdAt.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME),
|
createdAt = createdAt,
|
||||||
updatedAt = updatedAt?.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME),
|
updatedAt = updatedAt,
|
||||||
deletedAt = deletedAt?.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME),
|
deletedAt = deletedAt,
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun Product.toEntity() = ProductEntity(
|
||||||
|
id = id,
|
||||||
|
guid = guid,
|
||||||
|
name = name,
|
||||||
|
description = description,
|
||||||
|
price = price,
|
||||||
|
createdAt = createdAt,
|
||||||
|
updatedAt = updatedAt,
|
||||||
|
deletedAt = deletedAt,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.github.dannecron.demo.utils
|
package com.github.dannecron.demo.core.utils
|
||||||
|
|
||||||
import org.slf4j.Logger
|
import org.slf4j.Logger
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
package com.github.dannecron.demo.services.database.city
|
package com.github.dannecron.demo.core.services.city
|
||||||
|
|
||||||
|
import com.github.dannecron.demo.core.dto.City
|
||||||
|
import com.github.dannecron.demo.core.dto.CityCreate
|
||||||
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.City
|
import com.github.dannecron.demo.db.entity.CityEntity
|
||||||
import com.github.dannecron.demo.db.repository.CityRepository
|
import com.github.dannecron.demo.db.repository.CityRepository
|
||||||
import com.github.dannecron.demo.services.kafka.dto.CityCreateDto
|
|
||||||
import org.mockito.kotlin.any
|
import org.mockito.kotlin.any
|
||||||
import org.mockito.kotlin.doReturn
|
import org.mockito.kotlin.doReturn
|
||||||
import org.mockito.kotlin.mock
|
import org.mockito.kotlin.mock
|
||||||
@@ -11,7 +12,6 @@ import org.mockito.kotlin.times
|
|||||||
import org.mockito.kotlin.verify
|
import org.mockito.kotlin.verify
|
||||||
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.Test
|
import kotlin.test.Test
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
@@ -27,6 +27,14 @@ class CityServiceImplTest {
|
|||||||
private val cityRepository: CityRepository = mock()
|
private val cityRepository: CityRepository = mock()
|
||||||
private val cityServiceImpl = CityServiceImpl(cityRepository, commonGenerator)
|
private val cityServiceImpl = CityServiceImpl(cityRepository, commonGenerator)
|
||||||
|
|
||||||
|
private val cityEntity = CityEntity(
|
||||||
|
id = 1000,
|
||||||
|
guid = mockGuid,
|
||||||
|
name = "name",
|
||||||
|
createdAt = mockCurrentTime,
|
||||||
|
updatedAt = null,
|
||||||
|
deletedAt = null,
|
||||||
|
)
|
||||||
private val city = City(
|
private val city = City(
|
||||||
id = 1000,
|
id = 1000,
|
||||||
guid = mockGuid,
|
guid = mockGuid,
|
||||||
@@ -38,33 +46,34 @@ class CityServiceImplTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `create - by name`() {
|
fun `create - by name`() {
|
||||||
whenever(cityRepository.save(any<City>())).thenReturn(city)
|
whenever(cityRepository.save(any<CityEntity>())).thenReturn(cityEntity)
|
||||||
|
|
||||||
val result = cityServiceImpl.create("name")
|
val result = cityServiceImpl.create("name")
|
||||||
assertEquals(city, result)
|
assertEquals(city, result)
|
||||||
|
|
||||||
verify(cityRepository, times(1)).save(city.copy(id = null))
|
verify(cityRepository, times(1)).save(cityEntity.copy(id = null))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `create - by dto`() {
|
fun `create - by dto`() {
|
||||||
val cityGuid = UUID.randomUUID()
|
val cityGuid = UUID.randomUUID()
|
||||||
val createdAt = OffsetDateTime.now()
|
val createdAt = OffsetDateTime.now()
|
||||||
val cityCreate = CityCreateDto(
|
val cityCreate = CityCreate(
|
||||||
guid = cityGuid.toString(),
|
guid = cityGuid.toString(),
|
||||||
name = "name",
|
name = "name",
|
||||||
createdAt = createdAt.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME),
|
createdAt = createdAt,
|
||||||
updatedAt = null,
|
updatedAt = null,
|
||||||
deletedAt = null,
|
deletedAt = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val expectedCityEntity = cityEntity.copy(guid = cityGuid, createdAt = createdAt)
|
||||||
val expectedCity = city.copy(guid = cityGuid, createdAt = createdAt)
|
val expectedCity = city.copy(guid = cityGuid, createdAt = createdAt)
|
||||||
|
|
||||||
whenever(cityRepository.save(any<City>())).thenReturn(expectedCity)
|
whenever(cityRepository.save(any<CityEntity>())).thenReturn(expectedCityEntity)
|
||||||
|
|
||||||
val result = cityServiceImpl.create(cityCreate)
|
val result = cityServiceImpl.create(cityCreate)
|
||||||
assertEquals(expectedCity, result)
|
assertEquals(expectedCity, result)
|
||||||
|
|
||||||
verify(cityRepository, times(1)).save(expectedCity.copy(id = null))
|
verify(cityRepository, times(1)).save(expectedCityEntity.copy(id = null))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,12 +1,11 @@
|
|||||||
package com.github.dannecron.demo.services.database.product
|
package com.github.dannecron.demo.core.services.product
|
||||||
|
|
||||||
|
import com.github.dannecron.demo.core.dto.Product
|
||||||
|
import com.github.dannecron.demo.core.exceptions.AlreadyDeletedException
|
||||||
|
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.Product
|
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.services.database.exceptions.AlreadyDeletedException
|
|
||||||
import com.github.dannecron.demo.services.database.exceptions.ProductNotFoundException
|
|
||||||
import com.github.dannecron.demo.services.kafka.Producer
|
|
||||||
import com.github.dannecron.demo.services.kafka.dto.ProductDto
|
|
||||||
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
|
||||||
@@ -15,10 +14,8 @@ 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
|
||||||
|
|
||||||
@@ -27,7 +24,6 @@ class ProductServiceImplTest {
|
|||||||
private val mockCurrentTime = OffsetDateTime.now()
|
private val mockCurrentTime = OffsetDateTime.now()
|
||||||
|
|
||||||
private val productRepository: ProductRepository = mock()
|
private val productRepository: ProductRepository = mock()
|
||||||
private val producer: Producer = 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
|
||||||
@@ -35,72 +31,79 @@ class ProductServiceImplTest {
|
|||||||
|
|
||||||
private val productService = ProductServiceImpl(
|
private val productService = ProductServiceImpl(
|
||||||
productRepository = productRepository,
|
productRepository = productRepository,
|
||||||
producer = producer,
|
|
||||||
commonGenerator = commonGenerator,
|
commonGenerator = commonGenerator,
|
||||||
)
|
)
|
||||||
|
|
||||||
private val guid = UUID.randomUUID()
|
private val guid = UUID.randomUUID()
|
||||||
|
private val productEntity = ProductEntity(
|
||||||
|
id = 123,
|
||||||
|
guid = guid,
|
||||||
|
name = "name",
|
||||||
|
description = "description",
|
||||||
|
price = 10050,
|
||||||
|
createdAt = mockCurrentTime.minusDays(1),
|
||||||
|
updatedAt = mockCurrentTime.minusHours(2),
|
||||||
|
deletedAt = null,
|
||||||
|
)
|
||||||
private val product = Product(
|
private val product = Product(
|
||||||
id = 123,
|
id = 123,
|
||||||
guid = guid,
|
guid = guid,
|
||||||
name = "name",
|
name = "name",
|
||||||
description = "description",
|
description = "description",
|
||||||
price = 10050,
|
price = 10050,
|
||||||
createdAt = OffsetDateTime.now().minusDays(1),
|
createdAt = mockCurrentTime.minusDays(1),
|
||||||
updatedAt = OffsetDateTime.now().minusHours(2),
|
updatedAt = mockCurrentTime.minusHours(2),
|
||||||
deletedAt = null,
|
|
||||||
)
|
|
||||||
private val kafkaProductDto = ProductDto(
|
|
||||||
id = 123,
|
|
||||||
guid = guid.toString(),
|
|
||||||
name = "name",
|
|
||||||
description = "description",
|
|
||||||
price = 10050,
|
|
||||||
createdAt = product.createdAt.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME),
|
|
||||||
updatedAt = product.updatedAt!!.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME),
|
|
||||||
deletedAt = null,
|
deletedAt = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun create() {
|
fun create() {
|
||||||
val expectedProductForCreation = product.copy(
|
val expectedProductForCreation = productEntity.copy(
|
||||||
id = null,
|
id = null,
|
||||||
guid = mockGuid,
|
guid = mockGuid,
|
||||||
createdAt = mockCurrentTime,
|
createdAt = mockCurrentTime,
|
||||||
updatedAt = null,
|
updatedAt = null,
|
||||||
)
|
)
|
||||||
val expectedCreatedProduct = expectedProductForCreation.copy(id = 1)
|
val expectedCreatedProductEntity = expectedProductForCreation.copy(id = 1)
|
||||||
|
val expectedProduct = product.copy(
|
||||||
|
id = 1,
|
||||||
|
guid = mockGuid,
|
||||||
|
createdAt = mockCurrentTime,
|
||||||
|
updatedAt = null,
|
||||||
|
)
|
||||||
|
|
||||||
whenever(productRepository.save<Product>(any())).thenReturn(expectedCreatedProduct)
|
whenever(productRepository.save<ProductEntity>(any())).thenReturn(expectedCreatedProductEntity)
|
||||||
|
|
||||||
val result = productService.create(
|
val result = productService.create(
|
||||||
name = "name",
|
name = "name",
|
||||||
price = 10050,
|
price = 10050,
|
||||||
description = "description",
|
description = "description",
|
||||||
)
|
)
|
||||||
assertEquals(expectedCreatedProduct, result)
|
assertEquals(expectedProduct, result)
|
||||||
|
|
||||||
verify(productRepository, times(1)).save(expectedProductForCreation)
|
verify(productRepository, times(1)).save(expectedProductForCreation)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `delete - success`() {
|
fun `delete - success`() {
|
||||||
val deletedProduct = product.copy(
|
val deletedProductEntity = productEntity.copy(
|
||||||
deletedAt = mockCurrentTime,
|
deletedAt = mockCurrentTime,
|
||||||
)
|
)
|
||||||
whenever(productRepository.findByGuid(any())).thenReturn(product)
|
val expectedProduct = product.copy(deletedAt = mockCurrentTime)
|
||||||
whenever(productRepository.save<Product>(any())).thenReturn(deletedProduct)
|
|
||||||
|
whenever(productRepository.findByGuid(any())).thenReturn(productEntity)
|
||||||
|
whenever(productRepository.save<ProductEntity>(any())).thenReturn(deletedProductEntity)
|
||||||
|
|
||||||
val result = productService.delete(guid)
|
val result = productService.delete(guid)
|
||||||
assertEquals(deletedProduct, result)
|
assertEquals(expectedProduct, result)
|
||||||
|
|
||||||
verify(productRepository, times(1)).findByGuid(guid)
|
verify(productRepository, times(1)).findByGuid(guid)
|
||||||
verify(productRepository, times(1)).save(deletedProduct)
|
verify(productRepository, times(1)).save(deletedProductEntity)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `delete - fail - already deleted`() {
|
fun `delete - fail - already deleted`() {
|
||||||
val deletedProduct = product.copy(
|
val deletedProduct = productEntity.copy(
|
||||||
deletedAt = mockCurrentTime,
|
deletedAt = mockCurrentTime,
|
||||||
)
|
)
|
||||||
whenever(productRepository.findByGuid(any())).thenReturn(deletedProduct)
|
whenever(productRepository.findByGuid(any())).thenReturn(deletedProduct)
|
||||||
@@ -124,26 +127,4 @@ 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 `syncToKafka - success`() {
|
|
||||||
whenever(productRepository.findByGuid(any())) doReturn product
|
|
||||||
|
|
||||||
productService.syncToKafka(guid, null)
|
|
||||||
|
|
||||||
verify(productRepository, times(1)).findByGuid(guid)
|
|
||||||
verify(producer, times(1)).produceProductSync(kafkaProductDto)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `syncToKafka - not found`() {
|
|
||||||
whenever(productRepository.findByGuid(any())) doReturn null
|
|
||||||
|
|
||||||
assertThrows<ProductNotFoundException> {
|
|
||||||
productService.syncToKafka(guid, null)
|
|
||||||
}
|
|
||||||
|
|
||||||
verify(productRepository, times(1)).findByGuid(guid)
|
|
||||||
verifyNoInteractions(producer)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
package com.github.dannecron.demo.services.database.city
|
|
||||||
|
|
||||||
import com.github.dannecron.demo.db.entity.City
|
|
||||||
import com.github.dannecron.demo.services.database.exceptions.CityNotFoundException
|
|
||||||
import com.github.dannecron.demo.services.database.exceptions.AlreadyDeletedException
|
|
||||||
import com.github.dannecron.demo.services.kafka.dto.CityCreateDto
|
|
||||||
import org.springframework.stereotype.Service
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
@Service
|
|
||||||
interface CityService {
|
|
||||||
fun findByGuid(guid: UUID): City?
|
|
||||||
|
|
||||||
fun create(name: String): City
|
|
||||||
fun create(kafkaCityDto: CityCreateDto): City
|
|
||||||
|
|
||||||
@Throws(CityNotFoundException::class, AlreadyDeletedException::class)
|
|
||||||
fun delete(guid: UUID): City
|
|
||||||
}
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
package com.github.dannecron.demo.services.database.city
|
|
||||||
|
|
||||||
import com.github.dannecron.demo.core.services.generation.CommonGenerator
|
|
||||||
import com.github.dannecron.demo.db.entity.City
|
|
||||||
import com.github.dannecron.demo.db.repository.CityRepository
|
|
||||||
import com.github.dannecron.demo.services.database.exceptions.AlreadyDeletedException
|
|
||||||
import com.github.dannecron.demo.services.database.exceptions.CityNotFoundException
|
|
||||||
import com.github.dannecron.demo.services.kafka.dto.CityCreateDto
|
|
||||||
import org.springframework.stereotype.Service
|
|
||||||
import java.time.OffsetDateTime
|
|
||||||
import java.time.format.DateTimeFormatter
|
|
||||||
import java.util.UUID
|
|
||||||
|
|
||||||
@Service
|
|
||||||
class CityServiceImpl(
|
|
||||||
private val cityRepository: CityRepository,
|
|
||||||
private val commonGenerator: CommonGenerator,
|
|
||||||
): CityService {
|
|
||||||
override fun findByGuid(guid: UUID): City? = cityRepository.findByGuid(guid)
|
|
||||||
|
|
||||||
override fun create(name: String): City = City(
|
|
||||||
id = null,
|
|
||||||
guid = commonGenerator.generateUUID(),
|
|
||||||
name = name,
|
|
||||||
createdAt = commonGenerator.generateCurrentTime(),
|
|
||||||
updatedAt = null,
|
|
||||||
deletedAt = null,
|
|
||||||
).let {
|
|
||||||
cityRepository.save(it)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun create(kafkaCityDto: CityCreateDto): City = City(
|
|
||||||
id = null,
|
|
||||||
guid = UUID.fromString(kafkaCityDto.guid),
|
|
||||||
name = kafkaCityDto.name,
|
|
||||||
createdAt = OffsetDateTime.parse(kafkaCityDto.createdAt, DateTimeFormatter.ISO_OFFSET_DATE_TIME),
|
|
||||||
updatedAt = kafkaCityDto.deletedAt?.let {
|
|
||||||
OffsetDateTime.parse(it, DateTimeFormatter.ISO_OFFSET_DATE_TIME)
|
|
||||||
},
|
|
||||||
deletedAt = kafkaCityDto.deletedAt?.let {
|
|
||||||
OffsetDateTime.parse(it, DateTimeFormatter.ISO_OFFSET_DATE_TIME)
|
|
||||||
},
|
|
||||||
).let {
|
|
||||||
cityRepository.save(it)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun delete(guid: UUID): City {
|
|
||||||
val city = findByGuid(guid) ?: throw CityNotFoundException()
|
|
||||||
|
|
||||||
if (city.isDeleted()) {
|
|
||||||
throw AlreadyDeletedException()
|
|
||||||
}
|
|
||||||
|
|
||||||
return cityRepository.save(
|
|
||||||
city.copy(
|
|
||||||
deletedAt = OffsetDateTime.now(),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
package com.github.dannecron.demo.services.database.product
|
|
||||||
|
|
||||||
import com.github.dannecron.demo.db.entity.Product
|
|
||||||
import com.github.dannecron.demo.services.database.exceptions.AlreadyDeletedException
|
|
||||||
import com.github.dannecron.demo.services.database.exceptions.ProductNotFoundException
|
|
||||||
import com.github.dannecron.demo.services.kafka.exceptions.InvalidArgumentException
|
|
||||||
import org.springframework.data.domain.Page
|
|
||||||
import org.springframework.data.domain.Pageable
|
|
||||||
import org.springframework.stereotype.Service
|
|
||||||
import java.util.UUID
|
|
||||||
|
|
||||||
@Service
|
|
||||||
interface ProductService {
|
|
||||||
fun findByGuid(guid: UUID): Product?
|
|
||||||
|
|
||||||
fun findAll(pageable: Pageable): Page<Product>
|
|
||||||
|
|
||||||
fun create(name: String, price: Long, description: String?): Product
|
|
||||||
|
|
||||||
@Throws(ProductNotFoundException::class, AlreadyDeletedException::class)
|
|
||||||
fun delete(guid: UUID): Product
|
|
||||||
|
|
||||||
@Throws(ProductNotFoundException::class, InvalidArgumentException::class)
|
|
||||||
fun syncToKafka(guid: UUID, topic: String?)
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user