mirror of
https://github.com/Dannecron/spring-boot-demo.git
synced 2025-12-26 00:32:34 +03:00
move and refactor customer service to core
fix core dto serialization
This commit is contained in:
@@ -1,14 +1,22 @@
|
|||||||
package com.github.dannecron.demo.core.dto
|
package com.github.dannecron.demo.core.dto
|
||||||
|
|
||||||
|
import com.github.dannecron.demo.db.serialialization.OffsetDateTimeSerialization
|
||||||
|
import com.github.dannecron.demo.db.serialialization.UuidSerialization
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
import java.time.OffsetDateTime
|
import java.time.OffsetDateTime
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
|
@Serializable
|
||||||
data class City(
|
data class City(
|
||||||
val id: Long,
|
val id: Long,
|
||||||
|
@Serializable(with = UuidSerialization::class)
|
||||||
val guid: UUID,
|
val guid: UUID,
|
||||||
val name: String,
|
val name: String,
|
||||||
|
@Serializable(with = OffsetDateTimeSerialization::class)
|
||||||
val createdAt: OffsetDateTime,
|
val createdAt: OffsetDateTime,
|
||||||
|
@Serializable(with = OffsetDateTimeSerialization::class)
|
||||||
val updatedAt: OffsetDateTime?,
|
val updatedAt: OffsetDateTime?,
|
||||||
|
@Serializable(with = OffsetDateTimeSerialization::class)
|
||||||
val deletedAt: OffsetDateTime?,
|
val deletedAt: OffsetDateTime?,
|
||||||
) {
|
) {
|
||||||
fun isDeleted(): Boolean = deletedAt != null
|
fun isDeleted(): Boolean = deletedAt != null
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package com.github.dannecron.demo.core.dto
|
||||||
|
|
||||||
|
import com.github.dannecron.demo.db.serialialization.OffsetDateTimeSerialization
|
||||||
|
import com.github.dannecron.demo.db.serialialization.UuidSerialization
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import java.time.OffsetDateTime
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class Customer(
|
||||||
|
val id: Long,
|
||||||
|
@Serializable(with = UuidSerialization::class)
|
||||||
|
val guid: UUID,
|
||||||
|
val name: String,
|
||||||
|
val cityId: Long?,
|
||||||
|
@Serializable(with = OffsetDateTimeSerialization::class)
|
||||||
|
val createdAt: OffsetDateTime,
|
||||||
|
@Serializable(with = OffsetDateTimeSerialization::class)
|
||||||
|
val updatedAt: OffsetDateTime?,
|
||||||
|
)
|
||||||
@@ -1,18 +1,26 @@
|
|||||||
package com.github.dannecron.demo.core.dto
|
package com.github.dannecron.demo.core.dto
|
||||||
|
|
||||||
|
import com.github.dannecron.demo.db.serialialization.OffsetDateTimeSerialization
|
||||||
|
import com.github.dannecron.demo.db.serialialization.UuidSerialization
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
import java.time.OffsetDateTime
|
import java.time.OffsetDateTime
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
import kotlin.math.pow
|
import kotlin.math.pow
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
@Serializable
|
||||||
data class Product(
|
data class Product(
|
||||||
val id: Long,
|
val id: Long,
|
||||||
|
@Serializable(with = UuidSerialization::class)
|
||||||
val guid: UUID,
|
val guid: UUID,
|
||||||
val name: String,
|
val name: String,
|
||||||
val description: String?,
|
val description: String?,
|
||||||
val price: Long,
|
val price: Long,
|
||||||
|
@Serializable(with = OffsetDateTimeSerialization::class)
|
||||||
val createdAt: OffsetDateTime,
|
val createdAt: OffsetDateTime,
|
||||||
|
@Serializable(with = OffsetDateTimeSerialization::class)
|
||||||
val updatedAt: OffsetDateTime?,
|
val updatedAt: OffsetDateTime?,
|
||||||
|
@Serializable(with = OffsetDateTimeSerialization::class)
|
||||||
val deletedAt: OffsetDateTime?,
|
val deletedAt: OffsetDateTime?,
|
||||||
) {
|
) {
|
||||||
fun getPriceDouble(): Double = (price.toDouble() / 100).roundTo(2)
|
fun getPriceDouble(): Double = (price.toDouble() / 100).roundTo(2)
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package com.github.dannecron.demo.core.dto.view
|
||||||
|
|
||||||
|
import com.github.dannecron.demo.core.dto.City
|
||||||
|
import com.github.dannecron.demo.core.dto.Customer
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class CustomerExtended(
|
||||||
|
val customer: Customer,
|
||||||
|
val city: City?,
|
||||||
|
)
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.github.dannecron.demo.core.services.customer
|
||||||
|
|
||||||
|
import com.github.dannecron.demo.core.dto.Customer
|
||||||
|
import com.github.dannecron.demo.core.dto.view.CustomerExtended
|
||||||
|
import com.github.dannecron.demo.core.exceptions.CityNotFoundException
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
interface CustomerService {
|
||||||
|
fun findByGuid(guid: UUID): CustomerExtended?
|
||||||
|
|
||||||
|
@Throws(CityNotFoundException::class)
|
||||||
|
fun create(name: String, cityGuid: UUID?): Customer
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
package com.github.dannecron.demo.core.services.customer
|
||||||
|
|
||||||
|
import com.github.dannecron.demo.core.dto.City
|
||||||
|
import com.github.dannecron.demo.core.dto.Customer
|
||||||
|
import com.github.dannecron.demo.core.dto.view.CustomerExtended
|
||||||
|
import com.github.dannecron.demo.core.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.entity.CustomerEntity
|
||||||
|
import com.github.dannecron.demo.db.repository.CityRepository
|
||||||
|
import com.github.dannecron.demo.db.repository.CustomerRepository
|
||||||
|
import org.springframework.stereotype.Service
|
||||||
|
import java.util.UUID
|
||||||
|
import kotlin.jvm.optionals.getOrNull
|
||||||
|
|
||||||
|
@Service
|
||||||
|
class CustomerServiceImpl(
|
||||||
|
private val customerRepository: CustomerRepository,
|
||||||
|
private val cityRepository: CityRepository,
|
||||||
|
private val commonGenerator: CommonGenerator,
|
||||||
|
) : CustomerService {
|
||||||
|
override fun findByGuid(guid: UUID): CustomerExtended? = customerRepository.findByGuid(guid)
|
||||||
|
?.let { customer ->
|
||||||
|
CustomerExtended(
|
||||||
|
customer = customer.toCore(),
|
||||||
|
city = customer.cityId?.let { cityId -> cityRepository.findById(cityId).getOrNull()?.toCore() }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(CityNotFoundException::class)
|
||||||
|
override fun create(name: String, cityGuid: UUID?): Customer = CustomerEntity(
|
||||||
|
id = null,
|
||||||
|
guid = commonGenerator.generateUUID(),
|
||||||
|
name = name,
|
||||||
|
cityId = cityGuid?.let {
|
||||||
|
cityRepository.findByGuid(it)?.id ?: throw CityNotFoundException()
|
||||||
|
},
|
||||||
|
createdAt = commonGenerator.generateCurrentTime(),
|
||||||
|
updatedAt = null,
|
||||||
|
).let(customerRepository::save)
|
||||||
|
.toCore()
|
||||||
|
|
||||||
|
private fun CustomerEntity.toCore() = Customer(
|
||||||
|
id = id!!,
|
||||||
|
guid = guid,
|
||||||
|
name = name,
|
||||||
|
cityId = cityId,
|
||||||
|
createdAt = createdAt,
|
||||||
|
updatedAt = updatedAt,
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun CityEntity.toCore() = City(
|
||||||
|
id = id!!,
|
||||||
|
guid = guid,
|
||||||
|
name = name,
|
||||||
|
createdAt = createdAt,
|
||||||
|
updatedAt = updatedAt,
|
||||||
|
deletedAt = deletedAt,
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,146 @@
|
|||||||
|
package com.github.dannecron.demo.core.services.customer
|
||||||
|
|
||||||
|
import com.github.dannecron.demo.core.dto.City
|
||||||
|
import com.github.dannecron.demo.core.dto.Customer
|
||||||
|
import com.github.dannecron.demo.core.dto.view.CustomerExtended
|
||||||
|
import com.github.dannecron.demo.core.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.entity.CustomerEntity
|
||||||
|
import com.github.dannecron.demo.db.repository.CityRepository
|
||||||
|
import com.github.dannecron.demo.db.repository.CustomerRepository
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
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.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 createdAt = OffsetDateTime.now()
|
||||||
|
|
||||||
|
private val customerEntity = CustomerEntity(
|
||||||
|
id = 1,
|
||||||
|
guid = mockGuid,
|
||||||
|
name = "name",
|
||||||
|
cityId = cityId,
|
||||||
|
createdAt = mockCurrentTime,
|
||||||
|
updatedAt = null,
|
||||||
|
)
|
||||||
|
private val customer = Customer(
|
||||||
|
id = 1,
|
||||||
|
guid = mockGuid,
|
||||||
|
name = "name",
|
||||||
|
cityId = cityId,
|
||||||
|
createdAt = mockCurrentTime,
|
||||||
|
updatedAt = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
private val cityEntity = CityEntity(
|
||||||
|
id = cityId,
|
||||||
|
guid = cityGuid,
|
||||||
|
name = "city",
|
||||||
|
createdAt = createdAt,
|
||||||
|
updatedAt = null,
|
||||||
|
deletedAt = null,
|
||||||
|
)
|
||||||
|
private val city = City(
|
||||||
|
id = cityId,
|
||||||
|
guid = cityGuid,
|
||||||
|
name = "city",
|
||||||
|
createdAt = createdAt,
|
||||||
|
updatedAt = null,
|
||||||
|
deletedAt = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `create - success - with city`() {
|
||||||
|
whenever(customerRepository.save(any<CustomerEntity>())).thenReturn(customerEntity)
|
||||||
|
whenever(cityRepository.findByGuid(cityGuid)).thenReturn(cityEntity)
|
||||||
|
|
||||||
|
val result = customerServiceImpl.create("name", cityGuid)
|
||||||
|
assertEquals(customer, result)
|
||||||
|
|
||||||
|
verify(customerRepository, times(1)).save(customerEntity.copy(id = null))
|
||||||
|
verify(cityRepository, times(1)).findByGuid(cityGuid)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `create - success - no city`() {
|
||||||
|
val customerNoCityEntity = customerEntity.copy(cityId = null)
|
||||||
|
val customerNoCity = customer.copy(cityId = null)
|
||||||
|
|
||||||
|
whenever(customerRepository.save(any<CustomerEntity>())).thenReturn(customerNoCityEntity)
|
||||||
|
|
||||||
|
val result = customerServiceImpl.create("name", null)
|
||||||
|
assertEquals(customerNoCity, result)
|
||||||
|
|
||||||
|
verify(customerRepository, times(1)).save(customerNoCityEntity.copy(id = null))
|
||||||
|
verifyNoInteractions(cityRepository)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `create - fail - with city`() {
|
||||||
|
whenever(customerRepository.save(any<CustomerEntity>())).thenReturn(customerEntity)
|
||||||
|
whenever(cityRepository.findByGuid(cityGuid)).thenReturn(null)
|
||||||
|
|
||||||
|
assertThrows<CityNotFoundException> {
|
||||||
|
customerServiceImpl.create("name", cityGuid)
|
||||||
|
}
|
||||||
|
|
||||||
|
verify(customerRepository, never()).save(customerEntity.copy(id = null))
|
||||||
|
verify(cityRepository, times(1)).findByGuid(cityGuid)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `findByGuid - with city`() {
|
||||||
|
val customerGuid = mockGuid
|
||||||
|
whenever(customerRepository.findByGuid(any())).thenReturn(customerEntity)
|
||||||
|
whenever(cityRepository.findById(any())).thenReturn(Optional.of(cityEntity))
|
||||||
|
|
||||||
|
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(customerEntity)
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user