swift-sdk/Tests/MCPTests/ServerTests.swift

201 lines
6.3 KiB
Swift

import Foundation
import Testing
@testable import MCP
@Suite("Server Tests")
struct ServerTests {
@Test("Start and stop server")
func testServerStartAndStop() async throws {
let transport = MockTransport()
let server = Server(name: "TestServer", version: "1.0")
#expect(await transport.isConnected == false)
try await server.start(transport: transport)
#expect(await transport.isConnected == true)
await server.stop()
#expect(await transport.isConnected == false)
}
@Test("Initialize request handling")
func testServerHandleInitialize() async throws {
let transport = MockTransport()
// Queue an initialize request
try await transport.queue(
request: Initialize.request(
.init(
protocolVersion: Version.latest,
capabilities: .init(),
clientInfo: .init(name: "TestClient", version: "1.0")
)
))
// Start the server
let server: Server = Server(
name: "TestServer",
version: "1.0"
)
try await server.start(transport: transport)
// Wait for message processing and response
try await Task.sleep(nanoseconds: 100_000_000) // 100ms
#expect(await transport.sentMessages.count == 1)
let messages = await transport.sentMessages
if let response = messages.first {
#expect(response.contains("serverInfo"))
}
// Clean up
await server.stop()
await transport.disconnect()
}
@Test("Initialize hook - successful")
func testInitializeHookSuccess() async throws {
let transport = MockTransport()
actor TestState {
var hookCalled = false
func setHookCalled() { hookCalled = true }
func wasHookCalled() -> Bool { hookCalled }
}
let state = TestState()
let server = Server(name: "TestServer", version: "1.0")
// Start with the hook directly
try await server.start(transport: transport) { clientInfo, capabilities in
#expect(clientInfo.name == "TestClient")
#expect(clientInfo.version == "1.0")
await state.setHookCalled()
}
// Wait for server to initialize
try await Task.sleep(for: .milliseconds(10))
// Queue an initialize request
try await transport.queue(
request: Initialize.request(
.init(
protocolVersion: Version.latest,
capabilities: .init(),
clientInfo: .init(name: "TestClient", version: "1.0")
)
))
// Wait for message processing and hook execution
try await Task.sleep(for: .milliseconds(500))
#expect(await state.wasHookCalled() == true)
#expect(await transport.sentMessages.count >= 1)
let messages = await transport.sentMessages
if let response = messages.first {
#expect(response.contains("serverInfo"))
}
await server.stop()
await transport.disconnect()
}
@Test("Initialize hook - rejection")
func testInitializeHookRejection() async throws {
let transport = MockTransport()
let server = Server(name: "TestServer", version: "1.0")
try await server.start(transport: transport) { clientInfo, _ in
if clientInfo.name == "BlockedClient" {
throw MCPError.invalidRequest("Client not allowed")
}
}
// Wait for server to initialize
try await Task.sleep(nanoseconds: 10_000_000) // 10ms
// Queue an initialize request from blocked client
try await transport.queue(
request: Initialize.request(
.init(
protocolVersion: Version.latest,
capabilities: .init(),
clientInfo: .init(name: "BlockedClient", version: "1.0")
)
))
// Wait for message processing
try await Task.sleep(nanoseconds: 200_000_000) // 200ms
#expect(await transport.sentMessages.count >= 1)
let messages = await transport.sentMessages
if let response = messages.first {
#expect(response.contains("error"))
#expect(response.contains("Client not allowed"))
}
await server.stop()
await transport.disconnect()
}
@Test("JSON-RPC batch processing")
func testJSONRPCBatchProcessing() async throws {
let transport = MockTransport()
let server = Server(name: "TestServer", version: "1.0")
// Start the server
try await server.start(transport: transport)
// Initialize the server first
try await transport.queue(
request: Initialize.request(
.init(
protocolVersion: Version.latest,
capabilities: .init(),
clientInfo: .init(name: "TestClient", version: "1.0")
)
)
)
// Wait for server to initialize and respond
try await Task.sleep(for: .milliseconds(100))
// Clear sent messages
await transport.clearMessages()
// Create a batch with multiple requests
let batchJSON = """
[
{"jsonrpc":"2.0","id":1,"method":"ping","params":{}},
{"jsonrpc":"2.0","id":2,"method":"ping","params":{}}
]
"""
let batch = try JSONDecoder().decode([AnyRequest].self, from: batchJSON.data(using: .utf8)!)
// Send the batch request
try await transport.queue(batch: batch)
// Wait for batch processing
try await Task.sleep(for: .milliseconds(100))
// Verify response
let sentMessages = await transport.sentMessages
#expect(sentMessages.count == 1)
if let batchResponse = sentMessages.first {
// Should be an array
#expect(batchResponse.hasPrefix("["))
#expect(batchResponse.hasSuffix("]"))
// Should contain both request IDs
#expect(batchResponse.contains("\"id\":1"))
#expect(batchResponse.contains("\"id\":2"))
}
await server.stop()
await transport.disconnect()
}
}