Skip to content

Commit 3aebe02

Browse files
committed
Refactor TypeScript integration tests
- Use dynamic random ports for server setup - Add `Awaitility` for reliable server readiness checks and improved concurrency handling - Update test server start logic to return resolved port - Replace `StringBuilder` with `StringBuffer` - Remove redundant port discovery and wait logic
1 parent 9a70cea commit 3aebe02

File tree

6 files changed

+42
-30
lines changed

6 files changed

+42
-30
lines changed

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

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,12 @@ import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities
1212
import io.modelcontextprotocol.kotlin.sdk.client.Client
1313
import io.modelcontextprotocol.kotlin.sdk.client.SseClientTransport
1414
import io.modelcontextprotocol.kotlin.sdk.integration.utils.Retry
15+
import io.modelcontextprotocol.kotlin.sdk.integration.utils.port
1516
import io.modelcontextprotocol.kotlin.sdk.server.Server
1617
import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions
1718
import io.modelcontextprotocol.kotlin.sdk.server.mcp
1819
import kotlinx.coroutines.runBlocking
1920
import kotlinx.coroutines.withTimeout
20-
import org.awaitility.kotlin.await
2121
import org.junit.jupiter.api.AfterEach
2222
import org.junit.jupiter.api.BeforeEach
2323
import kotlin.time.Duration.Companion.seconds
@@ -40,12 +40,7 @@ abstract class KotlinTestBase {
4040
@BeforeEach
4141
fun setUp() {
4242
setupServer()
43-
await
44-
.ignoreExceptions()
45-
.until {
46-
port = runBlocking { serverEngine.engine.resolvedConnectors().first().port }
47-
port != 0
48-
}
43+
port = serverEngine.port()
4944
runBlocking {
5045
setupClient()
5146
}
@@ -74,7 +69,7 @@ abstract class KotlinTestBase {
7469

7570
configureServer()
7671

77-
serverEngine = embeddedServer(ServerCIO, host = host, port = port) {
72+
serverEngine = embeddedServer(ServerCIO, host = host, port = 0) {
7873
install(ServerSSE)
7974
routing {
8075
mcp { server }

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

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities
3737
import io.modelcontextprotocol.kotlin.sdk.TextContent
3838
import io.modelcontextprotocol.kotlin.sdk.TextResourceContents
3939
import io.modelcontextprotocol.kotlin.sdk.Tool
40+
import io.modelcontextprotocol.kotlin.sdk.integration.utils.port
4041
import io.modelcontextprotocol.kotlin.sdk.server.Server
4142
import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions
4243
import io.modelcontextprotocol.kotlin.sdk.shared.AbstractTransport
@@ -65,10 +66,10 @@ class KotlinServerForTypeScriptClient {
6566
private val jsonFormat = Json { ignoreUnknownKeys = true }
6667
private var server: EmbeddedServer<*, *>? = null
6768

68-
fun start(port: Int = 3000) {
69-
logger.info { "Starting HTTP server on port $port" }
69+
fun start(): Int {
70+
logger.info { "Starting HTTP server on random port" }
7071

71-
server = embeddedServer(CIO, port = port) {
72+
server = embeddedServer(CIO, port = 0) {
7273
routing {
7374
get("/mcp") {
7475
val sessionId = call.request.header("mcp-session-id")
@@ -186,7 +187,9 @@ class KotlinServerForTypeScriptClient {
186187
}
187188
}
188189

189-
server?.start(wait = false)
190+
val theServer = requireNotNull(server) { "Server must be created" }
191+
theServer.start(wait = false)
192+
return theServer.port()
190193
}
191194

192195
fun stop() {
@@ -357,6 +360,8 @@ class HttpServerTransport(private val sessionId: String) : AbstractTransport() {
357360
write("\n\n")
358361
flush()
359362
}
363+
} catch (e: CancellationException) {
364+
logger.info(e) { e.message }
360365
} catch (e: Exception) {
361366
logger.warn(e) { "SSE stream terminated for session: $sessionId" }
362367
} finally {

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

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,26 @@
11
package io.modelcontextprotocol.kotlin.sdk.integration.typescript
22

33
import kotlinx.coroutines.test.runTest
4+
import org.awaitility.kotlin.await
45
import org.junit.jupiter.api.AfterEach
56
import org.junit.jupiter.api.BeforeEach
67
import org.junit.jupiter.api.Test
78
import org.junit.jupiter.api.Timeout
89
import java.util.concurrent.TimeUnit
910
import kotlin.test.assertTrue
11+
import kotlin.time.Duration.Companion.milliseconds
12+
import kotlin.time.toJavaDuration
1013

1114
class TypeScriptClientKotlinServerTest : TypeScriptTestBase() {
1215

13-
private var port: Int = 0
1416
private lateinit var serverUrl: String
1517
private var httpServer: KotlinServerForTypeScriptClient? = null
1618

1719
@BeforeEach
1820
fun setUp() {
19-
port = findFreePort()
20-
serverUrl = "http://localhost:$port/mcp"
21-
killProcessOnPort(port)
2221
httpServer = KotlinServerForTypeScriptClient()
23-
httpServer?.start(port)
24-
if (!waitForPort(port = port)) {
25-
throw IllegalStateException("Kotlin test server did not become ready on localhost:$port within timeout")
26-
}
22+
val port = httpServer!!.start()
23+
serverUrl = "http://localhost:$port/mcp"
2724
println("Kotlin server started on port $port")
2825
}
2926

@@ -143,10 +140,13 @@ class TypeScriptClientKotlinServerTest : TypeScriptTestBase() {
143140
}
144141
threads.add(thread)
145142
thread.start()
146-
Thread.sleep(500)
147143
}
148144

149-
threads.forEach { it.join() }
145+
await
146+
.pollInterval(100.milliseconds.toJavaDuration())
147+
.until {
148+
outputs.size == clientCount
149+
}
150150

151151
if (exceptions.isNotEmpty()) {
152152
println(

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

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,9 @@ class TypeScriptEdgeCasesTest : TypeScriptTestBase() {
2020

2121
@BeforeEach
2222
fun setUp() {
23-
port = findFreePort()
24-
serverUrl = "http://localhost:$port/mcp"
25-
killProcessOnPort(port)
2623
httpServer = KotlinServerForTypeScriptClient()
27-
httpServer?.start(port)
28-
if (!waitForPort(port = port)) {
29-
throw IllegalStateException("Kotlin test server did not become ready on localhost:$port within timeout")
30-
}
24+
port = httpServer!!.start()
25+
serverUrl = "http://localhost:$port/mcp"
3126
println("Kotlin server started on port $port")
3227
}
3328

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ abstract class TypeScriptTestBase {
109109
.redirectErrorStream(true)
110110
.start()
111111

112-
val output = StringBuilder()
112+
val output = StringBuffer()
113113
BufferedReader(InputStreamReader(process.inputStream)).use { reader ->
114114
var line: String?
115115
while (reader.readLine().also { line = it } != null) {
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package io.modelcontextprotocol.kotlin.sdk.integration.utils
2+
3+
import io.ktor.server.engine.EmbeddedServer
4+
import kotlinx.coroutines.runBlocking
5+
import org.awaitility.kotlin.await
6+
7+
internal fun EmbeddedServer<*, *>.port(): Int {
8+
var port = 0
9+
val server = this
10+
await
11+
.ignoreExceptions()
12+
.until {
13+
port = runBlocking { server.engine.resolvedConnectors().first().port }
14+
port != 0
15+
}
16+
return port
17+
}

0 commit comments

Comments
 (0)