add ios and wasm targets (#81)
* add wasm and ios targets * add yarn.lock file to gitignore * clean up code * fix typo in method name * add wasm server example * update jrelease publishing for klibs * update gradle-publish workflow to use macos runner * update README to reflect multiplatform support and clarify server setup for JVM and WASM * remove the old main file from the kotlin-mcp-server example * update api dump * Refactor `build.gradle.kts` * clean up imports and remove unnecessary charset specification in tests * add JS target configuration and update API dump
This commit is contained in:
parent
7ce665b931
commit
0cff2ca718
12 changed files with 705 additions and 478 deletions
|
|
@ -1,46 +0,0 @@
|
|||
import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage
|
||||
import io.modelcontextprotocol.kotlin.sdk.shared.AbstractTransport
|
||||
|
||||
/**
|
||||
* In-memory transport for creating clients and servers that talk to each other within the same process.
|
||||
*/
|
||||
class InMemoryTransport : AbstractTransport() {
|
||||
private var otherTransport: InMemoryTransport? = null
|
||||
private val messageQueue: MutableList<JSONRPCMessage> = mutableListOf()
|
||||
|
||||
/**
|
||||
* Creates a pair of linked in-memory transports that can communicate with each other.
|
||||
* One should be passed to a Client and one to a Server.
|
||||
*/
|
||||
companion object {
|
||||
fun createLinkedPair(): Pair<InMemoryTransport, InMemoryTransport> {
|
||||
val clientTransport = InMemoryTransport()
|
||||
val serverTransport = InMemoryTransport()
|
||||
clientTransport.otherTransport = serverTransport
|
||||
serverTransport.otherTransport = clientTransport
|
||||
return Pair(clientTransport, serverTransport)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun start() {
|
||||
// Process any messages that were queued before start was called
|
||||
while (messageQueue.isNotEmpty()) {
|
||||
messageQueue.removeFirstOrNull()?.let { message ->
|
||||
_onMessage.invoke(message) // todo?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun close() {
|
||||
val other = otherTransport
|
||||
otherTransport = null
|
||||
other?.close()
|
||||
_onClose.invoke()
|
||||
}
|
||||
|
||||
override suspend fun send(message: JSONRPCMessage) {
|
||||
val other = otherTransport ?: throw IllegalStateException("Not connected")
|
||||
|
||||
other._onMessage.invoke(message)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,62 +0,0 @@
|
|||
package client
|
||||
|
||||
import io.modelcontextprotocol.kotlin.sdk.InitializedNotification
|
||||
import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage
|
||||
import io.modelcontextprotocol.kotlin.sdk.PingRequest
|
||||
import io.modelcontextprotocol.kotlin.sdk.shared.Transport
|
||||
import io.modelcontextprotocol.kotlin.sdk.toJSON
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
import kotlin.test.fail
|
||||
|
||||
abstract class BaseTransportTest {
|
||||
protected suspend fun testClientOpenClose(client: Transport) {
|
||||
client.onError { error ->
|
||||
fail("Unexpected error: $error")
|
||||
}
|
||||
|
||||
var didClose = false
|
||||
client.onClose { didClose = true }
|
||||
|
||||
client.start()
|
||||
assertFalse(didClose, "Transport should not be closed immediately after start")
|
||||
|
||||
client.close()
|
||||
assertTrue(didClose, "Transport should be closed after close() call")
|
||||
}
|
||||
|
||||
protected suspend fun testClientRead(client: Transport) {
|
||||
client.onError { error ->
|
||||
error.printStackTrace()
|
||||
fail("Unexpected error: $error")
|
||||
}
|
||||
|
||||
val messages = listOf<JSONRPCMessage>(
|
||||
PingRequest().toJSON(), InitializedNotification().toJSON()
|
||||
)
|
||||
|
||||
val readMessages = mutableListOf<JSONRPCMessage>()
|
||||
val finished = CompletableDeferred<Unit>()
|
||||
|
||||
client.onMessage { message ->
|
||||
readMessages.add(message)
|
||||
if (message == messages.last()) {
|
||||
finished.complete(Unit)
|
||||
}
|
||||
}
|
||||
|
||||
client.start()
|
||||
|
||||
for (message in messages) {
|
||||
client.send(message)
|
||||
}
|
||||
|
||||
finished.await()
|
||||
|
||||
assertEquals(messages, readMessages, "Assert messages received")
|
||||
|
||||
client.close()
|
||||
}
|
||||
}
|
||||
|
|
@ -5,9 +5,9 @@ import io.modelcontextprotocol.kotlin.sdk.CreateMessageRequest
|
|||
import io.modelcontextprotocol.kotlin.sdk.CreateMessageResult
|
||||
import io.modelcontextprotocol.kotlin.sdk.EmptyJsonObject
|
||||
import io.modelcontextprotocol.kotlin.sdk.Implementation
|
||||
import InMemoryTransport
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.spyk
|
||||
import io.modelcontextprotocol.kotlin.sdk.InMemoryTransport
|
||||
import io.modelcontextprotocol.kotlin.sdk.InitializeRequest
|
||||
import io.modelcontextprotocol.kotlin.sdk.InitializeResult
|
||||
import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage
|
||||
|
|
@ -486,10 +486,10 @@ class ClientTest {
|
|||
|
||||
server.setRequestHandler<ListResourcesRequest>(Method.Defined.ResourcesList) { _, extra ->
|
||||
// Simulate a delayed response
|
||||
// Wait ~100ms unless cancelled
|
||||
// Wait ~100ms unless canceled
|
||||
try {
|
||||
kotlinx.coroutines.withTimeout(100L) {
|
||||
// Just delay here, if timeout is 0 on client side this won't return in time
|
||||
// Just delay here, if timeout is 0 on the client side, this won't return in time
|
||||
kotlinx.coroutines.delay(100)
|
||||
}
|
||||
} catch (_: Exception) {
|
||||
|
|
|
|||
|
|
@ -1,122 +0,0 @@
|
|||
package client
|
||||
|
||||
import InMemoryTransport
|
||||
import io.modelcontextprotocol.kotlin.sdk.InitializedNotification
|
||||
import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import io.modelcontextprotocol.kotlin.sdk.toJSON
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.assertThrows
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNotNull
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class InMemoryTransportTest {
|
||||
private lateinit var clientTransport: InMemoryTransport
|
||||
private lateinit var serverTransport: InMemoryTransport
|
||||
|
||||
@BeforeEach
|
||||
fun setUp() {
|
||||
val (client, server) = InMemoryTransport.createLinkedPair()
|
||||
clientTransport = client
|
||||
serverTransport = server
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should create linked pair`() {
|
||||
assertNotNull(clientTransport)
|
||||
assertNotNull(serverTransport)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should start without error`() {
|
||||
runBlocking {
|
||||
clientTransport.start()
|
||||
serverTransport.start()
|
||||
// If no exception is thrown, the test passes
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should send message from client to server`() {
|
||||
runBlocking {
|
||||
val message = InitializedNotification()
|
||||
|
||||
var receivedMessage: JSONRPCMessage? = null
|
||||
serverTransport.onMessage { msg ->
|
||||
receivedMessage = msg
|
||||
}
|
||||
|
||||
val rpcNotification = message.toJSON()
|
||||
clientTransport.send(rpcNotification)
|
||||
assertEquals(rpcNotification, receivedMessage)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should send message from server to client`() {
|
||||
runBlocking {
|
||||
val message = InitializedNotification()
|
||||
.toJSON()
|
||||
|
||||
var receivedMessage: JSONRPCMessage? = null
|
||||
clientTransport.onMessage { msg ->
|
||||
receivedMessage = msg
|
||||
}
|
||||
|
||||
serverTransport.send(message)
|
||||
assertEquals(message, receivedMessage)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should handle close`() {
|
||||
runBlocking {
|
||||
var clientClosed = false
|
||||
var serverClosed = false
|
||||
|
||||
clientTransport.onClose {
|
||||
clientClosed = true
|
||||
}
|
||||
|
||||
serverTransport.onClose {
|
||||
serverClosed = true
|
||||
}
|
||||
|
||||
clientTransport.close()
|
||||
assertTrue(clientClosed)
|
||||
assertTrue(serverClosed)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should throw error when sending after close`() {
|
||||
runBlocking {
|
||||
clientTransport.close()
|
||||
|
||||
assertThrows<IllegalStateException> {
|
||||
clientTransport.send(
|
||||
InitializedNotification().toJSON()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should queue messages sent before start`() {
|
||||
runBlocking {
|
||||
val message = InitializedNotification()
|
||||
.toJSON()
|
||||
|
||||
var receivedMessage: JSONRPCMessage? = null
|
||||
serverTransport.onMessage { msg ->
|
||||
receivedMessage = msg
|
||||
}
|
||||
|
||||
clientTransport.send(message)
|
||||
serverTransport.start()
|
||||
assertEquals(message, receivedMessage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,85 +0,0 @@
|
|||
package client
|
||||
|
||||
import io.ktor.client.*
|
||||
import io.ktor.client.plugins.sse.*
|
||||
import io.ktor.server.application.*
|
||||
import io.ktor.server.cio.*
|
||||
import io.ktor.server.engine.*
|
||||
import io.ktor.server.routing.*
|
||||
import io.ktor.server.sse.sse
|
||||
import io.ktor.util.collections.ConcurrentMap
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import io.modelcontextprotocol.kotlin.sdk.client.mcpSseTransport
|
||||
import io.modelcontextprotocol.kotlin.sdk.server.SseServerTransport
|
||||
import io.modelcontextprotocol.kotlin.sdk.server.mcpPostEndpoint
|
||||
import io.modelcontextprotocol.kotlin.sdk.server.mcpSseTransport
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
private const val PORT = 8080
|
||||
|
||||
class SseTransportTest : BaseTransportTest() {
|
||||
@Test
|
||||
fun `should start then close cleanly`() = runTest {
|
||||
val server = embeddedServer(CIO, port = PORT) {
|
||||
install(io.ktor.server.sse.SSE)
|
||||
val transports = ConcurrentMap<String, SseServerTransport>()
|
||||
routing {
|
||||
sse {
|
||||
mcpSseTransport("", transports).start()
|
||||
}
|
||||
|
||||
post {
|
||||
mcpPostEndpoint(transports)
|
||||
}
|
||||
}
|
||||
}.start(wait = false)
|
||||
|
||||
val client = HttpClient {
|
||||
install(SSE)
|
||||
}.mcpSseTransport {
|
||||
url {
|
||||
host = "localhost"
|
||||
port = PORT
|
||||
}
|
||||
}
|
||||
|
||||
testClientOpenClose(client)
|
||||
|
||||
server.stop()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should read messages`() = runTest {
|
||||
val server = embeddedServer(CIO, port = PORT) {
|
||||
install(io.ktor.server.sse.SSE)
|
||||
val transports = ConcurrentMap<String, SseServerTransport>()
|
||||
routing {
|
||||
sse {
|
||||
mcpSseTransport("", transports).apply {
|
||||
onMessage {
|
||||
send(it)
|
||||
}
|
||||
|
||||
start()
|
||||
}
|
||||
}
|
||||
|
||||
post {
|
||||
mcpPostEndpoint(transports)
|
||||
}
|
||||
}
|
||||
}.start(wait = false)
|
||||
|
||||
val client = HttpClient {
|
||||
install(SSE)
|
||||
}.mcpSseTransport {
|
||||
url {
|
||||
host = "localhost"
|
||||
port = PORT
|
||||
}
|
||||
}
|
||||
|
||||
testClientRead(client)
|
||||
server.stop()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package client
|
||||
|
||||
import io.modelcontextprotocol.kotlin.sdk.client.BaseTransportTest
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import io.modelcontextprotocol.kotlin.sdk.client.StdioClientTransport
|
||||
import kotlinx.io.asSink
|
||||
|
|
|
|||
|
|
@ -1,47 +0,0 @@
|
|||
package client
|
||||
|
||||
import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage
|
||||
import io.modelcontextprotocol.kotlin.sdk.Request
|
||||
import org.junit.jupiter.api.Test
|
||||
import io.modelcontextprotocol.kotlin.sdk.shared.McpJson
|
||||
|
||||
class TypesTest {
|
||||
|
||||
@Test
|
||||
fun testRequestResult() {
|
||||
val message = "{\"result\":{\"protocolVersion\":\"2024-11-05\",\"capabilities\":{\"tools\":{\"listChanged\":true},\"resources\":{}},\"serverInfo\":{\"name\":\"jetbrains/proxy\",\"version\":\"0.1.0\"}},\"jsonrpc\":\"2.0\",\"id\":1}"
|
||||
McpJson.decodeFromString<JSONRPCMessage>(message)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRequestError() {
|
||||
val message = "{\"method\":\"initialize\", \"protocolVersion\":\"2024-11-05\",\"capabilities\":{\"experimental\":{},\"sampling\":{}},\"clientInfo\":{\"name\":\"test client\",\"version\":\"1.0\"},\"_meta\":{}}"
|
||||
McpJson.decodeFromString<Request>(message)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testJSONRPCMessage() {
|
||||
val line = "{\"result\":{\"content\":[{\"type\":\"text\"}],\"isError\":false},\"jsonrpc\":\"2.0\",\"id\":4}"
|
||||
McpJson.decodeFromString<JSONRPCMessage>(line)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testJSONRPCMessageWithStringId() {
|
||||
val line = """
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "initialize",
|
||||
"id": "ebf9f64a-0",
|
||||
"params": {
|
||||
"protocolVersion": "2024-11-05",
|
||||
"capabilities": {},
|
||||
"clientInfo": {
|
||||
"name": "mcp-java-client",
|
||||
"version": "0.2.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
McpJson.decodeFromString<JSONRPCMessage>(line)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
package client
|
||||
|
||||
import io.ktor.server.testing.*
|
||||
import io.ktor.server.websocket.*
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import io.modelcontextprotocol.kotlin.sdk.client.mcpWebSocketTransport
|
||||
import org.junit.jupiter.api.Test
|
||||
import io.modelcontextprotocol.kotlin.sdk.server.mcpWebSocket
|
||||
import io.modelcontextprotocol.kotlin.sdk.server.mcpWebSocketTransport
|
||||
import org.junit.jupiter.api.Disabled
|
||||
|
||||
class WebSocketTransportTest : BaseTransportTest() {
|
||||
@Test
|
||||
@Disabled("Test disabled for investigation #17")
|
||||
fun `should start then close cleanly`() = testApplication {
|
||||
install(WebSockets)
|
||||
routing {
|
||||
mcpWebSocket()
|
||||
}
|
||||
|
||||
val client = createClient {
|
||||
install(io.ktor.client.plugins.websocket.WebSockets)
|
||||
}.mcpWebSocketTransport()
|
||||
|
||||
testClientOpenClose(client)
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled("Test disabled for investigation #17")
|
||||
fun `should read messages`() = testApplication {
|
||||
val clientFinished = CompletableDeferred<Unit>()
|
||||
|
||||
install(WebSockets)
|
||||
routing {
|
||||
mcpWebSocketTransport {
|
||||
onMessage {
|
||||
send(it)
|
||||
}
|
||||
|
||||
clientFinished.await()
|
||||
}
|
||||
}
|
||||
|
||||
val client = createClient {
|
||||
install(io.ktor.client.plugins.websocket.WebSockets)
|
||||
}.mcpWebSocketTransport()
|
||||
|
||||
testClientRead(client)
|
||||
|
||||
clientFinished.complete(Unit)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
package integration
|
||||
|
||||
import io.ktor.client.*
|
||||
import io.ktor.client.plugins.sse.*
|
||||
import io.ktor.server.cio.*
|
||||
import io.ktor.server.engine.*
|
||||
import io.modelcontextprotocol.kotlin.sdk.Implementation
|
||||
import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities
|
||||
import io.modelcontextprotocol.kotlin.sdk.client.Client
|
||||
import io.modelcontextprotocol.kotlin.sdk.client.mcpSse
|
||||
import io.modelcontextprotocol.kotlin.sdk.server.Server
|
||||
import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions
|
||||
import io.modelcontextprotocol.kotlin.sdk.server.mcp
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.assertDoesNotThrow
|
||||
import io.ktor.client.engine.cio.CIO as ClientCIO
|
||||
import io.ktor.server.cio.CIO as ServerCIO
|
||||
|
||||
class SseIntegrationTest {
|
||||
@Test
|
||||
fun `client should be able to connect to sse server`() { // runTest will cause network timeout issues
|
||||
runBlocking {
|
||||
val serverEngine = initServer()
|
||||
try {
|
||||
assertDoesNotThrow { initClient() }
|
||||
} finally {
|
||||
// Make sure to stop the server
|
||||
serverEngine.stop(1000, 2000)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun initClient(): Client {
|
||||
return HttpClient(ClientCIO) { install(SSE) }.mcpSse("http://$URL:$PORT")
|
||||
}
|
||||
|
||||
private fun initServer(): EmbeddedServer<CIOApplicationEngine, CIOApplicationEngine.Configuration> {
|
||||
val server = Server(
|
||||
Implementation(name = "sse-e2e-test", version = "1.0.0"),
|
||||
ServerOptions(capabilities = ServerCapabilities()),
|
||||
)
|
||||
|
||||
return embeddedServer(ServerCIO, host = URL, port = PORT) { mcp { server } }.start(wait = false)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val PORT = 3001
|
||||
private const val URL = "localhost"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,35 +1,22 @@
|
|||
package server
|
||||
|
||||
import io.modelcontextprotocol.kotlin.sdk.CallToolRequest
|
||||
import io.modelcontextprotocol.kotlin.sdk.CallToolResult
|
||||
import io.modelcontextprotocol.kotlin.sdk.ClientCapabilities
|
||||
import io.modelcontextprotocol.kotlin.sdk.EmptyJsonObject
|
||||
import io.modelcontextprotocol.kotlin.sdk.GetPromptRequest
|
||||
import io.modelcontextprotocol.kotlin.sdk.GetPromptResult
|
||||
import io.modelcontextprotocol.kotlin.sdk.Implementation
|
||||
import io.modelcontextprotocol.kotlin.sdk.InMemoryTransport
|
||||
import io.modelcontextprotocol.kotlin.sdk.Method
|
||||
import io.modelcontextprotocol.kotlin.sdk.Prompt
|
||||
import io.modelcontextprotocol.kotlin.sdk.PromptListChangedNotification
|
||||
import io.modelcontextprotocol.kotlin.sdk.ReadResourceRequest
|
||||
import io.modelcontextprotocol.kotlin.sdk.ReadResourceResult
|
||||
import io.modelcontextprotocol.kotlin.sdk.Resource
|
||||
import io.modelcontextprotocol.kotlin.sdk.ResourceListChangedNotification
|
||||
import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities
|
||||
import io.modelcontextprotocol.kotlin.sdk.TextContent
|
||||
import io.modelcontextprotocol.kotlin.sdk.TextResourceContents
|
||||
import io.modelcontextprotocol.kotlin.sdk.Tool
|
||||
import io.modelcontextprotocol.kotlin.sdk.ToolListChangedNotification
|
||||
import io.modelcontextprotocol.kotlin.sdk.client.Client
|
||||
import io.modelcontextprotocol.kotlin.sdk.client.ClientOptions
|
||||
import io.modelcontextprotocol.kotlin.sdk.server.RegisteredPrompt
|
||||
import io.modelcontextprotocol.kotlin.sdk.server.RegisteredResource
|
||||
import io.modelcontextprotocol.kotlin.sdk.server.RegisteredTool
|
||||
import io.modelcontextprotocol.kotlin.sdk.server.Server
|
||||
import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions
|
||||
import InMemoryTransport
|
||||
import io.modelcontextprotocol.kotlin.sdk.PromptMessage
|
||||
import io.modelcontextprotocol.kotlin.sdk.PromptMessageContent
|
||||
import io.modelcontextprotocol.kotlin.sdk.Role
|
||||
import io.modelcontextprotocol.kotlin.sdk.TextContent
|
||||
import io.modelcontextprotocol.kotlin.sdk.TextResourceContents
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.test.runTest
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ class StdioServerTransportTest {
|
|||
|
||||
val message = PingRequest().toJSON()
|
||||
|
||||
// Push message before the server started
|
||||
// Push a message before the server started
|
||||
val serialized = serializeMessage(message)
|
||||
inputWriter.write(serialized)
|
||||
inputWriter.flush()
|
||||
|
|
|
|||
|
|
@ -1,65 +0,0 @@
|
|||
package shared
|
||||
|
||||
import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage
|
||||
import io.modelcontextprotocol.kotlin.sdk.JSONRPCNotification
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import io.modelcontextprotocol.kotlin.sdk.shared.ReadBuffer
|
||||
import io.modelcontextprotocol.kotlin.sdk.shared.serializeMessage
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertNull
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.nio.charset.StandardCharsets
|
||||
|
||||
class ReadBufferTest {
|
||||
private val testMessage: JSONRPCMessage = JSONRPCNotification(method = "foobar")
|
||||
|
||||
private val json = Json {
|
||||
ignoreUnknownKeys = true
|
||||
encodeDefaults = true
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should have no messages after initialization`() {
|
||||
val readBuffer = ReadBuffer()
|
||||
assertNull(readBuffer.readMessage())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should only yield a message after a newline`() {
|
||||
val readBuffer = ReadBuffer()
|
||||
|
||||
// Append message without newline
|
||||
val messageBytes = json.encodeToString(testMessage)
|
||||
.toByteArray(StandardCharsets.UTF_8)
|
||||
readBuffer.append(messageBytes)
|
||||
assertNull(readBuffer.readMessage())
|
||||
|
||||
// Append newline and verify message is now available
|
||||
readBuffer.append("\n".toByteArray(StandardCharsets.UTF_8))
|
||||
assertEquals(testMessage, readBuffer.readMessage())
|
||||
assertNull(readBuffer.readMessage())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `skip empty line`() {
|
||||
val readBuffer = ReadBuffer()
|
||||
readBuffer.append("\n".toByteArray(StandardCharsets.UTF_8))
|
||||
assertNull(readBuffer.readMessage())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should be reusable after clearing`() {
|
||||
val readBuffer = ReadBuffer()
|
||||
|
||||
readBuffer.append("foobar".toByteArray(Charsets.UTF_8))
|
||||
readBuffer.clear()
|
||||
assertNull(readBuffer.readMessage())
|
||||
|
||||
val messageJson = serializeMessage(testMessage)
|
||||
readBuffer.append(messageJson.toByteArray(Charsets.UTF_8))
|
||||
readBuffer.append("\n".toByteArray(Charsets.UTF_8))
|
||||
val message = readBuffer.readMessage()
|
||||
assertEquals(testMessage, message)
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue