mirror of
https://github.com/Dannecron/spring-boot-demo.git
synced 2025-12-25 16:22:35 +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
|
||||
|
||||
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 City(
|
||||
val id: Long,
|
||||
@Serializable(with = UuidSerialization::class)
|
||||
val guid: UUID,
|
||||
val name: String,
|
||||
@Serializable(with = OffsetDateTimeSerialization::class)
|
||||
val createdAt: OffsetDateTime,
|
||||
@Serializable(with = OffsetDateTimeSerialization::class)
|
||||
val updatedAt: OffsetDateTime?,
|
||||
@Serializable(with = OffsetDateTimeSerialization::class)
|
||||
val deletedAt: OffsetDateTime?,
|
||||
) {
|
||||
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
|
||||
|
||||
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
|
||||
import kotlin.math.pow
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
@Serializable
|
||||
data class Product(
|
||||
val id: Long,
|
||||
@Serializable(with = UuidSerialization::class)
|
||||
val guid: UUID,
|
||||
val name: String,
|
||||
val description: String?,
|
||||
val price: Long,
|
||||
@Serializable(with = OffsetDateTimeSerialization::class)
|
||||
val createdAt: OffsetDateTime,
|
||||
@Serializable(with = OffsetDateTimeSerialization::class)
|
||||
val updatedAt: OffsetDateTime?,
|
||||
@Serializable(with = OffsetDateTimeSerialization::class)
|
||||
val deletedAt: OffsetDateTime?,
|
||||
) {
|
||||
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