mirror of
https://github.com/Dannecron/spring-boot-demo.git
synced 2025-12-25 16:22:35 +03:00
move neko integration to sub-project
This commit is contained in:
7
edge-integration/build.gradle.kts
Normal file
7
edge-integration/build.gradle.kts
Normal file
@@ -0,0 +1,7 @@
|
||||
dependencies {
|
||||
implementation(rootProject.libs.springFramework.context)
|
||||
implementation(rootProject.libs.ktor.client.cio)
|
||||
implementation(rootProject.libs.ktor.client.core)
|
||||
|
||||
testImplementation(rootProject.libs.ktor.client.mock)
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.github.dannecron.demo.edgeintegration.client
|
||||
|
||||
import com.github.dannecron.demo.edgeintegration.client.dto.ImagesResponse
|
||||
import com.github.dannecron.demo.edgeintegration.client.exceptions.RequestException
|
||||
|
||||
interface Client {
|
||||
@Throws(RequestException::class)
|
||||
fun getCategories(): Set<String>
|
||||
|
||||
@Throws(RequestException::class)
|
||||
fun getImages(category: String, amount: Int): ImagesResponse
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.github.dannecron.demo.edgeintegration.client
|
||||
|
||||
import com.github.dannecron.demo.edgeintegration.client.dto.CategoryFormat
|
||||
import com.github.dannecron.demo.edgeintegration.client.dto.ImagesResponse
|
||||
import com.github.dannecron.demo.edgeintegration.client.exceptions.RequestException
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.engine.HttpClientEngine
|
||||
import io.ktor.client.request.get
|
||||
import io.ktor.client.statement.bodyAsText
|
||||
import io.ktor.http.path
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.serialization.json.Json
|
||||
import org.springframework.stereotype.Service
|
||||
|
||||
@Service
|
||||
class ClientImpl(
|
||||
engine: HttpClientEngine,
|
||||
private val baseUrl: String,
|
||||
): Client {
|
||||
private val httpClient = HttpClient(engine)
|
||||
|
||||
override fun getCategories() = runBlocking {
|
||||
httpClient.get(urlString = baseUrl) {
|
||||
url {
|
||||
path("/api/v2/endpoints")
|
||||
}
|
||||
}
|
||||
.takeIf { it.status.value in 200..209 }
|
||||
?.let {
|
||||
response -> Json.decodeFromString<Map<String, CategoryFormat>>(response.bodyAsText()).keys
|
||||
}
|
||||
?: throw RequestException("get categories error")
|
||||
}
|
||||
|
||||
override fun getImages(category: String, amount: Int) = runBlocking {
|
||||
httpClient.get(urlString = baseUrl) {
|
||||
url {
|
||||
path("/api/v2/$category")
|
||||
parameters.append("amount", amount.toString())
|
||||
}
|
||||
}
|
||||
.takeIf { it.status.value in 200..209 }
|
||||
?.let {
|
||||
response -> Json.decodeFromString<ImagesResponse>(response.bodyAsText())
|
||||
}
|
||||
?: throw RequestException("get images error")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.github.dannecron.demo.edgeintegration.client.dto
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class CategoryFormat(
|
||||
val format: String,
|
||||
)
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.github.dannecron.demo.edgeintegration.client.dto
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class Image(
|
||||
val url: String,
|
||||
@SerialName("anime_name")
|
||||
val animeName: String? = null,
|
||||
@SerialName("artist_href")
|
||||
val artistHref: String? = null,
|
||||
@SerialName("artist_name")
|
||||
val artistName: String? = null,
|
||||
@SerialName("source_url")
|
||||
val sourceUrl: String? = null,
|
||||
)
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.github.dannecron.demo.edgeintegration.client.dto
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class ImagesResponse(
|
||||
val results: List<Image>
|
||||
)
|
||||
@@ -0,0 +1,3 @@
|
||||
package com.github.dannecron.demo.edgeintegration.client.exceptions
|
||||
|
||||
class RequestException(message: String): RuntimeException(message)
|
||||
@@ -0,0 +1,162 @@
|
||||
package com.github.dannecron.demo.edgeintegration.client
|
||||
|
||||
import com.github.dannecron.demo.edgeintegration.client.exceptions.RequestException
|
||||
import io.ktor.client.engine.mock.MockEngine
|
||||
import io.ktor.client.engine.mock.respond
|
||||
import io.ktor.http.HttpHeaders
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.http.headersOf
|
||||
import io.ktor.utils.io.ByteReadChannel
|
||||
import org.junit.jupiter.api.assertThrows
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertContains
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNotNull
|
||||
import kotlin.test.assertNull
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class ClientImplTest {
|
||||
@Test
|
||||
fun getCategories_success() {
|
||||
val mockEngine = MockEngine { req ->
|
||||
assertEquals("localhost", req.url.host)
|
||||
assertEquals("/api/v2/endpoints", req.url.encodedPath)
|
||||
|
||||
respond(
|
||||
content = ByteReadChannel(
|
||||
"""
|
||||
{"neko": {"format": "png"},"wink": {"format": "gif"}}
|
||||
""".trimIndent()
|
||||
),
|
||||
status = HttpStatusCode.OK,
|
||||
headers = headersOf(HttpHeaders.ContentType, "application/json"),
|
||||
)
|
||||
}
|
||||
|
||||
val client = ClientImpl(engine = mockEngine, baseUrl = "https://localhost")
|
||||
client.getCategories().let {
|
||||
assertContains(it.toList(), "neko")
|
||||
assertContains(it.toList(), "wink")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getCategories_fail() {
|
||||
val mockEngine = MockEngine { req ->
|
||||
assertEquals("localhost", req.url.host)
|
||||
assertEquals("/api/v2/endpoints", req.url.encodedPath)
|
||||
|
||||
respond(
|
||||
content = ByteReadChannel(""),
|
||||
status = HttpStatusCode.InternalServerError,
|
||||
headers = headersOf(HttpHeaders.ContentType, "text/plain"),
|
||||
)
|
||||
}
|
||||
|
||||
val client = ClientImpl(engine = mockEngine, baseUrl = "https://localhost")
|
||||
assertThrows<RequestException> {
|
||||
client.getCategories()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getImages_success_gifs() {
|
||||
val category = "hug"
|
||||
val amount = 2
|
||||
|
||||
val mockEngine = MockEngine { req ->
|
||||
assertEquals("localhost", req.url.host)
|
||||
assertEquals("/api/v2/$category", req.url.encodedPath)
|
||||
assertTrue {
|
||||
req.url.parameters.contains("amount", amount.toString())
|
||||
}
|
||||
|
||||
respond(
|
||||
content = ByteReadChannel(
|
||||
"""{"results":[
|
||||
{"anime_name":"Sword Art Online",
|
||||
"url":"https://nekos.best/api/v2/hug/c6a7d384-dc40-11ed-afa1-0242ac120002.gif"
|
||||
},
|
||||
{"anime_name":"Hibike! Euphonium",
|
||||
"url":"https://nekos.best/api/v2/hug/ca26cfba-dc40-11ed-afa1-0242ac120002.gif"
|
||||
}
|
||||
]}""".trimIndent(),
|
||||
),
|
||||
status = HttpStatusCode.OK,
|
||||
headers = headersOf(HttpHeaders.ContentType, "application/json"),
|
||||
)
|
||||
}
|
||||
|
||||
val client = ClientImpl(engine = mockEngine, baseUrl = "https://localhost")
|
||||
client.getImages(category = category, amount = amount).results.map {
|
||||
assertNull(it.sourceUrl)
|
||||
assertNull(it.artistName)
|
||||
assertNull(it.artistHref)
|
||||
assertNotNull(it.animeName)
|
||||
assertContains(it.url, "https://nekos.best/api/v2/hug")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getImages_success_jpegs() {
|
||||
val category = "neko"
|
||||
val amount = 1
|
||||
|
||||
val mockEngine = MockEngine { req ->
|
||||
assertEquals("localhost", req.url.host)
|
||||
assertEquals("/api/v2/$category", req.url.encodedPath)
|
||||
assertTrue {
|
||||
req.url.parameters.contains("amount", amount.toString())
|
||||
}
|
||||
|
||||
respond(
|
||||
content = ByteReadChannel(
|
||||
"""{"results":[
|
||||
{
|
||||
"artist_href":"https://www.pixiv.net/en/users/47065875",
|
||||
"artist_name":"かえで",
|
||||
"source_url":"https://www.pixiv.net/en/artworks/88682108",
|
||||
"url":"https://nekos.best/api/v2/neko/bbffa4e8-dc40-11ed-afa1-0242ac120002.png"
|
||||
}
|
||||
]}""".trimIndent(),
|
||||
),
|
||||
status = HttpStatusCode.OK,
|
||||
headers = headersOf(HttpHeaders.ContentType, "application/json"),
|
||||
)
|
||||
}
|
||||
|
||||
val client = ClientImpl(engine = mockEngine, baseUrl = "https://localhost")
|
||||
client.getImages(category = category, amount = amount).results.map {
|
||||
assertNotNull(it.sourceUrl)
|
||||
assertNotNull(it.artistName)
|
||||
assertNotNull(it.artistHref)
|
||||
assertNull(it.animeName)
|
||||
assertContains(it.url, "https://nekos.best/api/v2/neko")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getImages_fail() {
|
||||
val category = "hug"
|
||||
val amount = 2
|
||||
|
||||
val mockEngine = MockEngine { req ->
|
||||
assertEquals("localhost", req.url.host)
|
||||
assertEquals("/api/v2/$category", req.url.encodedPath)
|
||||
assertTrue {
|
||||
req.url.parameters.contains("amount", amount.toString())
|
||||
}
|
||||
|
||||
respond(
|
||||
content = ByteReadChannel(""),
|
||||
status = HttpStatusCode.InternalServerError,
|
||||
headers = headersOf(HttpHeaders.ContentType, "plain/text"),
|
||||
)
|
||||
}
|
||||
|
||||
val client = ClientImpl(engine = mockEngine, baseUrl = "https://localhost")
|
||||
assertThrows<RequestException> {
|
||||
client.getImages(category = category, amount = amount)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user