mirror of
https://github.com/Dannecron/spring-boot-demo.git
synced 2025-12-26 00:32:34 +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
|
||||
|
||||
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
|
||||
|
||||
@@ -1,20 +1,13 @@
|
||||
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,22 +1,14 @@
|
||||
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 Order(
|
||||
val id: Long,
|
||||
@Serializable(with = UuidSerialization::class)
|
||||
val guid: UUID,
|
||||
val customerId: Long,
|
||||
@Serializable(with = OffsetDateTimeSerialization::class)
|
||||
val deliveredAt: OffsetDateTime?,
|
||||
@Serializable(with = OffsetDateTimeSerialization::class)
|
||||
val createdAt: OffsetDateTime,
|
||||
@Serializable(with = OffsetDateTimeSerialization::class)
|
||||
val updatedAt: OffsetDateTime?,
|
||||
) {
|
||||
fun isDelivered(): Boolean = deliveredAt != null
|
||||
|
||||
@@ -1,19 +1,12 @@
|
||||
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 OrderProduct(
|
||||
@Serializable(with = UuidSerialization::class)
|
||||
val guid: UUID,
|
||||
val orderId: Long,
|
||||
val productId: Long,
|
||||
@Serializable(with = OffsetDateTimeSerialization::class)
|
||||
val createdAt: OffsetDateTime,
|
||||
@Serializable(with = OffsetDateTimeSerialization::class)
|
||||
val updatedAt: OffsetDateTime?,
|
||||
)
|
||||
|
||||
@@ -1,26 +1,18 @@
|
||||
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,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.Customer
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class CustomerExtended(
|
||||
val customer: Customer,
|
||||
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