Skip to content

Commit ee7b6b5

Browse files
committed
fixup! Introduce Kotlin integration tests
1 parent 1c5f127 commit ee7b6b5

File tree

3 files changed

+193
-197
lines changed

3 files changed

+193
-197
lines changed

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

Lines changed: 109 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -70,172 +70,156 @@ class KotlinClientTypeScriptServerEdgeCasesTest : TypeScriptTestBase() {
7070
@Test
7171
@Timeout(30, unit = TimeUnit.SECONDS)
7272
fun testNonExistentTool() = runTest {
73-
client = HttpClient(CIO) {
74-
install(SSE)
75-
}.mcpStreamableHttp(serverUrl)
73+
withClient(serverUrl) { client ->
74+
val nonExistentToolName = "non-existent-tool"
75+
val arguments = mapOf("name" to "TestUser")
7676

77-
val nonExistentToolName = "non-existent-tool"
78-
val arguments = mapOf("name" to "TestUser")
77+
val exception = assertThrows<IllegalStateException> {
78+
client.callTool(nonExistentToolName, arguments)
79+
}
7980

80-
val exception = assertThrows<IllegalStateException> {
81-
client.callTool(nonExistentToolName, arguments)
81+
val expectedMessage =
82+
"JSONRPCError(code=InvalidParams, message=MCP error -32602: Tool non-existent-tool not found, data={})"
83+
assertEquals(
84+
expectedMessage,
85+
exception.message,
86+
"Unexpected error message for non-existent tool",
87+
)
8288
}
83-
84-
val expectedMessage =
85-
"JSONRPCError(code=InvalidParams, message=MCP error -32602: Tool non-existent-tool not found, data={})"
86-
assertEquals(
87-
expectedMessage,
88-
exception.message,
89-
"Unexpected error message for non-existent tool",
90-
)
9189
}
9290

9391
@Test
9492
@Timeout(30, unit = TimeUnit.SECONDS)
9593
fun testSpecialCharactersInArguments() = runTest {
96-
client = HttpClient(CIO) {
97-
install(SSE)
98-
}.mcpStreamableHttp(serverUrl)
99-
100-
val specialChars = "!@#$%^&*()_+{}[]|\\:;\"'<>,.?/"
101-
val arguments = mapOf("name" to specialChars)
94+
withClient(serverUrl) { client ->
95+
val specialChars = "!@#$%^&*()_+{}[]|\\:;\"'<>,.?/"
96+
val arguments = mapOf("name" to specialChars)
10297

103-
val result = client.callTool("greet", arguments)
104-
assertNotNull(result, "Tool call result should not be null")
98+
val result = client.callTool("greet", arguments)
99+
assertNotNull(result, "Tool call result should not be null")
105100

106-
val callResult = result as CallToolResult
107-
val textContent = callResult.content.firstOrNull { it is TextContent } as? TextContent
108-
assertNotNull(textContent, "Text content should be present in the result")
101+
val callResult = result as CallToolResult
102+
val textContent = callResult.content.firstOrNull { it is TextContent } as? TextContent
103+
assertNotNull(textContent, "Text content should be present in the result")
109104

110-
val text = textContent.text ?: ""
111-
assertTrue(
112-
text.contains(specialChars),
113-
"Tool response should contain the special characters",
114-
)
105+
val text = textContent.text ?: ""
106+
assertTrue(
107+
text.contains(specialChars),
108+
"Tool response should contain the special characters",
109+
)
110+
}
115111
}
116112

117113
@Test
118114
@Timeout(30, unit = TimeUnit.SECONDS)
119115
fun testLargePayload() = runTest {
120-
client = HttpClient(CIO) {
121-
install(SSE)
122-
}.mcpStreamableHttp(serverUrl)
116+
withClient(serverUrl) { client ->
117+
val largeName = "A".repeat(10 * 1024)
118+
val arguments = mapOf("name" to largeName)
123119

124-
val largeName = "A".repeat(10 * 1024)
125-
val arguments = mapOf("name" to largeName)
126-
127-
val result = client.callTool("greet", arguments)
128-
assertNotNull(result, "Tool call result should not be null")
120+
val result = client.callTool("greet", arguments)
121+
assertNotNull(result, "Tool call result should not be null")
129122

130-
val callResult = result as CallToolResult
131-
val textContent = callResult.content.firstOrNull { it is TextContent } as? TextContent
132-
assertNotNull(textContent, "Text content should be present in the result")
123+
val callResult = result as CallToolResult
124+
val textContent = callResult.content.firstOrNull { it is TextContent } as? TextContent
125+
assertNotNull(textContent, "Text content should be present in the result")
133126

134-
val text = textContent.text ?: ""
135-
assertTrue(
136-
text.contains("Hello,") && text.contains("A"),
137-
"Tool response should contain the greeting with the large name",
138-
)
127+
val text = textContent.text ?: ""
128+
assertTrue(
129+
text.contains("Hello,") && text.contains("A"),
130+
"Tool response should contain the greeting with the large name",
131+
)
132+
}
139133
}
140134

141135
@Test
142136
@Timeout(60, unit = TimeUnit.SECONDS)
143137
fun testConcurrentRequests() = runTest {
144-
client = HttpClient(CIO) {
145-
install(SSE)
146-
}.mcpStreamableHttp(serverUrl)
147-
148-
val concurrentCount = 5
149-
val results = mutableListOf<Deferred<String>>()
150-
151-
for (i in 1..concurrentCount) {
152-
runBlocking {
153-
val deferred = async {
154-
val name = "ConcurrentClient$i"
155-
val arguments = mapOf("name" to name)
156-
157-
val result = client.callTool("greet", arguments)
158-
assertNotNull(result, "Tool call result should not be null for client $i")
159-
160-
val callResult = result as CallToolResult
161-
val textContent = callResult.content.firstOrNull { it is TextContent } as? TextContent
162-
assertNotNull(textContent, "Text content should be present for client $i")
163-
164-
textContent.text ?: ""
138+
withClient(serverUrl) { client ->
139+
val concurrentCount = 5
140+
val responses = kotlinx.coroutines.coroutineScope {
141+
val results = (1..concurrentCount).map { i ->
142+
async {
143+
val name = "ConcurrentClient$i"
144+
val arguments = mapOf("name" to name)
145+
146+
val result = client.callTool("greet", arguments)
147+
assertNotNull(result, "Tool call result should not be null for client $i")
148+
149+
val callResult = result as CallToolResult
150+
val textContent = callResult.content.firstOrNull { it is TextContent } as? TextContent
151+
assertNotNull(textContent, "Text content should be present for client $i")
152+
153+
textContent.text ?: ""
154+
}
165155
}
166-
results.add(deferred)
156+
results.awaitAll()
167157
}
168-
}
169158

170-
val responses = results.awaitAll()
171-
172-
for (i in 1..concurrentCount) {
173-
val expectedName = "ConcurrentClient$i"
174-
val matchingResponses = responses.filter { it.contains("Hello, $expectedName!") }
175-
assertEquals(
176-
1,
177-
matchingResponses.size,
178-
"Should have exactly one response for $expectedName",
179-
)
159+
for (i in 1..concurrentCount) {
160+
val expectedName = "ConcurrentClient$i"
161+
val matchingResponses = responses.filter { it.contains("Hello, $expectedName!") }
162+
assertEquals(
163+
1,
164+
matchingResponses.size,
165+
"Should have exactly one response for $expectedName",
166+
)
167+
}
180168
}
181169
}
182170

183171
@Test
184172
@Timeout(30, unit = TimeUnit.SECONDS)
185173
fun testInvalidArguments() = runTest {
186-
client = HttpClient(CIO) {
187-
install(SSE)
188-
}.mcpStreamableHttp(serverUrl)
174+
withClient(serverUrl) { client ->
175+
val invalidArguments = mapOf(
176+
"name" to JsonObject(mapOf("nested" to JsonPrimitive("value"))),
177+
)
189178

190-
val invalidArguments = mapOf(
191-
"name" to JsonObject(mapOf("nested" to JsonPrimitive("value"))),
192-
)
179+
val exception = assertThrows<IllegalStateException> {
180+
client.callTool("greet", invalidArguments)
181+
}
193182

194-
val exception = assertThrows<IllegalStateException> {
195-
client.callTool("greet", invalidArguments)
183+
val msg = exception.message ?: ""
184+
val expectedMessage = """
185+
JSONRPCError(code=InvalidParams, message=MCP error -32602: Invalid arguments for tool greet: [
186+
{
187+
"code": "invalid_type",
188+
"expected": "string",
189+
"received": "object",
190+
"path": [
191+
"name"
192+
],
193+
"message": "Expected string, received object"
194+
}
195+
], data={})
196+
""".trimIndent()
197+
198+
assertEquals(expectedMessage, msg, "Unexpected error message for invalid arguments")
196199
}
197-
198-
val msg = exception.message ?: ""
199-
val expectedMessage = """
200-
JSONRPCError(code=InvalidParams, message=MCP error -32602: Invalid arguments for tool greet: [
201-
{
202-
"code": "invalid_type",
203-
"expected": "string",
204-
"received": "object",
205-
"path": [
206-
"name"
207-
],
208-
"message": "Expected string, received object"
209-
}
210-
], data={})
211-
""".trimIndent()
212-
213-
assertEquals(expectedMessage, msg, "Unexpected error message for invalid arguments")
214200
}
215201

216202
@Test
217203
@Timeout(30, unit = TimeUnit.SECONDS)
218204
fun testMultipleToolCalls() = runTest {
219-
client = HttpClient(CIO) {
220-
install(SSE)
221-
}.mcpStreamableHttp(serverUrl)
222-
223-
repeat(10) { i ->
224-
val name = "SequentialClient$i"
225-
val arguments = mapOf("name" to name)
226-
227-
val result = client.callTool("greet", arguments)
228-
assertNotNull(result, "Tool call result should not be null for call $i")
229-
230-
val callResult = result as CallToolResult
231-
val textContent = callResult.content.firstOrNull { it is TextContent } as? TextContent
232-
assertNotNull(textContent, "Text content should be present for call $i")
233-
234-
assertEquals(
235-
"Hello, $name!",
236-
textContent.text,
237-
"Tool response should contain the greeting with the provided name",
238-
)
205+
withClient(serverUrl) { client ->
206+
repeat(10) { i ->
207+
val name = "SequentialClient$i"
208+
val arguments = mapOf("name" to name)
209+
210+
val result = client.callTool("greet", arguments)
211+
assertNotNull(result, "Tool call result should not be null for call $i")
212+
213+
val callResult = result as CallToolResult
214+
val textContent = callResult.content.firstOrNull { it is TextContent } as? TextContent
215+
assertNotNull(textContent, "Text content should be present for call $i")
216+
217+
assertEquals(
218+
"Hello, $name!",
219+
textContent.text,
220+
"Tool response should contain the greeting with the provided name",
221+
)
222+
}
239223
}
240224
}
241225
}

0 commit comments

Comments
 (0)