Skip to content

Commit 38b5fb2

Browse files
skarpovdevdevcrocod
authored andcommitted
fixup! Introduce Kotlin integration tests
1 parent aee13bb commit 38b5fb2

File tree

5 files changed

+69
-88
lines changed

5 files changed

+69
-88
lines changed

kotlin-sdk-test/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ kotlin {
1515
implementation(kotlin("test"))
1616
implementation(libs.ktor.server.test.host)
1717
implementation(libs.kotlinx.coroutines.test)
18+
implementation(libs.kotest.assertions.json)
1819
}
1920
}
2021
jvmTest {

kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/ToolEdgeCasesTest.kt

Lines changed: 25 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities
77
import io.modelcontextprotocol.kotlin.sdk.TextContent
88
import io.modelcontextprotocol.kotlin.sdk.Tool
99
import io.modelcontextprotocol.kotlin.sdk.integration.utils.TestUtils.assertCallToolResult
10-
import io.modelcontextprotocol.kotlin.sdk.integration.utils.TestUtils.assertJsonProperty
10+
import io.modelcontextprotocol.kotlin.sdk.integration.utils.TestUtils.assertJsonEquals
1111
import io.modelcontextprotocol.kotlin.sdk.integration.utils.TestUtils.assertTextContent
1212
import io.modelcontextprotocol.kotlin.sdk.integration.utils.TestUtils.runTest
1313
import kotlinx.coroutines.delay
@@ -291,7 +291,8 @@ class ToolEdgeCasesTest : KotlinTestBase() {
291291
assertTextContent(toolResult.content.firstOrNull(), "Echo: $testText")
292292

293293
val structuredContent = toolResult.structuredContent as JsonObject
294-
assertJsonProperty(structuredContent, "result", testText)
294+
val expected = buildJsonObject { put("result", testText) }
295+
assertJsonEquals(expected, structuredContent)
295296
}
296297

297298
@Test
@@ -342,18 +343,19 @@ class ToolEdgeCasesTest : KotlinTestBase() {
342343
assertTrue(text.contains("option3"), "Result should contain option3")
343344

344345
val structuredContent = toolResult.structuredContent as JsonObject
345-
assertJsonProperty(structuredContent, "name", "John Doe")
346-
assertJsonProperty(structuredContent, "age", 30)
347-
348-
val address = structuredContent["address"] as? JsonObject
349-
assertNotNull(address, "Address should be present in structured content")
350-
assertJsonProperty(address, "street", "123 Main St")
351-
assertJsonProperty(address, "city", "New York")
352-
assertJsonProperty(address, "country", "USA")
353-
354-
val options = structuredContent["options"] as? JsonArray
355-
assertNotNull(options, "Options should be present in structured content")
356-
assertEquals(3, options.size, "Options should have 3 items")
346+
val expectedStructured = buildJsonObject {
347+
put("name", "John Doe")
348+
put("age", 30)
349+
put("address", buildJsonObject {
350+
put("street", "123 Main St")
351+
put("city", "New York")
352+
put("country", "USA")
353+
})
354+
put("options", buildJsonArray {
355+
add("option1"); add("option2"); add("option3")
356+
})
357+
}
358+
assertJsonEquals(expectedStructured, structuredContent)
357359
}
358360

359361
@Test
@@ -371,7 +373,8 @@ class ToolEdgeCasesTest : KotlinTestBase() {
371373
assertEquals(10000, text.length, "Response should be 10KB in size")
372374

373375
val structuredContent = toolResult.structuredContent as JsonObject
374-
assertJsonProperty(structuredContent, "size", 10000)
376+
val expected = buildJsonObject { put("size", 10000) }
377+
assertJsonEquals(expected, structuredContent)
375378
}
376379

377380
@Test
@@ -392,7 +395,8 @@ class ToolEdgeCasesTest : KotlinTestBase() {
392395
assertTrue(endTime - startTime >= delay, "Tool should take at least the specified delay")
393396

394397
val structuredContent = toolResult.structuredContent as JsonObject
395-
assertJsonProperty(structuredContent, "delay", delay)
398+
val expected = buildJsonObject { put("delay", delay) }
399+
assertJsonEquals(expected, structuredContent)
396400
}
397401

398402
@Test
@@ -409,10 +413,11 @@ class ToolEdgeCasesTest : KotlinTestBase() {
409413
assertTrue(text.contains(specialCharsContent), "Result should contain the special characters")
410414

411415
val structuredContent = toolResult.structuredContent as JsonObject
412-
val special = structuredContent["special"]?.toString()?.trim('"')
413-
414-
assertNotNull(special, "Special characters should be in structured content")
415-
assertTrue(text.contains(specialCharsContent), "Special characters should be in the content")
416+
val expected = buildJsonObject {
417+
put("special", specialCharsContent)
418+
put("length", specialCharsContent.length)
419+
}
420+
assertJsonEquals(expected, structuredContent)
416421
}
417422

418423
@Test

kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/ToolIntegrationTest.kt

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities
77
import io.modelcontextprotocol.kotlin.sdk.TextContent
88
import io.modelcontextprotocol.kotlin.sdk.Tool
99
import io.modelcontextprotocol.kotlin.sdk.integration.utils.TestUtils.assertCallToolResult
10-
import io.modelcontextprotocol.kotlin.sdk.integration.utils.TestUtils.assertJsonProperty
10+
import io.modelcontextprotocol.kotlin.sdk.integration.utils.TestUtils.assertJsonEquals
1111
import io.modelcontextprotocol.kotlin.sdk.integration.utils.TestUtils.assertTextContent
1212
import io.modelcontextprotocol.kotlin.sdk.integration.utils.TestUtils.runTest
1313
import kotlinx.coroutines.runBlocking
@@ -324,7 +324,8 @@ class ToolIntegrationTest : KotlinTestBase() {
324324
assertTextContent(toolResult.content.firstOrNull(), "Echo: $testText")
325325

326326
val structuredContent = toolResult.structuredContent as JsonObject
327-
assertJsonProperty(structuredContent, "result", testText)
327+
val expected = buildJsonObject { put("result", testText) }
328+
assertJsonEquals(expected, structuredContent)
328329
}
329330

330331
@Test
@@ -362,18 +363,18 @@ class ToolIntegrationTest : KotlinTestBase() {
362363
assertTrue(contentText.contains("11"), "Result should contain result value")
363364

364365
val structuredContent = toolResult.structuredContent as JsonObject
365-
assertJsonProperty(structuredContent, "operation", "multiply")
366-
assertJsonProperty(structuredContent, "result", 11.0)
367-
368-
val formattedResult = structuredContent["formattedResult"]?.toString()?.trim('"') ?: ""
369-
assertTrue(
370-
formattedResult == "11.000" || formattedResult == "11,000",
371-
"Formatted result should be either '11.000' or '11,000', but was '$formattedResult'",
372-
)
373-
assertJsonProperty(structuredContent, "precision", 3)
366+
val actualWithoutFormatted = buildJsonObject {
367+
structuredContent.filterKeys { it != "formattedResult" && it != "tags" }.forEach { (k, v) -> put(k, v) }
368+
}
369+
val expectedWithoutFormatted = buildJsonObject {
370+
put("operation", "multiply")
371+
put("a", 5.5)
372+
put("b", 2.0)
373+
put("result", 11.0)
374+
put("precision", 3)
375+
}
374376

375-
val tags = structuredContent["tags"] as? JsonArray
376-
assertNotNull(tags, "Tags should be present")
377+
assertJsonEquals(expectedWithoutFormatted, actualWithoutFormatted)
377378
}
378379
}
379380

@@ -386,7 +387,11 @@ class ToolIntegrationTest : KotlinTestBase() {
386387
assertTextContent(successToolResult.content.firstOrNull(), "No error occurred")
387388

388389
val noErrorStructured = successToolResult.structuredContent as JsonObject
389-
assertJsonProperty(noErrorStructured, "error", false)
390+
val expectedNoError = buildJsonObject {
391+
put("error", false)
392+
put("message", "Success")
393+
}
394+
assertJsonEquals(expectedNoError, noErrorStructured)
390395

391396
val errorArgs = mapOf(
392397
"errorType" to "error",
@@ -398,8 +403,11 @@ class ToolIntegrationTest : KotlinTestBase() {
398403
assertTextContent(errorToolResult.content.firstOrNull(), "Error: Custom error message")
399404

400405
val errorStructured = errorToolResult.structuredContent as JsonObject
401-
assertJsonProperty(errorStructured, "error", true)
402-
assertJsonProperty(errorStructured, "message", "Custom error message")
406+
val expectedError = buildJsonObject {
407+
put("error", true)
408+
put("message", "Custom error message")
409+
}
410+
assertJsonEquals(expectedError, errorStructured)
403411

404412
val exceptionArgs = mapOf(
405413
"errorType" to "exception",
@@ -450,8 +458,11 @@ class ToolIntegrationTest : KotlinTestBase() {
450458
assertTrue(imageContent.data.isNotEmpty(), "Image data should not be empty")
451459

452460
val structuredContent = toolResult.structuredContent as JsonObject
453-
assertJsonProperty(structuredContent, "text", testText)
454-
assertJsonProperty(structuredContent, "includeImage", true)
461+
val expectedStructured = buildJsonObject {
462+
put("text", testText)
463+
put("includeImage", true)
464+
}
465+
assertJsonEquals(expectedStructured, structuredContent)
455466

456467
val textOnlyArgs = mapOf(
457468
"text" to testText,

kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/TypeScriptEdgeCasesTest.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ class TypeScriptEdgeCasesTest : TypeScriptTestBase() {
4343

4444
@Test
4545
@Timeout(30, unit = TimeUnit.SECONDS)
46-
fun testErrorHandling() = runTest {
46+
fun testInvalidURL() = runTest {
4747
val nonExistentToolCommand = "npx tsx myClient.ts $serverUrl non-existent-tool"
4848
val nonExistentToolOutput = executeCommandAllowingFailure(nonExistentToolCommand, tsClientDir)
4949

@@ -56,7 +56,9 @@ class TypeScriptEdgeCasesTest : TypeScriptTestBase() {
5656
val invalidUrlOutput = executeCommandAllowingFailure(invalidUrlCommand, tsClientDir)
5757

5858
assertTrue(
59-
invalidUrlOutput.contains("Invalid URL") && invalidUrlOutput.contains("ERR_INVALID_URL"),
59+
invalidUrlOutput.contains("Invalid URL") ||
60+
invalidUrlOutput.contains("ERR_INVALID_URL") ||
61+
invalidUrlOutput.contains("ECONNREFUSED"),
6062
"Client should handle connection errors gracefully",
6163
)
6264
}

kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/utils/TestUtils.kt

Lines changed: 10 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@ import io.modelcontextprotocol.kotlin.sdk.TextContent
66
import kotlinx.coroutines.Dispatchers
77
import kotlinx.coroutines.runBlocking
88
import kotlinx.coroutines.withContext
9+
import kotlinx.serialization.json.JsonElement
910
import kotlinx.serialization.json.JsonObject
1011
import kotlinx.serialization.json.JsonPrimitive
12+
import io.kotest.assertions.json.*
1113
import kotlin.test.assertEquals
1214
import kotlin.test.assertNotNull
1315
import kotlin.test.assertTrue
@@ -35,57 +37,17 @@ object TestUtils {
3537
return result
3638
}
3739

38-
/**
39-
* Asserts that a JSON property has the expected string value.
40-
*/
41-
fun assertJsonProperty(
42-
json: JsonObject,
43-
property: String,
44-
expectedValue: String,
45-
message: String = "",
46-
) {
47-
assertEquals(expectedValue, json[property]?.toString()?.trim('"'), "${message}$property should match")
40+
// Use Kotest JSON assertions to compare whole JSON structures.
41+
fun assertJsonEquals(expectedJson: String, actual: JsonElement, message: String = "") {
42+
val prefix = if (message.isNotEmpty()) "$message\n" else ""
43+
(actual.toString()).shouldEqualJson(prefix + expectedJson)
4844
}
4945

50-
/**
51-
* Asserts that a JSON property has the expected numeric value.
52-
*/
53-
fun assertJsonProperty(
54-
json: JsonObject,
55-
property: String,
56-
expectedValue: Number,
57-
message: String = "",
58-
) {
59-
when (expectedValue) {
60-
is Int -> assertEquals(
61-
expectedValue,
62-
(json[property] as? JsonPrimitive)?.content?.toIntOrNull(),
63-
"${message}$property should match",
64-
)
65-
66-
is Double -> assertEquals(
67-
expectedValue,
68-
(json[property] as? JsonPrimitive)?.content?.toDoubleOrNull(),
69-
"${message}$property should match",
70-
)
71-
72-
else -> assertEquals(
73-
expectedValue.toString(),
74-
json[property]?.toString()?.trim('"'),
75-
"${message}$property should match",
76-
)
77-
}
46+
fun assertJsonEquals(expected: JsonElement, actual: JsonElement) {
47+
(actual.toString()).shouldEqualJson(expected.toString())
7848
}
7949

80-
/**
81-
* Asserts that a JSON property has the expected boolean value.
82-
*/
83-
fun assertJsonProperty(
84-
json: JsonObject,
85-
property: String,
86-
expectedValue: Boolean,
87-
message: String = "",
88-
) {
89-
assertEquals(expectedValue.toString(), json[property].toString(), "${message}$property should match")
50+
fun assertIsJsonArray(actual: JsonElement) {
51+
actual.toString().shouldBeJsonArray()
9052
}
9153
}

0 commit comments

Comments
 (0)