mirror of
https://github.com/Dannecron/spring-boot-demo.git
synced 2025-12-25 16:22:35 +03:00
core: add NekoService, remove serialization annotations from dto
This commit is contained in:
@@ -1,22 +1,14 @@
|
|||||||
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
|
||||||
|
|||||||
@@ -1,20 +1,13 @@
|
|||||||
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 Customer(
|
data class Customer(
|
||||||
val id: Long,
|
val id: Long,
|
||||||
@Serializable(with = UuidSerialization::class)
|
|
||||||
val guid: UUID,
|
val guid: UUID,
|
||||||
val name: String,
|
val name: String,
|
||||||
val cityId: Long?,
|
val cityId: Long?,
|
||||||
@Serializable(with = OffsetDateTimeSerialization::class)
|
|
||||||
val createdAt: OffsetDateTime,
|
val createdAt: OffsetDateTime,
|
||||||
@Serializable(with = OffsetDateTimeSerialization::class)
|
|
||||||
val updatedAt: OffsetDateTime?,
|
val updatedAt: OffsetDateTime?,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,22 +1,14 @@
|
|||||||
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 Order(
|
data class Order(
|
||||||
val id: Long,
|
val id: Long,
|
||||||
@Serializable(with = UuidSerialization::class)
|
|
||||||
val guid: UUID,
|
val guid: UUID,
|
||||||
val customerId: Long,
|
val customerId: Long,
|
||||||
@Serializable(with = OffsetDateTimeSerialization::class)
|
|
||||||
val deliveredAt: OffsetDateTime?,
|
val deliveredAt: OffsetDateTime?,
|
||||||
@Serializable(with = OffsetDateTimeSerialization::class)
|
|
||||||
val createdAt: OffsetDateTime,
|
val createdAt: OffsetDateTime,
|
||||||
@Serializable(with = OffsetDateTimeSerialization::class)
|
|
||||||
val updatedAt: OffsetDateTime?,
|
val updatedAt: OffsetDateTime?,
|
||||||
) {
|
) {
|
||||||
fun isDelivered(): Boolean = deliveredAt != null
|
fun isDelivered(): Boolean = deliveredAt != null
|
||||||
|
|||||||
@@ -1,19 +1,12 @@
|
|||||||
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 OrderProduct(
|
data class OrderProduct(
|
||||||
@Serializable(with = UuidSerialization::class)
|
|
||||||
val guid: UUID,
|
val guid: UUID,
|
||||||
val orderId: Long,
|
val orderId: Long,
|
||||||
val productId: Long,
|
val productId: Long,
|
||||||
@Serializable(with = OffsetDateTimeSerialization::class)
|
|
||||||
val createdAt: OffsetDateTime,
|
val createdAt: OffsetDateTime,
|
||||||
@Serializable(with = OffsetDateTimeSerialization::class)
|
|
||||||
val updatedAt: OffsetDateTime?,
|
val updatedAt: OffsetDateTime?,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,26 +1,18 @@
|
|||||||
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,9 @@
|
|||||||
|
package com.github.dannecron.demo.core.dto.neko
|
||||||
|
|
||||||
|
data class ImageDto(
|
||||||
|
val url: String,
|
||||||
|
val animeName: String?,
|
||||||
|
val artistHref: String?,
|
||||||
|
val artistName: String?,
|
||||||
|
val sourceUrl: String?,
|
||||||
|
)
|
||||||
@@ -2,9 +2,7 @@ package com.github.dannecron.demo.core.dto.view
|
|||||||
|
|
||||||
import com.github.dannecron.demo.core.dto.City
|
import com.github.dannecron.demo.core.dto.City
|
||||||
import com.github.dannecron.demo.core.dto.Customer
|
import com.github.dannecron.demo.core.dto.Customer
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class CustomerExtended(
|
data class CustomerExtended(
|
||||||
val customer: Customer,
|
val customer: Customer,
|
||||||
val city: City?,
|
val city: City?,
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package com.github.dannecron.demo.core.exceptions.neko
|
||||||
|
|
||||||
|
class IntegrationException(
|
||||||
|
override val message: String,
|
||||||
|
override val cause: Throwable? = null,
|
||||||
|
) : RuntimeException()
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.github.dannecron.demo.core.services.neko
|
||||||
|
|
||||||
|
import com.github.dannecron.demo.core.dto.neko.ImageDto
|
||||||
|
import com.github.dannecron.demo.core.exceptions.neko.IntegrationException
|
||||||
|
|
||||||
|
interface NekoService {
|
||||||
|
|
||||||
|
@Throws(IntegrationException::class)
|
||||||
|
fun getCategories(): Set<String>
|
||||||
|
|
||||||
|
@Throws(IntegrationException::class)
|
||||||
|
fun getImages(category: String, amount: Int): List<ImageDto>
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package com.github.dannecron.demo.core.services.neko
|
||||||
|
|
||||||
|
import com.github.dannecron.demo.core.dto.neko.ImageDto
|
||||||
|
import com.github.dannecron.demo.core.exceptions.neko.IntegrationException
|
||||||
|
import com.github.dannecron.demo.edgeintegration.client.neko.Client
|
||||||
|
import com.github.dannecron.demo.edgeintegration.client.neko.dto.Image
|
||||||
|
import com.github.dannecron.demo.edgeintegration.client.neko.exceptions.RequestException
|
||||||
|
import org.springframework.stereotype.Service
|
||||||
|
|
||||||
|
@Service
|
||||||
|
class NekoServiceImpl(
|
||||||
|
private val nekoClient: Client
|
||||||
|
) : NekoService {
|
||||||
|
override fun getCategories(): Set<String> =
|
||||||
|
try {
|
||||||
|
nekoClient.getCategories()
|
||||||
|
} catch (ex: RequestException) {
|
||||||
|
throw IntegrationException("Neko request error", ex)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getImages(category: String, amount: Int): List<ImageDto> =
|
||||||
|
try {
|
||||||
|
nekoClient.getImages(category, amount).results.map { it.toCoreDto()}
|
||||||
|
} catch (ex: RequestException) {
|
||||||
|
throw IntegrationException("Neko request error", ex)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Image.toCoreDto() = ImageDto(
|
||||||
|
url = url,
|
||||||
|
animeName = animeName,
|
||||||
|
artistHref = artistHref,
|
||||||
|
artistName = artistName,
|
||||||
|
sourceUrl = sourceUrl,
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,165 @@
|
|||||||
|
package com.github.dannecron.demo.core.services.neko
|
||||||
|
|
||||||
|
import com.github.dannecron.demo.core.exceptions.neko.IntegrationException
|
||||||
|
import com.github.dannecron.demo.edgeintegration.client.neko.Client
|
||||||
|
import com.github.dannecron.demo.edgeintegration.client.neko.dto.Image
|
||||||
|
import com.github.dannecron.demo.edgeintegration.client.neko.dto.ImagesResponse
|
||||||
|
import com.github.dannecron.demo.edgeintegration.client.neko.exceptions.RequestException
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.assertThrows
|
||||||
|
import org.mockito.kotlin.doReturn
|
||||||
|
import org.mockito.kotlin.doThrow
|
||||||
|
import org.mockito.kotlin.mock
|
||||||
|
import org.mockito.kotlin.times
|
||||||
|
import org.mockito.kotlin.verify
|
||||||
|
import org.mockito.kotlin.whenever
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AI generated
|
||||||
|
*/
|
||||||
|
class NekoServiceImplTest {
|
||||||
|
|
||||||
|
private val nekoClient: Client = mock()
|
||||||
|
private val nekoService: NekoService = NekoServiceImpl(nekoClient)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `getCategories - success`() {
|
||||||
|
// Given
|
||||||
|
val expectedCategories = setOf("neko", "wink", "hug")
|
||||||
|
whenever(nekoClient.getCategories()).doReturn(expectedCategories)
|
||||||
|
|
||||||
|
// When
|
||||||
|
val result = nekoService.getCategories()
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assertEquals(expectedCategories, result)
|
||||||
|
verify(nekoClient, times(1)).getCategories()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `getCategories - throws IntegrationException when client throws RequestException`() {
|
||||||
|
// Given
|
||||||
|
val requestException = RequestException("Client error")
|
||||||
|
whenever(nekoClient.getCategories()).doThrow(requestException)
|
||||||
|
|
||||||
|
// When & Then
|
||||||
|
val exception = assertThrows<IntegrationException> {
|
||||||
|
nekoService.getCategories()
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals("Neko request error", exception.message)
|
||||||
|
assertEquals(requestException, exception.cause)
|
||||||
|
verify(nekoClient, times(1)).getCategories()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `getImages - success - maps images correctly`() {
|
||||||
|
// Given
|
||||||
|
val category = "neko"
|
||||||
|
val amount = 2
|
||||||
|
val image1 = Image(
|
||||||
|
url = "https://example.com/image1.png",
|
||||||
|
animeName = "Test Anime 1",
|
||||||
|
artistHref = "https://artist1.com",
|
||||||
|
artistName = "Artist 1",
|
||||||
|
sourceUrl = "https://source1.com"
|
||||||
|
)
|
||||||
|
val image2 = Image(
|
||||||
|
url = "https://example.com/image2.gif",
|
||||||
|
animeName = null,
|
||||||
|
artistHref = null,
|
||||||
|
artistName = null,
|
||||||
|
sourceUrl = null
|
||||||
|
)
|
||||||
|
val imagesResponse = ImagesResponse(results = listOf(image1, image2))
|
||||||
|
whenever(nekoClient.getImages(category, amount)).doReturn(imagesResponse)
|
||||||
|
|
||||||
|
// When
|
||||||
|
val result = nekoService.getImages(category, amount)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assertEquals(2, result.size)
|
||||||
|
|
||||||
|
val firstImage = result[0]
|
||||||
|
assertEquals("https://example.com/image1.png", firstImage.url)
|
||||||
|
assertEquals("Test Anime 1", firstImage.animeName)
|
||||||
|
assertEquals("https://artist1.com", firstImage.artistHref)
|
||||||
|
assertEquals("Artist 1", firstImage.artistName)
|
||||||
|
assertEquals("https://source1.com", firstImage.sourceUrl)
|
||||||
|
|
||||||
|
val secondImage = result[1]
|
||||||
|
assertEquals("https://example.com/image2.gif", secondImage.url)
|
||||||
|
assertEquals(null, secondImage.animeName)
|
||||||
|
assertEquals(null, secondImage.artistHref)
|
||||||
|
assertEquals(null, secondImage.artistName)
|
||||||
|
assertEquals(null, secondImage.sourceUrl)
|
||||||
|
|
||||||
|
verify(nekoClient, times(1)).getImages(category, amount)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `getImages - success - empty results`() {
|
||||||
|
// Given
|
||||||
|
val category = "empty"
|
||||||
|
val amount = 1
|
||||||
|
val imagesResponse = ImagesResponse(results = emptyList())
|
||||||
|
whenever(nekoClient.getImages(category, amount)).doReturn(imagesResponse)
|
||||||
|
|
||||||
|
// When
|
||||||
|
val result = nekoService.getImages(category, amount)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assertTrue(result.isEmpty())
|
||||||
|
verify(nekoClient, times(1)).getImages(category, amount)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `getImages - throws IntegrationException when client throws RequestException`() {
|
||||||
|
// Given
|
||||||
|
val category = "neko"
|
||||||
|
val amount = 5
|
||||||
|
val requestException = RequestException("Client error")
|
||||||
|
whenever(nekoClient.getImages(category, amount)).doThrow(requestException)
|
||||||
|
|
||||||
|
// When & Then
|
||||||
|
val exception = assertThrows<IntegrationException> {
|
||||||
|
nekoService.getImages(category, amount)
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals("Neko request error", exception.message)
|
||||||
|
assertEquals(requestException, exception.cause)
|
||||||
|
verify(nekoClient, times(1)).getImages(category, amount)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `getImages - success - single image with all fields null`() {
|
||||||
|
// Given
|
||||||
|
val category = "test"
|
||||||
|
val amount = 1
|
||||||
|
val image = Image(
|
||||||
|
url = "https://example.com/minimal.jpg",
|
||||||
|
animeName = null,
|
||||||
|
artistHref = null,
|
||||||
|
artistName = null,
|
||||||
|
sourceUrl = null
|
||||||
|
)
|
||||||
|
val imagesResponse = ImagesResponse(results = listOf(image))
|
||||||
|
whenever(nekoClient.getImages(category, amount)).doReturn(imagesResponse)
|
||||||
|
|
||||||
|
// When
|
||||||
|
val result = nekoService.getImages(category, amount)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assertEquals(1, result.size)
|
||||||
|
val resultImage = result[0]
|
||||||
|
assertEquals("https://example.com/minimal.jpg", resultImage.url)
|
||||||
|
assertEquals(null, resultImage.animeName)
|
||||||
|
assertEquals(null, resultImage.artistHref)
|
||||||
|
assertEquals(null, resultImage.artistName)
|
||||||
|
assertEquals(null, resultImage.sourceUrl)
|
||||||
|
|
||||||
|
verify(nekoClient, times(1)).getImages(category, amount)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user