mirror of
https://github.com/Dannecron/spring-boot-demo.git
synced 2025-12-25 16:22:35 +03:00
move validation to separate core package, add generation service
some code cleanup
This commit is contained in:
@@ -69,13 +69,13 @@ subprojects {
|
||||
|
||||
dependencies {
|
||||
implementation(project(":db"))
|
||||
implementation(project(":core"))
|
||||
|
||||
runtimeOnly(libs.micrometer.registry.prometheus)
|
||||
|
||||
implementation(libs.bundles.tracing)
|
||||
implementation(libs.jackson.datatype.jsr)
|
||||
implementation(libs.jackson.module.kotlin)
|
||||
implementation(libs.json.schema.validator)
|
||||
implementation(libs.ktor.client.cio)
|
||||
implementation(libs.ktor.client.core)
|
||||
implementation(libs.logback.encoder)
|
||||
@@ -85,13 +85,14 @@ dependencies {
|
||||
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.doc.openapi.starter)
|
||||
implementation(libs.spring.kafka)
|
||||
|
||||
testImplementation(libs.spring.kafka.test)
|
||||
testImplementation(libs.ktor.client.mock)
|
||||
testImplementation(libs.spring.boot.starter.actuatorAutoconfigure)
|
||||
testImplementation(libs.spring.cloud.starter.streamTestBinder)
|
||||
testImplementation(libs.testcontainers)
|
||||
testImplementation(libs.testcontainers.junit.jupiter)
|
||||
testImplementation(libs.ktor.client.mock)
|
||||
|
||||
developmentOnly(libs.spring.boot.devtools)
|
||||
}
|
||||
|
||||
7
core/build.gradle.kts
Normal file
7
core/build.gradle.kts
Normal file
@@ -0,0 +1,7 @@
|
||||
group = "com.github.dannecron.demo"
|
||||
version = "single-version"
|
||||
|
||||
dependencies {
|
||||
implementation(rootProject.libs.spring.boot.starter.validation)
|
||||
implementation(rootProject.libs.json.schema.validator)
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.github.dannecron.demo.core.config
|
||||
|
||||
import com.github.dannecron.demo.core.config.properties.ValidationProperties
|
||||
import com.github.dannecron.demo.core.services.validation.SchemaValidator
|
||||
import com.github.dannecron.demo.core.services.validation.SchemaValidatorImp
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.util.ResourceUtils
|
||||
|
||||
@Configuration
|
||||
class SchemaValidationConfig(
|
||||
private val validationProperties: ValidationProperties,
|
||||
) {
|
||||
|
||||
@Bean
|
||||
fun schemaValidator(): SchemaValidator = SchemaValidatorImp(
|
||||
schemaMap = validationProperties.schema.mapValues {
|
||||
schema -> ResourceUtils.getFile("classpath:json-schemas/${schema.value}")
|
||||
.readText(Charsets.UTF_8)
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.github.dannecron.demo.config.properties
|
||||
package com.github.dannecron.demo.core.config.properties
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.github.dannecron.demo.core.services.generation
|
||||
|
||||
import java.time.OffsetDateTime
|
||||
import java.util.UUID
|
||||
|
||||
interface CommonGenerator {
|
||||
|
||||
fun generateUUID(): UUID
|
||||
|
||||
fun generateCurrentTime(): OffsetDateTime
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.github.dannecron.demo.core.services.generation
|
||||
|
||||
import org.springframework.stereotype.Component
|
||||
import java.time.OffsetDateTime
|
||||
import java.util.UUID
|
||||
|
||||
@Component
|
||||
class CommonGeneratorImpl : CommonGenerator {
|
||||
override fun generateUUID(): UUID {
|
||||
return UUID.randomUUID()
|
||||
}
|
||||
|
||||
override fun generateCurrentTime(): OffsetDateTime {
|
||||
return OffsetDateTime.now()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.github.dannecron.demo.core.services.validation
|
||||
|
||||
import com.github.dannecron.demo.core.services.validation.exceptions.ElementNotValidException
|
||||
import com.github.dannecron.demo.core.services.validation.exceptions.SchemaNotFoundException
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
|
||||
interface SchemaValidator {
|
||||
|
||||
@Throws(ElementNotValidException::class, SchemaNotFoundException::class)
|
||||
fun validate(schemaName: String, value: JsonElement)
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.github.dannecron.demo.core.services.validation
|
||||
|
||||
import com.github.dannecron.demo.core.services.validation.exceptions.ElementNotValidException
|
||||
import com.github.dannecron.demo.core.services.validation.exceptions.SchemaNotFoundException
|
||||
import io.github.optimumcode.json.schema.JsonSchema
|
||||
import io.github.optimumcode.json.schema.ValidationError
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
|
||||
class SchemaValidatorImp(
|
||||
private val schemaMap: Map<String, String>,
|
||||
): SchemaValidator {
|
||||
|
||||
@Throws(
|
||||
SchemaNotFoundException::class,
|
||||
ElementNotValidException::class,
|
||||
)
|
||||
override fun validate(schemaName: String, value: JsonElement) {
|
||||
JsonSchema.fromDefinition(
|
||||
getSchema(schemaName),
|
||||
).also {
|
||||
val errors = mutableListOf<ValidationError>()
|
||||
|
||||
if (!it.validate(value, errors::add)) {
|
||||
throw ElementNotValidException(errors)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(SchemaNotFoundException::class)
|
||||
private fun getSchema(schemaName: String) = schemaMap[schemaName]
|
||||
?: throw SchemaNotFoundException()
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.github.dannecron.demo.services.validation.exceptions
|
||||
package com.github.dannecron.demo.core.services.validation.exceptions
|
||||
|
||||
import io.github.optimumcode.json.schema.ValidationError
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
package com.github.dannecron.demo.core.services.validation.exceptions
|
||||
|
||||
class SchemaNotFoundException: RuntimeException()
|
||||
@@ -1,25 +1,20 @@
|
||||
package com.github.dannecron.demo.services.validation
|
||||
package com.github.dannecron.demo.core.services.validation
|
||||
|
||||
import com.github.dannecron.demo.BaseUnitTest
|
||||
import com.github.dannecron.demo.services.validation.SchemaValidator.Companion.SCHEMA_KAFKA_PRODUCT_SYNC
|
||||
import com.github.dannecron.demo.services.validation.exceptions.ElementNotValidException
|
||||
import com.github.dannecron.demo.services.validation.exceptions.SchemaNotFoundException
|
||||
import com.github.dannecron.demo.core.services.validation.exceptions.ElementNotValidException
|
||||
import com.github.dannecron.demo.core.services.validation.exceptions.SchemaNotFoundException
|
||||
import kotlinx.serialization.json.Json
|
||||
import org.junit.jupiter.params.ParameterizedTest
|
||||
import org.junit.jupiter.params.provider.Arguments
|
||||
import org.junit.jupiter.params.provider.MethodSource
|
||||
import org.junit.runner.RunWith
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.boot.test.context.SpringBootTest
|
||||
import org.springframework.test.context.junit4.SpringRunner
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.test.assertFailsWith
|
||||
|
||||
@RunWith(SpringRunner::class)
|
||||
@SpringBootTest
|
||||
class SchemaValidatorImpTest(
|
||||
@Autowired val schemaValidatorImp: SchemaValidatorImp
|
||||
): BaseUnitTest() {
|
||||
class SchemaValidatorImpTest {
|
||||
private val schemaValidatorImp = SchemaValidatorImp(
|
||||
schemaMap = mapOf(
|
||||
KAFKA_PRODUCT_SYNC_SCHEMA to getJsonSchema("json-schemas/kafka/product/sync.json")),
|
||||
)
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("validateDataProvider")
|
||||
fun validate(schemaName: String, inputRawJson: String, expectedException: KClass<out Throwable>?) {
|
||||
@@ -27,8 +22,6 @@ class SchemaValidatorImpTest(
|
||||
|
||||
if (expectedException == null) {
|
||||
schemaValidatorImp.validate(schemaName = schemaName, value = element)
|
||||
// second time should use cache
|
||||
schemaValidatorImp.validate(schemaName = schemaName, value = element)
|
||||
|
||||
return
|
||||
}
|
||||
@@ -39,10 +32,12 @@ class SchemaValidatorImpTest(
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val KAFKA_PRODUCT_SYNC_SCHEMA = "kafkaProductSync"
|
||||
|
||||
@JvmStatic
|
||||
fun validateDataProvider() = listOf(
|
||||
Arguments.of(
|
||||
SCHEMA_KAFKA_PRODUCT_SYNC,
|
||||
KAFKA_PRODUCT_SYNC_SCHEMA,
|
||||
"""
|
||||
{
|
||||
"id": 123,
|
||||
@@ -58,7 +53,7 @@ class SchemaValidatorImpTest(
|
||||
null,
|
||||
),
|
||||
Arguments.of( // no id
|
||||
SCHEMA_KAFKA_PRODUCT_SYNC,
|
||||
KAFKA_PRODUCT_SYNC_SCHEMA,
|
||||
"""
|
||||
{
|
||||
"guid": "3a27e322-b5b6-427f-b761-a02284c1cfa4",
|
||||
@@ -73,7 +68,7 @@ class SchemaValidatorImpTest(
|
||||
ElementNotValidException::class,
|
||||
),
|
||||
Arguments.of( // wrong guid
|
||||
SCHEMA_KAFKA_PRODUCT_SYNC,
|
||||
KAFKA_PRODUCT_SYNC_SCHEMA,
|
||||
"""
|
||||
{
|
||||
"id": 213,
|
||||
@@ -95,4 +90,9 @@ class SchemaValidatorImpTest(
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun getJsonSchema(resourcePath: String) = javaClass.classLoader
|
||||
.getResourceAsStream(resourcePath)!!
|
||||
.readAllBytes()
|
||||
.toString(Charsets.UTF_8)
|
||||
}
|
||||
@@ -3,7 +3,7 @@ jackson = "2.15.4"
|
||||
kotlin = "2.1.10"
|
||||
ktor = "3.0.0"
|
||||
spring-boot = "3.2.10"
|
||||
spring-kafka = "3.1.3"
|
||||
spring-cloud = "3.2.10"
|
||||
testcontainers = "1.19.7"
|
||||
|
||||
[libraries]
|
||||
@@ -26,14 +26,15 @@ postgres = { module = "org.postgresql:postgresql", version = "42.6.2" }
|
||||
spring-aspects = { module = "org.springframework:spring-aspects" }
|
||||
spring-boot-devtools = { module = "org.springframework.boot:spring-boot-devtools" }
|
||||
spring-boot-starter-actuator = { module = "org.springframework.boot:spring-boot-starter-actuator", version.ref = "spring-boot" }
|
||||
spring-boot-starter-actuatorAutoconfigure = { module = "org.springframework.boot:spring-boot-actuator-autoconfigure" }
|
||||
spring-boot-starter-jdbc = { module = "org.springframework.boot:spring-boot-starter-data-jdbc", version.ref = "spring-boot"}
|
||||
spring-boot-starter-mustache = { module = "org.springframework.boot:spring-boot-starter-mustache", version.ref = "spring-boot" }
|
||||
spring-boot-starter-test = { module = "org.springframework.boot:spring-boot-starter-test", version.ref = "spring-boot" }
|
||||
spring-boot-starter-validation = { module = "org.springframework.boot:spring-boot-starter-validation", version.ref = "spring-boot" }
|
||||
spring-boot-starter-web = { module = "org.springframework.boot:spring-boot-starter-web", version.ref = "spring-boot" }
|
||||
spring-cloud-starter-streamKafka = { module = "org.springframework.cloud:spring-cloud-starter-stream-kafka", version.ref = "spring-cloud"}
|
||||
spring-cloud-starter-streamTestBinder = { module = "org.springframework.cloud:spring-cloud-stream-test-binder", version = "4.0.4"}
|
||||
spring-doc-openapi-starter = "org.springdoc:springdoc-openapi-starter-webmvc-ui:2.6.0"
|
||||
spring-kafka = { module = "org.springframework.kafka:spring-kafka", version.ref = "spring-kafka"}
|
||||
spring-kafka-test = { module = "org.springframework.kafka:spring-kafka-test", version.ref = "spring-kafka"}
|
||||
testcontainers = { module = "org.testcontainers:testcontainers", version.ref = "testcontainers"}
|
||||
testcontainers-junit-jupiter = { module = "org.testcontainers:junit-jupiter", version.ref = "testcontainers"}
|
||||
testcontainers-postgresql = { module = "org.testcontainers:postgresql", version.ref = "testcontainers"}
|
||||
|
||||
@@ -1,2 +1,6 @@
|
||||
plugins {
|
||||
id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0"
|
||||
}
|
||||
rootProject.name = "demo"
|
||||
include("db")
|
||||
include("core")
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
package com.github.dannecron.demo.models
|
||||
|
||||
import com.github.dannecron.demo.models.serializables.OffsetDateTimeSerialization
|
||||
import com.github.dannecron.demo.models.serializables.UuidSerialization
|
||||
import kotlinx.serialization.Serializable
|
||||
import org.springframework.data.annotation.Id
|
||||
import org.springframework.data.relational.core.mapping.Column
|
||||
import org.springframework.data.relational.core.mapping.Table
|
||||
import java.time.OffsetDateTime
|
||||
import java.util.*
|
||||
|
||||
@Table("city")
|
||||
@Serializable
|
||||
data class City(
|
||||
@Id
|
||||
val id: Long?,
|
||||
@Serializable(with = UuidSerialization::class)
|
||||
val guid: UUID,
|
||||
val name: String,
|
||||
@Serializable(with = OffsetDateTimeSerialization::class)
|
||||
@Column(value = "created_at")
|
||||
val createdAt: OffsetDateTime,
|
||||
@Serializable(with = OffsetDateTimeSerialization::class)
|
||||
@Column(value = "updated_at")
|
||||
val updatedAt: OffsetDateTime?,
|
||||
@Serializable(with = OffsetDateTimeSerialization::class)
|
||||
@Column(value = "deleted_at")
|
||||
val deletedAt: OffsetDateTime?,
|
||||
) {
|
||||
fun isDeleted(): Boolean = deletedAt != null
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package com.github.dannecron.demo.models
|
||||
|
||||
import com.github.dannecron.demo.models.serializables.OffsetDateTimeSerialization
|
||||
import com.github.dannecron.demo.models.serializables.UuidSerialization
|
||||
import kotlinx.serialization.Serializable
|
||||
import org.springframework.data.annotation.Id
|
||||
import org.springframework.data.relational.core.mapping.Column
|
||||
import org.springframework.data.relational.core.mapping.Table
|
||||
import java.time.OffsetDateTime
|
||||
import java.util.*
|
||||
|
||||
@Table("customer")
|
||||
@Serializable
|
||||
data class Customer(
|
||||
@Id
|
||||
val id: Long?,
|
||||
@Serializable(with = UuidSerialization::class)
|
||||
val guid: UUID,
|
||||
val name: String,
|
||||
val cityId: Long?,
|
||||
@Serializable(with = OffsetDateTimeSerialization::class)
|
||||
@Column(value = "created_at")
|
||||
val createdAt: OffsetDateTime,
|
||||
@Serializable(with = OffsetDateTimeSerialization::class)
|
||||
@Column(value = "updated_at")
|
||||
val updatedAt: OffsetDateTime?,
|
||||
)
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.github.dannecron.demo.models
|
||||
|
||||
import com.github.dannecron.demo.db.entity.City
|
||||
import com.github.dannecron.demo.db.entity.Customer
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.github.dannecron.demo.models.order
|
||||
package com.github.dannecron.demo.models
|
||||
|
||||
import com.github.dannecron.demo.models.Product
|
||||
import com.github.dannecron.demo.db.entity.Product
|
||||
import com.github.dannecron.demo.db.entity.order.Order
|
||||
|
||||
data class OrderWithProducts(
|
||||
val order: Order,
|
||||
@@ -1,37 +0,0 @@
|
||||
package com.github.dannecron.demo.models
|
||||
|
||||
|
||||
import com.github.dannecron.demo.models.serializables.OffsetDateTimeSerialization
|
||||
import com.github.dannecron.demo.models.serializables.UuidSerialization
|
||||
import com.github.dannecron.demo.utils.roundTo
|
||||
import kotlinx.serialization.Serializable
|
||||
import org.springframework.data.annotation.Id
|
||||
import org.springframework.data.relational.core.mapping.Column
|
||||
import org.springframework.data.relational.core.mapping.Table
|
||||
import java.time.OffsetDateTime
|
||||
import java.util.*
|
||||
|
||||
@Table(value = "product")
|
||||
@Serializable
|
||||
data class Product(
|
||||
@Id
|
||||
val id: Long?,
|
||||
@Serializable(with = UuidSerialization::class)
|
||||
val guid: UUID,
|
||||
val name: String,
|
||||
val description: String?,
|
||||
val price: Long,
|
||||
@Serializable(with = OffsetDateTimeSerialization::class)
|
||||
@Column(value = "created_at")
|
||||
val createdAt: OffsetDateTime,
|
||||
@Serializable(with = OffsetDateTimeSerialization::class)
|
||||
@Column(value = "updated_at")
|
||||
val updatedAt: OffsetDateTime?,
|
||||
@Serializable(with = OffsetDateTimeSerialization::class)
|
||||
@Column(value = "deleted_at")
|
||||
val deletedAt: OffsetDateTime?,
|
||||
) {
|
||||
fun getPriceDouble(): Double = (price.toDouble() / 100).roundTo(2)
|
||||
|
||||
fun isDeleted(): Boolean = deletedAt != null
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
package com.github.dannecron.demo.models.order
|
||||
|
||||
import com.github.dannecron.demo.models.serializables.OffsetDateTimeSerialization
|
||||
import com.github.dannecron.demo.models.serializables.UuidSerialization
|
||||
import kotlinx.serialization.Serializable
|
||||
import org.springframework.data.annotation.Id
|
||||
import org.springframework.data.relational.core.mapping.Column
|
||||
import org.springframework.data.relational.core.mapping.Table
|
||||
import java.time.OffsetDateTime
|
||||
import java.util.*
|
||||
|
||||
@Table(value = "order")
|
||||
@Serializable
|
||||
data class Order(
|
||||
@Id
|
||||
val id: Long?,
|
||||
@Serializable(with = UuidSerialization::class)
|
||||
val guid: UUID,
|
||||
val customerId: Long,
|
||||
@Serializable(with = OffsetDateTimeSerialization::class)
|
||||
@Column(value = "delivered_at")
|
||||
val deliveredAt: OffsetDateTime?,
|
||||
@Serializable(with = OffsetDateTimeSerialization::class)
|
||||
@Column(value = "created_at")
|
||||
val createdAt: OffsetDateTime,
|
||||
@Serializable(with = OffsetDateTimeSerialization::class)
|
||||
@Column(value = "updated_at")
|
||||
val updatedAt: OffsetDateTime?
|
||||
) {
|
||||
fun isDelivered(): Boolean = deliveredAt != null
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
package com.github.dannecron.demo.models.order
|
||||
|
||||
import com.github.dannecron.demo.models.serializables.OffsetDateTimeSerialization
|
||||
import com.github.dannecron.demo.models.serializables.UuidSerialization
|
||||
import kotlinx.serialization.Serializable
|
||||
import org.springframework.data.annotation.Id
|
||||
import org.springframework.data.annotation.Transient
|
||||
import org.springframework.data.domain.Persistable
|
||||
import org.springframework.data.relational.core.mapping.Column
|
||||
import org.springframework.data.relational.core.mapping.Table
|
||||
import java.time.OffsetDateTime
|
||||
import java.util.*
|
||||
|
||||
@Table(value = "order_product")
|
||||
@Serializable
|
||||
data class OrderProduct(
|
||||
@Id
|
||||
@Serializable(with = UuidSerialization::class)
|
||||
val guid: UUID,
|
||||
@Column(value = "order_id")
|
||||
val orderId: Long,
|
||||
@Column(value = "product_id")
|
||||
val productId: Long,
|
||||
@Serializable(with = OffsetDateTimeSerialization::class)
|
||||
@Column(value = "created_at")
|
||||
val createdAt: OffsetDateTime,
|
||||
@Serializable(with = OffsetDateTimeSerialization::class)
|
||||
@Column(value = "updated_at")
|
||||
val updatedAt: OffsetDateTime?,
|
||||
): Persistable<UUID> {
|
||||
@Transient
|
||||
var isNewInstance: Boolean? = null
|
||||
|
||||
override fun getId(): UUID {
|
||||
return guid
|
||||
}
|
||||
|
||||
override fun isNew(): Boolean {
|
||||
return isNewInstance ?: true
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package com.github.dannecron.demo.models.serializables
|
||||
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.descriptors.PrimitiveKind
|
||||
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
|
||||
import kotlinx.serialization.encoding.Decoder
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
import java.time.OffsetDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
object OffsetDateTimeSerialization: KSerializer<OffsetDateTime> {
|
||||
override val descriptor = PrimitiveSerialDescriptor("Time", PrimitiveKind.STRING)
|
||||
|
||||
override fun deserialize(decoder: Decoder): OffsetDateTime = OffsetDateTime.parse(decoder.decodeString())
|
||||
|
||||
override fun serialize(encoder: Encoder, value: OffsetDateTime) {
|
||||
encoder.encodeString(value.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME))
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
package com.github.dannecron.demo.models.serializables
|
||||
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.descriptors.PrimitiveKind
|
||||
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
|
||||
import kotlinx.serialization.encoding.Decoder
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
import java.util.*
|
||||
|
||||
object UuidSerialization: KSerializer<UUID> {
|
||||
override val descriptor = PrimitiveSerialDescriptor("UUID", PrimitiveKind.STRING)
|
||||
|
||||
override fun deserialize(decoder: Decoder): UUID = UUID.fromString(decoder.decodeString())
|
||||
|
||||
override fun serialize(encoder: Encoder, value: UUID) {
|
||||
encoder.encodeString(value.toString())
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.github.dannecron.demo.services.database.city
|
||||
|
||||
import com.github.dannecron.demo.models.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
|
||||
|
||||
@@ -1,24 +1,28 @@
|
||||
package com.github.dannecron.demo.services.database.city
|
||||
|
||||
import com.github.dannecron.demo.models.City
|
||||
import com.github.dannecron.demo.providers.CityRepository
|
||||
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.*
|
||||
import java.util.UUID
|
||||
|
||||
@Service
|
||||
class CityServiceImpl(
|
||||
private val cityRepository: CityRepository
|
||||
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 = UUID.randomUUID(),
|
||||
guid = commonGenerator.generateUUID(),
|
||||
name = name,
|
||||
createdAt = OffsetDateTime.now(),
|
||||
createdAt = commonGenerator.generateCurrentTime(),
|
||||
updatedAt = null,
|
||||
deletedAt = null,
|
||||
).let {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.github.dannecron.demo.services.database.customer
|
||||
|
||||
import com.github.dannecron.demo.models.Customer
|
||||
import com.github.dannecron.demo.db.entity.Customer
|
||||
import com.github.dannecron.demo.models.CustomerExtended
|
||||
import com.github.dannecron.demo.services.database.exceptions.CityNotFoundException
|
||||
import java.util.*
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
package com.github.dannecron.demo.services.database.customer
|
||||
|
||||
import com.github.dannecron.demo.models.Customer
|
||||
import com.github.dannecron.demo.core.services.generation.CommonGenerator
|
||||
import com.github.dannecron.demo.db.entity.Customer
|
||||
import com.github.dannecron.demo.db.repository.CityRepository
|
||||
import com.github.dannecron.demo.db.repository.CustomerRepository
|
||||
import com.github.dannecron.demo.models.CustomerExtended
|
||||
import com.github.dannecron.demo.providers.CityRepository
|
||||
import com.github.dannecron.demo.providers.CustomerRepository
|
||||
import com.github.dannecron.demo.services.database.exceptions.CityNotFoundException
|
||||
import java.time.OffsetDateTime
|
||||
import java.util.*
|
||||
import org.springframework.stereotype.Service
|
||||
import java.util.UUID
|
||||
|
||||
@Service
|
||||
class CustomerServiceImpl(
|
||||
private val customerRepository: CustomerRepository,
|
||||
private val cityRepository: CityRepository
|
||||
private val cityRepository: CityRepository,
|
||||
private val commonGenerator: CommonGenerator,
|
||||
): CustomerService {
|
||||
override fun findByGuid(guid: UUID): CustomerExtended? = customerRepository.findByGuid(guid)
|
||||
?.let {
|
||||
@@ -22,12 +25,12 @@ class CustomerServiceImpl(
|
||||
|
||||
override fun create(name: String, cityGuid: UUID?): Customer = Customer(
|
||||
id = null,
|
||||
guid = UUID.randomUUID(),
|
||||
guid = commonGenerator.generateUUID(),
|
||||
name = name,
|
||||
cityId = cityGuid?.let {
|
||||
cityRepository.findByGuid(it)?.id ?: throw CityNotFoundException()
|
||||
},
|
||||
createdAt = OffsetDateTime.now(),
|
||||
createdAt = commonGenerator.generateCurrentTime(),
|
||||
updatedAt = null,
|
||||
).let {
|
||||
customerRepository.save(it)
|
||||
|
||||
@@ -1,55 +1,65 @@
|
||||
package com.github.dannecron.demo.services.database.order
|
||||
|
||||
import com.github.dannecron.demo.models.Customer
|
||||
import com.github.dannecron.demo.models.Product
|
||||
import com.github.dannecron.demo.models.order.Order
|
||||
import com.github.dannecron.demo.models.order.OrderProduct
|
||||
import com.github.dannecron.demo.models.order.OrderWithProducts
|
||||
import com.github.dannecron.demo.providers.OrderProductRepository
|
||||
import com.github.dannecron.demo.providers.OrderRepository
|
||||
import com.github.dannecron.demo.providers.ProductRepository
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import com.github.dannecron.demo.core.services.generation.CommonGenerator
|
||||
import com.github.dannecron.demo.db.entity.Customer
|
||||
import com.github.dannecron.demo.db.entity.Product
|
||||
import com.github.dannecron.demo.db.entity.order.Order
|
||||
import com.github.dannecron.demo.db.entity.order.OrderProduct
|
||||
import com.github.dannecron.demo.db.repository.OrderProductRepository
|
||||
import com.github.dannecron.demo.db.repository.OrderRepository
|
||||
import com.github.dannecron.demo.db.repository.ProductRepository
|
||||
import com.github.dannecron.demo.models.OrderWithProducts
|
||||
import org.springframework.stereotype.Service
|
||||
import org.springframework.transaction.annotation.Transactional
|
||||
import java.time.OffsetDateTime
|
||||
import java.util.*
|
||||
|
||||
@Service
|
||||
class OrderServiceImpl(
|
||||
@Autowired private val orderRepository: OrderRepository,
|
||||
@Autowired private val orderProductRepository: OrderProductRepository,
|
||||
@Autowired private val productRepository: ProductRepository,
|
||||
private val orderRepository: OrderRepository,
|
||||
private val orderProductRepository: OrderProductRepository,
|
||||
private val productRepository: ProductRepository,
|
||||
private val commonGenerator: CommonGenerator,
|
||||
) {
|
||||
fun getByCustomerId(customerId: Long): List<OrderWithProducts> = orderRepository.findByCustomerId(customerId)
|
||||
fun findByCustomerId(customerId: Long): List<OrderWithProducts> = orderRepository.findByCustomerId(customerId)
|
||||
.let { orders -> orders.map { order -> OrderWithProducts(
|
||||
order = order,
|
||||
products = orderProductRepository.findByOrderId(orderId = order.id!!)
|
||||
.map { orderProduct -> orderProduct.productId }
|
||||
.let { productIds -> productRepository.findAllById(productIds).toList() }
|
||||
products = findProductsByOrderId(order.id!!),
|
||||
) } }
|
||||
|
||||
@Transactional
|
||||
fun createOrder(customer: Customer, products: Set<Product>): Order {
|
||||
val order = Order(
|
||||
id = null,
|
||||
guid = UUID.randomUUID(),
|
||||
guid = commonGenerator.generateUUID(),
|
||||
customerId = customer.id!!,
|
||||
deliveredAt = null,
|
||||
createdAt = OffsetDateTime.now(),
|
||||
createdAt = commonGenerator.generateCurrentTime(),
|
||||
updatedAt = null,
|
||||
)
|
||||
|
||||
return orderRepository.save(order)
|
||||
.also {
|
||||
savedOrder -> products.toList()
|
||||
.map { product -> OrderProduct(
|
||||
guid = UUID.randomUUID(),
|
||||
orderId = savedOrder.id!!,
|
||||
productId = product.id!!,
|
||||
createdAt = OffsetDateTime.now(),
|
||||
updatedAt = null
|
||||
) }
|
||||
.also { orderProductRepository.saveAll(it) }
|
||||
.also { saveProductsForNewOrder(it, products.toList()) }
|
||||
}
|
||||
|
||||
private fun findProductsByOrderId(orderId: Long): List<Product> =
|
||||
orderProductRepository.findByOrderId(orderId = orderId)
|
||||
.map { it.productId }
|
||||
.let {
|
||||
if (it.isEmpty()) {
|
||||
emptyList()
|
||||
} else {
|
||||
productRepository.findAllById(it).toList()
|
||||
}
|
||||
}
|
||||
|
||||
private fun saveProductsForNewOrder(savedOrder: Order, products: List<Product>) {
|
||||
products.map {
|
||||
OrderProduct(
|
||||
guid = commonGenerator.generateUUID(),
|
||||
orderId = savedOrder.id!!,
|
||||
productId = it.id!!,
|
||||
createdAt = commonGenerator.generateCurrentTime(),
|
||||
updatedAt = null
|
||||
)
|
||||
}.also { orderProductRepository.saveAll(it) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package com.github.dannecron.demo.services.database.product
|
||||
|
||||
import com.github.dannecron.demo.models.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.*
|
||||
import java.util.UUID
|
||||
|
||||
@Service
|
||||
interface ProductService {
|
||||
|
||||
@@ -1,21 +1,26 @@
|
||||
package com.github.dannecron.demo.services.database.product
|
||||
|
||||
import com.github.dannecron.demo.models.Product
|
||||
import com.github.dannecron.demo.providers.ProductRepository
|
||||
import com.github.dannecron.demo.core.services.generation.CommonGenerator
|
||||
import com.github.dannecron.demo.db.entity.Product
|
||||
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 org.springframework.data.domain.Page
|
||||
import org.springframework.data.domain.Pageable
|
||||
import java.time.OffsetDateTime
|
||||
import java.util.*
|
||||
import org.springframework.stereotype.Service
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.util.UUID
|
||||
|
||||
@Service
|
||||
class ProductServiceImpl(
|
||||
private val defaultSyncTopic: String,
|
||||
private val productRepository: ProductRepository,
|
||||
private val producer: Producer,
|
||||
private val commonGenerator: CommonGenerator,
|
||||
): ProductService {
|
||||
private val logger by LoggerDelegate()
|
||||
|
||||
@@ -32,11 +37,11 @@ class ProductServiceImpl(
|
||||
override fun create(name: String, price: Long, description: String?): Product {
|
||||
val product = Product(
|
||||
id = null,
|
||||
guid = UUID.randomUUID(),
|
||||
guid = commonGenerator.generateUUID(),
|
||||
name = name,
|
||||
description = description,
|
||||
price = price,
|
||||
createdAt = OffsetDateTime.now(),
|
||||
createdAt = commonGenerator.generateCurrentTime(),
|
||||
updatedAt = null,
|
||||
deletedAt = null,
|
||||
)
|
||||
@@ -52,7 +57,7 @@ class ProductServiceImpl(
|
||||
}
|
||||
|
||||
val deletedProduct = product.copy(
|
||||
deletedAt = OffsetDateTime.now(),
|
||||
deletedAt = commonGenerator.generateCurrentTime(),
|
||||
)
|
||||
|
||||
return productRepository.save(deletedProduct)
|
||||
@@ -61,7 +66,17 @@ class ProductServiceImpl(
|
||||
override fun syncToKafka(guid: UUID, topic: String?) {
|
||||
val product = findByGuid(guid) ?: throw ProductNotFoundException()
|
||||
|
||||
producer.produceProductInfo(topic ?: defaultSyncTopic, product)
|
||||
producer.produceProductSync(product.toKafkaDto())
|
||||
}
|
||||
|
||||
private fun Product.toKafkaDto() = ProductDto(
|
||||
id = id ?: throw InvalidArgumentException("product.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),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
package com.github.dannecron.demo.services.validation
|
||||
|
||||
import com.github.dannecron.demo.services.validation.exceptions.ElementNotValidException
|
||||
import com.github.dannecron.demo.services.validation.exceptions.SchemaNotFoundException
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
|
||||
interface SchemaValidator {
|
||||
companion object {
|
||||
const val SCHEMA_KAFKA_PRODUCT_SYNC = "kafka-product-sync"
|
||||
}
|
||||
|
||||
@Throws(ElementNotValidException::class, SchemaNotFoundException::class)
|
||||
fun validate(schemaName: String, value: JsonElement)
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
package com.github.dannecron.demo.services.validation
|
||||
|
||||
import com.github.dannecron.demo.services.validation.exceptions.ElementNotValidException
|
||||
import com.github.dannecron.demo.services.validation.exceptions.SchemaNotFoundException
|
||||
import io.github.optimumcode.json.schema.JsonSchema
|
||||
import io.github.optimumcode.json.schema.ValidationError
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import org.springframework.util.ResourceUtils
|
||||
|
||||
class SchemaValidatorImp(
|
||||
private val schemaMap: Map<String, String>,
|
||||
): SchemaValidator {
|
||||
private val loadedSchema: MutableMap<String, String> = mutableMapOf()
|
||||
|
||||
override fun validate(schemaName: String, value: JsonElement) {
|
||||
JsonSchema.fromDefinition(
|
||||
getSchema(schemaName),
|
||||
).also {
|
||||
val errors = mutableListOf<ValidationError>()
|
||||
|
||||
if (!it.validate(value, errors::add)) {
|
||||
throw ElementNotValidException(errors)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getSchema(schemaName: String): String {
|
||||
val loaded = loadedSchema[schemaName]
|
||||
if (loaded != null) {
|
||||
return loaded
|
||||
}
|
||||
|
||||
val schemaFile = schemaMap[schemaName]
|
||||
?: throw SchemaNotFoundException()
|
||||
|
||||
val schema = ResourceUtils.getFile("classpath:json-schemas/$schemaFile")
|
||||
.readText(Charsets.UTF_8)
|
||||
loadedSchema[schemaName] = schema
|
||||
|
||||
return schema
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
package com.github.dannecron.demo.services.validation.exceptions
|
||||
|
||||
class SchemaNotFoundException: RuntimeException()
|
||||
@@ -1,14 +0,0 @@
|
||||
package com.github.dannecron.demo.utils
|
||||
|
||||
import kotlin.math.pow
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
fun Double.roundTo(numFractionDigits: Int): Double {
|
||||
val factor = 10.0.pow(numFractionDigits.toDouble())
|
||||
return (this * factor).roundToInt() / factor
|
||||
}
|
||||
|
||||
fun String.snakeToCamelCase(): String {
|
||||
val pattern = "_[a-z]".toRegex()
|
||||
return replace(pattern) { it.value.last().uppercase() }
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package com.github.dannecron.demo
|
||||
|
||||
import com.github.dannecron.demo.services.kafka.Producer
|
||||
import org.springframework.boot.test.autoconfigure.data.jdbc.DataJdbcTest
|
||||
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase
|
||||
import org.springframework.boot.test.mock.mockito.MockBean
|
||||
import org.springframework.data.jdbc.repository.config.EnableJdbcRepositories
|
||||
import org.springframework.test.context.ActiveProfiles
|
||||
import org.testcontainers.junit.jupiter.Testcontainers
|
||||
|
||||
@ActiveProfiles("db")
|
||||
@DataJdbcTest
|
||||
@Testcontainers(disabledWithoutDocker = false)
|
||||
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
|
||||
@EnableJdbcRepositories
|
||||
class BaseDbTest {
|
||||
@MockBean
|
||||
lateinit var producer: Producer
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
package com.github.dannecron.demo.services.database.city
|
||||
|
||||
import com.github.dannecron.demo.BaseDbTest
|
||||
import com.github.dannecron.demo.models.City
|
||||
import com.github.dannecron.demo.providers.CityRepository
|
||||
import com.github.dannecron.demo.services.database.exceptions.AlreadyDeletedException
|
||||
import com.github.dannecron.demo.services.database.exceptions.CityNotFoundException
|
||||
import org.junit.jupiter.api.assertThrows
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.test.context.ContextConfiguration
|
||||
import java.util.*
|
||||
import kotlin.test.*
|
||||
|
||||
@ContextConfiguration(classes = [CityRepository::class, CityServiceImpl::class])
|
||||
class CityServiceImplDbTest: BaseDbTest() {
|
||||
@Autowired
|
||||
private lateinit var cityRepository: CityRepository
|
||||
@Autowired
|
||||
private lateinit var cityServiceImpl: CityServiceImpl
|
||||
|
||||
@Test
|
||||
fun createFindDelete_success() {
|
||||
val name = "Some city"
|
||||
var city: City? = null
|
||||
|
||||
try {
|
||||
city = cityServiceImpl.create(name = name)
|
||||
assertNotNull(city.id)
|
||||
assertEquals(name, city.name)
|
||||
|
||||
val dbCity = cityServiceImpl.findByGuid(city.guid)
|
||||
assertNotNull(dbCity)
|
||||
assertEquals(city.id, dbCity.id)
|
||||
assertFalse(dbCity.isDeleted())
|
||||
|
||||
val deletedCity = cityServiceImpl.delete(city.guid)
|
||||
assertEquals(city.id, deletedCity.id)
|
||||
assertNotNull(deletedCity.deletedAt)
|
||||
assertTrue(deletedCity.isDeleted())
|
||||
|
||||
assertThrows<AlreadyDeletedException> {
|
||||
cityServiceImpl.delete(city.guid)
|
||||
}
|
||||
|
||||
assertThrows<CityNotFoundException> {
|
||||
cityServiceImpl.delete(UUID.randomUUID())
|
||||
}
|
||||
} finally {
|
||||
val id = city?.id
|
||||
if (id != null) {
|
||||
cityRepository.deleteById(id)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
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.kafka.dto.CityCreateDto
|
||||
import org.mockito.kotlin.any
|
||||
import org.mockito.kotlin.doReturn
|
||||
import org.mockito.kotlin.mock
|
||||
import org.mockito.kotlin.times
|
||||
import org.mockito.kotlin.verify
|
||||
import org.mockito.kotlin.whenever
|
||||
import java.time.OffsetDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.util.UUID
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class CityServiceImplTest {
|
||||
private val mockGuid = UUID.randomUUID()
|
||||
private val mockCurrentTime = OffsetDateTime.now()
|
||||
|
||||
private val commonGenerator: CommonGenerator = mock {
|
||||
on { generateUUID() } doReturn mockGuid
|
||||
on { generateCurrentTime() } doReturn mockCurrentTime
|
||||
}
|
||||
private val cityRepository: CityRepository = mock()
|
||||
private val cityServiceImpl = CityServiceImpl(cityRepository, commonGenerator)
|
||||
|
||||
private val city = City(
|
||||
id = 1000,
|
||||
guid = mockGuid,
|
||||
name = "name",
|
||||
createdAt = mockCurrentTime,
|
||||
updatedAt = null,
|
||||
deletedAt = null,
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `create - by name`() {
|
||||
whenever(cityRepository.save(any<City>())).thenReturn(city)
|
||||
|
||||
val result = cityServiceImpl.create("name")
|
||||
assertEquals(city, result)
|
||||
|
||||
verify(cityRepository, times(1)).save(city.copy(id = null))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `create - by dto`() {
|
||||
val cityGuid = UUID.randomUUID()
|
||||
val createdAt = OffsetDateTime.now()
|
||||
val cityCreate = CityCreateDto(
|
||||
guid = cityGuid.toString(),
|
||||
name = "name",
|
||||
createdAt = createdAt.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME),
|
||||
updatedAt = null,
|
||||
deletedAt = null,
|
||||
)
|
||||
|
||||
val expectedCity = city.copy(guid = cityGuid, createdAt = createdAt)
|
||||
|
||||
whenever(cityRepository.save(any<City>())).thenReturn(expectedCity)
|
||||
|
||||
val result = cityServiceImpl.create(cityCreate)
|
||||
assertEquals(expectedCity, result)
|
||||
|
||||
verify(cityRepository, times(1)).save(expectedCity.copy(id = null))
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
package com.github.dannecron.demo.services.database.customer
|
||||
|
||||
import com.github.dannecron.demo.BaseDbTest
|
||||
import com.github.dannecron.demo.models.City
|
||||
import com.github.dannecron.demo.providers.CityRepository
|
||||
import com.github.dannecron.demo.providers.CustomerRepository
|
||||
import com.github.dannecron.demo.services.database.exceptions.CityNotFoundException
|
||||
import org.junit.jupiter.api.assertThrows
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.test.context.ContextConfiguration
|
||||
import java.time.OffsetDateTime
|
||||
import java.util.*
|
||||
import kotlin.test.*
|
||||
|
||||
@ContextConfiguration(classes = [CustomerRepository::class, CityRepository::class, CustomerServiceImpl::class])
|
||||
class CustomerServiceImplDbTest: BaseDbTest() {
|
||||
@Autowired
|
||||
private lateinit var customerRepository: CustomerRepository
|
||||
|
||||
@Autowired
|
||||
private lateinit var cityRepository: CityRepository
|
||||
|
||||
@Autowired
|
||||
private lateinit var customerServiceImpl: CustomerServiceImpl
|
||||
|
||||
@Test
|
||||
fun createFind_success() {
|
||||
val nameOne = "Some Dude-One"
|
||||
val nameTwo = "Some Dude-Two"
|
||||
val nameThree = "Some Dude-Three"
|
||||
var city = City(
|
||||
id = null,
|
||||
guid = UUID.randomUUID(),
|
||||
name = "some city name",
|
||||
createdAt = OffsetDateTime.now(),
|
||||
updatedAt = null,
|
||||
deletedAt = null,
|
||||
)
|
||||
var customerIds = longArrayOf()
|
||||
|
||||
try {
|
||||
city = cityRepository.save(city)
|
||||
|
||||
customerServiceImpl.create(nameTwo, null).let {
|
||||
customerIds += it.id ?: fail("customerWithNoCity id is null")
|
||||
assertNull(it.cityId)
|
||||
assertNotNull(it.createdAt)
|
||||
assertNull(it.updatedAt)
|
||||
}
|
||||
|
||||
val customerWithCity = customerServiceImpl.create(nameOne, city.guid)
|
||||
customerIds += customerWithCity.id ?: fail("customerWithCity id is null")
|
||||
assertEquals(city.id, customerWithCity.cityId)
|
||||
assertNotNull(customerWithCity.createdAt)
|
||||
assertNull(customerWithCity.updatedAt)
|
||||
|
||||
val existedCustomer = customerServiceImpl.findByGuid(customerWithCity.guid)
|
||||
assertNotNull(existedCustomer)
|
||||
assertEquals(customerWithCity.id, existedCustomer.customer.id)
|
||||
assertEquals(city.id, existedCustomer.city?.id)
|
||||
|
||||
assertThrows<CityNotFoundException> {
|
||||
customerServiceImpl.create(nameThree, UUID.randomUUID())
|
||||
}
|
||||
} finally {
|
||||
val cityId = city.id
|
||||
if (cityId != null) {
|
||||
cityRepository.deleteById(cityId)
|
||||
}
|
||||
|
||||
customerIds.onEach { customerRepository.deleteById(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
package com.github.dannecron.demo.services.database.customer
|
||||
|
||||
import com.github.dannecron.demo.core.services.generation.CommonGenerator
|
||||
import com.github.dannecron.demo.db.entity.City
|
||||
import com.github.dannecron.demo.db.entity.Customer
|
||||
import com.github.dannecron.demo.db.repository.CityRepository
|
||||
import com.github.dannecron.demo.db.repository.CustomerRepository
|
||||
import com.github.dannecron.demo.models.CustomerExtended
|
||||
import com.github.dannecron.demo.services.database.exceptions.CityNotFoundException
|
||||
import org.junit.jupiter.api.assertThrows
|
||||
import org.mockito.kotlin.any
|
||||
import org.mockito.kotlin.doReturn
|
||||
import org.mockito.kotlin.mock
|
||||
import org.mockito.kotlin.never
|
||||
import org.mockito.kotlin.times
|
||||
import org.mockito.kotlin.verify
|
||||
import org.mockito.kotlin.verifyNoInteractions
|
||||
import org.mockito.kotlin.whenever
|
||||
import java.time.OffsetDateTime
|
||||
import java.util.Optional
|
||||
import java.util.UUID
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class CustomerServiceImplTest {
|
||||
private val mockGuid = UUID.randomUUID()
|
||||
private val mockCurrentTime = OffsetDateTime.now()
|
||||
|
||||
private val commonGenerator: CommonGenerator = mock {
|
||||
on { generateUUID() } doReturn mockGuid
|
||||
on { generateCurrentTime() } doReturn mockCurrentTime
|
||||
}
|
||||
|
||||
private val customerRepository: CustomerRepository = mock()
|
||||
private val cityRepository: CityRepository = mock()
|
||||
private val customerServiceImpl = CustomerServiceImpl(
|
||||
customerRepository = customerRepository,
|
||||
cityRepository = cityRepository,
|
||||
commonGenerator = commonGenerator,
|
||||
)
|
||||
|
||||
private val cityId = 123L
|
||||
private val cityGuid = UUID.randomUUID()
|
||||
private val customer = Customer(
|
||||
id = 1,
|
||||
guid = mockGuid,
|
||||
name = "name",
|
||||
cityId = cityId,
|
||||
createdAt = mockCurrentTime,
|
||||
updatedAt = null,
|
||||
)
|
||||
private val city = City(
|
||||
id = cityId,
|
||||
guid = cityGuid,
|
||||
name = "city",
|
||||
createdAt = OffsetDateTime.now(),
|
||||
updatedAt = null,
|
||||
deletedAt = null,
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `create - success - with city`() {
|
||||
whenever(customerRepository.save(any<Customer>())).thenReturn(customer)
|
||||
whenever(cityRepository.findByGuid(cityGuid)).thenReturn(city)
|
||||
|
||||
val result = customerServiceImpl.create("name", cityGuid)
|
||||
assertEquals(customer, result)
|
||||
|
||||
verify(customerRepository, times(1)).save(customer.copy(id = null))
|
||||
verify(cityRepository, times(1)).findByGuid(cityGuid)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `create - success - no city`() {
|
||||
val customerNoCity = customer.copy(cityId = null)
|
||||
|
||||
whenever(customerRepository.save(any<Customer>())).thenReturn(customerNoCity)
|
||||
|
||||
val result = customerServiceImpl.create("name", null)
|
||||
assertEquals(customerNoCity, result)
|
||||
|
||||
verify(customerRepository, times(1)).save(customerNoCity.copy(id = null))
|
||||
verifyNoInteractions(cityRepository)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `create - fail - with city`() {
|
||||
whenever(customerRepository.save(any<Customer>())).thenReturn(customer)
|
||||
whenever(cityRepository.findByGuid(cityGuid)).thenReturn(null)
|
||||
|
||||
assertThrows<CityNotFoundException> {
|
||||
customerServiceImpl.create("name", cityGuid)
|
||||
}
|
||||
|
||||
verify(customerRepository, never()).save(customer.copy(id = null))
|
||||
verify(cityRepository, times(1)).findByGuid(cityGuid)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `findByGuid - with city`() {
|
||||
val customerGuid = mockGuid
|
||||
whenever(customerRepository.findByGuid(any())).thenReturn(customer)
|
||||
whenever(cityRepository.findById(any())).thenReturn(Optional.of(city))
|
||||
|
||||
val result = customerServiceImpl.findByGuid(customerGuid)
|
||||
assertEquals(CustomerExtended(customer, city), result)
|
||||
|
||||
verify(customerRepository, times(1)).findByGuid(customerGuid)
|
||||
verify(cityRepository, times(1)).findById(cityId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `findByGuid - no city`() {
|
||||
val customerGuid = mockGuid
|
||||
whenever(customerRepository.findByGuid(any())).thenReturn(customer)
|
||||
whenever(cityRepository.findById(any())).thenReturn(Optional.empty())
|
||||
|
||||
val result = customerServiceImpl.findByGuid(customerGuid)
|
||||
assertEquals(CustomerExtended(customer, null), result)
|
||||
|
||||
verify(customerRepository, times(1)).findByGuid(customerGuid)
|
||||
verify(cityRepository, times(1)).findById(cityId)
|
||||
}
|
||||
}
|
||||
@@ -1,83 +1,147 @@
|
||||
package com.github.dannecron.demo.services.database.order
|
||||
|
||||
import com.github.dannecron.demo.BaseDbTest
|
||||
import com.github.dannecron.demo.models.Customer
|
||||
import com.github.dannecron.demo.models.Product
|
||||
import com.github.dannecron.demo.models.order.Order
|
||||
import com.github.dannecron.demo.providers.CustomerRepository
|
||||
import com.github.dannecron.demo.providers.OrderRepository
|
||||
import com.github.dannecron.demo.providers.ProductRepository
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.test.context.ContextConfiguration
|
||||
import com.github.dannecron.demo.core.services.generation.CommonGenerator
|
||||
import com.github.dannecron.demo.db.entity.Customer
|
||||
import com.github.dannecron.demo.db.entity.Product
|
||||
import com.github.dannecron.demo.db.entity.order.Order
|
||||
import com.github.dannecron.demo.db.entity.order.OrderProduct
|
||||
import com.github.dannecron.demo.db.repository.OrderProductRepository
|
||||
import com.github.dannecron.demo.db.repository.OrderRepository
|
||||
import com.github.dannecron.demo.db.repository.ProductRepository
|
||||
import com.github.dannecron.demo.models.OrderWithProducts
|
||||
import org.mockito.kotlin.any
|
||||
import org.mockito.kotlin.doReturn
|
||||
import org.mockito.kotlin.mock
|
||||
import org.mockito.kotlin.times
|
||||
import org.mockito.kotlin.verify
|
||||
import org.mockito.kotlin.whenever
|
||||
import java.time.OffsetDateTime
|
||||
import java.util.*
|
||||
import java.util.UUID
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNotNull
|
||||
|
||||
@ContextConfiguration(classes = [OrderServiceImpl::class])
|
||||
class OrderServiceImplTest: BaseDbTest() {
|
||||
@Autowired
|
||||
private lateinit var orderRepository: OrderRepository
|
||||
@Autowired
|
||||
private lateinit var productRepository: ProductRepository
|
||||
@Autowired
|
||||
private lateinit var customerRepository: CustomerRepository
|
||||
@Autowired
|
||||
private lateinit var orderServiceImpl: OrderServiceImpl
|
||||
class OrderServiceImplTest {
|
||||
private val mockGuid = UUID.randomUUID()
|
||||
private val mockCurrentTime = OffsetDateTime.now()
|
||||
|
||||
@Test
|
||||
fun createAndFind_success() {
|
||||
var productOneId: Long? = null
|
||||
var productTwoId: Long? = null
|
||||
var customerId: Long? = null
|
||||
var order: Order? = null
|
||||
|
||||
try {
|
||||
val productOne = makeProduct().let { productRepository.save(it) }.also { productOneId = it.id!! }
|
||||
val productTwo = makeProduct(price = 20000L)
|
||||
.let { productRepository.save(it) }
|
||||
.also { productTwoId = it.id!! }
|
||||
val customer = makeCustomer().let { customerRepository.save(it) }.also { customerId = it.id!! }
|
||||
|
||||
order = orderServiceImpl.createOrder(customer, setOf(productOne, productTwo))
|
||||
assertNotNull(order.id)
|
||||
|
||||
val orderWithProducts = orderServiceImpl.getByCustomerId(customerId = customerId!!)
|
||||
assertEquals(1, orderWithProducts.count())
|
||||
orderWithProducts.first().also {
|
||||
assertEquals(order.id, it.order.id)
|
||||
assertEquals(2, it.products.count())
|
||||
assertEquals(productTwo.id, it.getMostExpensiveOrderedProduct()?.id)
|
||||
assertEquals(300.00, it.getTotalOrderPrice())
|
||||
private val commonGenerator: CommonGenerator = mock {
|
||||
on { generateUUID() } doReturn mockGuid
|
||||
on { generateCurrentTime() } doReturn mockCurrentTime
|
||||
}
|
||||
|
||||
private val orderRepository: OrderRepository = mock()
|
||||
private val productRepository: ProductRepository = mock()
|
||||
private val orderProductRepository: OrderProductRepository = mock()
|
||||
|
||||
} finally {
|
||||
order?.id?.also { orderRepository.deleteById(it) }
|
||||
customerId?.also { customerRepository.deleteById(it) }
|
||||
productOneId?.also { productRepository.deleteById(it) }
|
||||
productTwoId?.also { productRepository.deleteById(it) }
|
||||
}
|
||||
}
|
||||
private val orderServiceImpl = OrderServiceImpl(
|
||||
orderRepository = orderRepository,
|
||||
orderProductRepository = orderProductRepository,
|
||||
productRepository = productRepository,
|
||||
commonGenerator = commonGenerator,
|
||||
)
|
||||
|
||||
private fun makeProduct(price: Long = 10000L): Product = Product(
|
||||
id = null,
|
||||
private val now = OffsetDateTime.now()
|
||||
|
||||
private val customerId = 123L
|
||||
private val customer = Customer(
|
||||
id = customerId,
|
||||
guid = UUID.randomUUID(),
|
||||
name = "name" + UUID.randomUUID(),
|
||||
name = "customer",
|
||||
cityId = null,
|
||||
createdAt = now,
|
||||
updatedAt = null,
|
||||
)
|
||||
|
||||
private val orderOneId = 1001L
|
||||
private val orderTwoId = 1002L
|
||||
private val orderOne = Order(
|
||||
id = orderOneId,
|
||||
guid = UUID.randomUUID(),
|
||||
customerId = customerId,
|
||||
deliveredAt = now.minusHours(1),
|
||||
createdAt = now.minusDays(1),
|
||||
updatedAt = now.minusHours(1),
|
||||
)
|
||||
private val orderTwo = Order(
|
||||
id = orderTwoId,
|
||||
guid = UUID.randomUUID(),
|
||||
customerId = customerId,
|
||||
deliveredAt = null,
|
||||
createdAt = now,
|
||||
updatedAt = null,
|
||||
)
|
||||
|
||||
private val productId = 100L
|
||||
private val product = Product(
|
||||
id = productId,
|
||||
guid = UUID.randomUUID(),
|
||||
name = "product",
|
||||
description = null,
|
||||
price = price,
|
||||
createdAt = OffsetDateTime.now(),
|
||||
price = 10000L,
|
||||
createdAt = now.minusMonths(1),
|
||||
updatedAt = null,
|
||||
deletedAt = null,
|
||||
)
|
||||
|
||||
private fun makeCustomer(): Customer = Customer(
|
||||
id = null,
|
||||
private val orderProduct = OrderProduct(
|
||||
guid = UUID.randomUUID(),
|
||||
name = "client",
|
||||
cityId = null,
|
||||
createdAt = OffsetDateTime.now(),
|
||||
orderId = orderOneId,
|
||||
productId = productId,
|
||||
createdAt = now.minusDays(1),
|
||||
updatedAt = null,
|
||||
)
|
||||
|
||||
@Test
|
||||
fun findByCustomerId() {
|
||||
whenever(orderRepository.findByCustomerId(any())).thenReturn(listOf(orderOne, orderTwo))
|
||||
whenever(orderProductRepository.findByOrderId(any()))
|
||||
.thenReturn(listOf(orderProduct))
|
||||
.thenReturn(emptyList())
|
||||
whenever(productRepository.findAllById(any())).thenReturn(listOf(product))
|
||||
|
||||
val expectedResult = listOf(
|
||||
OrderWithProducts(
|
||||
order = orderOne,
|
||||
products = listOf(product),
|
||||
),
|
||||
OrderWithProducts(
|
||||
order = orderTwo,
|
||||
products = emptyList(),
|
||||
),
|
||||
)
|
||||
|
||||
val result = orderServiceImpl.findByCustomerId(customerId)
|
||||
assertEquals(expectedResult, result)
|
||||
|
||||
verify(orderRepository, times(1)).findByCustomerId(customerId)
|
||||
verify(orderProductRepository, times(1)).findByOrderId(orderOneId)
|
||||
verify(orderProductRepository, times(1)).findByOrderId(orderTwoId)
|
||||
verify(productRepository, times(1)).findAllById(listOf(productId))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun create() {
|
||||
val newOrder = orderTwo.copy(
|
||||
guid = mockGuid,
|
||||
createdAt = mockCurrentTime,
|
||||
)
|
||||
val newOrderProduct = orderProduct.copy(
|
||||
guid = mockGuid,
|
||||
createdAt = mockCurrentTime,
|
||||
orderId = orderTwoId,
|
||||
)
|
||||
|
||||
whenever(orderRepository.save(any<Order>())).thenReturn(newOrder)
|
||||
whenever(orderProductRepository.saveAll(any<List<OrderProduct>>())).thenReturn(listOf(newOrderProduct))
|
||||
|
||||
val result = orderServiceImpl.createOrder(
|
||||
customer = customer,
|
||||
products = setOf(product),
|
||||
)
|
||||
|
||||
assertEquals(newOrder, result)
|
||||
|
||||
verify(orderRepository, times(1)).save(newOrder.copy(id = null))
|
||||
verify(orderProductRepository, times(1)).saveAll(listOf(newOrderProduct))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
package com.github.dannecron.demo.services.database.product
|
||||
|
||||
import com.github.dannecron.demo.BaseDbTest
|
||||
import com.github.dannecron.demo.models.Product
|
||||
import com.github.dannecron.demo.providers.ProductRepository
|
||||
import com.github.dannecron.demo.services.database.exceptions.AlreadyDeletedException
|
||||
import com.github.dannecron.demo.services.database.exceptions.ProductNotFoundException
|
||||
import org.junit.jupiter.api.assertThrows
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.test.context.ContextConfiguration
|
||||
import java.util.*
|
||||
import kotlin.test.*
|
||||
|
||||
@ContextConfiguration(classes = [ProductRepository::class])
|
||||
class ProductServiceImplDbTest: BaseDbTest() {
|
||||
private lateinit var productService: ProductServiceImpl
|
||||
@Autowired
|
||||
private lateinit var productRepository: ProductRepository
|
||||
|
||||
@BeforeTest
|
||||
fun setUp() {
|
||||
productService = ProductServiceImpl(
|
||||
defaultSyncTopic = "some-default-topic",
|
||||
productRepository = productRepository,
|
||||
producer = producer
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun createFindDelete_success() {
|
||||
val name = "new-product-name"
|
||||
val price = 33333L
|
||||
val description = "some-description"
|
||||
var product: Product? = null
|
||||
|
||||
try {
|
||||
product = productService.create(name = name, price = price, description = description)
|
||||
assertNotNull(product.id)
|
||||
assertEquals(name, product.name)
|
||||
assertEquals(price, product.price)
|
||||
assertEquals(333.33, product.getPriceDouble())
|
||||
|
||||
val dbProduct = productService.findByGuid(product.guid)
|
||||
assertNotNull(dbProduct)
|
||||
assertEquals(product.id, dbProduct.id)
|
||||
assertFalse(dbProduct.isDeleted())
|
||||
|
||||
val deletedProduct = productService.delete(product.guid)
|
||||
assertNotNull(deletedProduct)
|
||||
assertEquals(product.id, deletedProduct.id)
|
||||
assertNotNull(deletedProduct.deletedAt)
|
||||
assertTrue(deletedProduct.isDeleted())
|
||||
|
||||
// try to delete already deleted product
|
||||
assertThrows<AlreadyDeletedException> {
|
||||
productService.delete(product.guid)
|
||||
}
|
||||
|
||||
assertThrows<ProductNotFoundException> {
|
||||
productService.delete(UUID.randomUUID())
|
||||
}
|
||||
} finally {
|
||||
val id = product?.id
|
||||
if (id != null) {
|
||||
productRepository.deleteById(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,48 +1,46 @@
|
||||
package com.github.dannecron.demo.services.database.product
|
||||
|
||||
import com.github.dannecron.demo.BaseUnitTest
|
||||
import com.github.dannecron.demo.models.Product
|
||||
import com.github.dannecron.demo.providers.ProductRepository
|
||||
import com.github.dannecron.demo.core.services.generation.CommonGenerator
|
||||
import com.github.dannecron.demo.db.entity.Product
|
||||
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.exceptions.InvalidArgumentException
|
||||
import com.github.dannecron.demo.services.kafka.dto.ProductDto
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.assertThrows
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.kotlin.*
|
||||
import org.springframework.beans.factory.annotation.Qualifier
|
||||
import org.springframework.boot.test.context.SpringBootTest
|
||||
import org.springframework.boot.test.mock.mockito.MockBean
|
||||
import org.springframework.test.context.junit4.SpringRunner
|
||||
import org.mockito.kotlin.any
|
||||
import org.mockito.kotlin.doReturn
|
||||
import org.mockito.kotlin.mock
|
||||
import org.mockito.kotlin.never
|
||||
import org.mockito.kotlin.times
|
||||
import org.mockito.kotlin.verify
|
||||
import org.mockito.kotlin.verifyNoInteractions
|
||||
import org.mockito.kotlin.whenever
|
||||
import java.time.OffsetDateTime
|
||||
import java.util.*
|
||||
import kotlin.test.BeforeTest
|
||||
import kotlin.test.Test
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.util.UUID
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
@RunWith(SpringRunner::class)
|
||||
@SpringBootTest
|
||||
class ProductServiceImplTest: BaseUnitTest() {
|
||||
private val defaultTopic = "some-default-topic"
|
||||
private lateinit var productService: ProductServiceImpl
|
||||
class ProductServiceImplTest {
|
||||
private val mockGuid = UUID.randomUUID()
|
||||
private val mockCurrentTime = OffsetDateTime.now()
|
||||
|
||||
@MockBean
|
||||
@Qualifier("producer")
|
||||
private lateinit var producer: Producer
|
||||
@MockBean
|
||||
private lateinit var productRepository: ProductRepository
|
||||
|
||||
@BeforeTest
|
||||
fun setUp() {
|
||||
productService = ProductServiceImpl(
|
||||
defaultSyncTopic = defaultTopic,
|
||||
productRepository = productRepository,
|
||||
producer = producer,
|
||||
)
|
||||
private val productRepository: ProductRepository = mock()
|
||||
private val producer: Producer = mock()
|
||||
private val commonGenerator: CommonGenerator = mock {
|
||||
on { generateUUID() } doReturn mockGuid
|
||||
on { generateCurrentTime() } doReturn mockCurrentTime
|
||||
}
|
||||
|
||||
@Test
|
||||
fun syncToKafka_success() {
|
||||
val guid = UUID.randomUUID()
|
||||
val product = Product(
|
||||
private val productService = ProductServiceImpl(
|
||||
productRepository = productRepository,
|
||||
producer = producer,
|
||||
commonGenerator = commonGenerator,
|
||||
)
|
||||
|
||||
private val guid = UUID.randomUUID()
|
||||
private val product = Product(
|
||||
id = 123,
|
||||
guid = guid,
|
||||
name = "name",
|
||||
@@ -50,50 +48,102 @@ class ProductServiceImplTest: BaseUnitTest() {
|
||||
price = 10050,
|
||||
createdAt = OffsetDateTime.now().minusDays(1),
|
||||
updatedAt = OffsetDateTime.now().minusHours(2),
|
||||
deletedAt = OffsetDateTime.now(),
|
||||
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,
|
||||
)
|
||||
|
||||
whenever(productRepository.findByGuid(eq(guid))) doReturn product
|
||||
whenever(producer.produceProductInfo(defaultTopic, product)) doAnswer {}
|
||||
@Test
|
||||
fun create() {
|
||||
val expectedProductForCreation = product.copy(
|
||||
id = null,
|
||||
guid = mockGuid,
|
||||
createdAt = mockCurrentTime,
|
||||
updatedAt = null,
|
||||
)
|
||||
val expectedCreatedProduct = expectedProductForCreation.copy(id = 1)
|
||||
|
||||
whenever(productRepository.save<Product>(any())).thenReturn(expectedCreatedProduct)
|
||||
|
||||
val result = productService.create(
|
||||
name = "name",
|
||||
price = 10050,
|
||||
description = "description",
|
||||
)
|
||||
assertEquals(expectedCreatedProduct, result)
|
||||
|
||||
verify(productRepository, times(1)).save(expectedProductForCreation)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `delete - success`() {
|
||||
val deletedProduct = product.copy(
|
||||
deletedAt = mockCurrentTime,
|
||||
)
|
||||
whenever(productRepository.findByGuid(any())).thenReturn(product)
|
||||
whenever(productRepository.save<Product>(any())).thenReturn(deletedProduct)
|
||||
|
||||
val result = productService.delete(guid)
|
||||
assertEquals(deletedProduct, result)
|
||||
|
||||
verify(productRepository, times(1)).findByGuid(guid)
|
||||
verify(productRepository, times(1)).save(deletedProduct)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `delete - fail - already deleted`() {
|
||||
val deletedProduct = product.copy(
|
||||
deletedAt = mockCurrentTime,
|
||||
)
|
||||
whenever(productRepository.findByGuid(any())).thenReturn(deletedProduct)
|
||||
|
||||
assertThrows<AlreadyDeletedException> {
|
||||
productService.delete(guid)
|
||||
}
|
||||
|
||||
verify(productRepository, times(1)).findByGuid(guid)
|
||||
verify(productRepository, never()).save(any())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `delete - fail - not found`() {
|
||||
whenever(productRepository.findByGuid(any())).thenReturn(null)
|
||||
|
||||
assertThrows<ProductNotFoundException> {
|
||||
productService.delete(guid)
|
||||
}
|
||||
|
||||
verify(productRepository, times(1)).findByGuid(guid)
|
||||
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)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun syncToKafka_notFound() {
|
||||
val specificTopic = "specificNotice"
|
||||
val guid = UUID.randomUUID()
|
||||
|
||||
whenever(productRepository.findByGuid(eq(guid))) doReturn null
|
||||
|
||||
assertThrows<ProductNotFoundException> {
|
||||
productService.syncToKafka(guid, specificTopic)
|
||||
}
|
||||
|
||||
verify(productRepository, times(1)).findByGuid(guid)
|
||||
verifyNoInteractions(producer)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun syncToKafka_invalidArgumentException() {
|
||||
val specificTopic = "specificNotice"
|
||||
val guid = UUID.randomUUID()
|
||||
|
||||
val product = Product(
|
||||
id = 123,
|
||||
guid = guid,
|
||||
name = "name",
|
||||
description = "description",
|
||||
price = 10050,
|
||||
createdAt = OffsetDateTime.now().minusDays(1),
|
||||
updatedAt = OffsetDateTime.now().minusHours(2),
|
||||
deletedAt = OffsetDateTime.now(),
|
||||
)
|
||||
|
||||
whenever(productRepository.findByGuid(eq(guid))) doReturn product
|
||||
whenever(producer.produceProductInfo(specificTopic, product)) doThrow InvalidArgumentException("some error")
|
||||
|
||||
assertThrows< InvalidArgumentException> {
|
||||
productService.syncToKafka(guid, specificTopic)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user