swift-sdk/Tests/MCPTests/StdioTransportTests.swift
Mattt d583151871
Add streamable HTTP transport for clients (#70)
* Rename file to Transport.swift

* Rename file to StdioTransportTests.swift

* Add .index-build/ to .gitignore

* Implement HTTP client transport

* Add test coverage for streamable HTTP client transport

* Conditionally import FoundationNetworking

* Conditionalize streaming for unsupported platforms

* Import FoundationNetworking in tests

* Add 5 minute timeout to CI jobs

* Remove debug logging

* Formatting

* Skip SSE tests on platforms that don't support streaming
2025-04-15 09:06:29 -07:00

127 lines
4.1 KiB
Swift

import Foundation
import Testing
@testable import MCP
#if canImport(System)
import System
#else
@preconcurrency import SystemPackage
#endif
@Suite("Stdio Transport Tests")
struct StdioTransportTests {
@Test("Connection")
func testStdioTransportConnection() async throws {
let (input, _) = try FileDescriptor.pipe()
let (_, output) = try FileDescriptor.pipe()
let transport = StdioTransport(input: input, output: output, logger: nil)
try await transport.connect()
await transport.disconnect()
}
@Test("Send Message")
func testStdioTransportSendMessage() async throws {
let (reader, output) = try FileDescriptor.pipe()
let (input, _) = try FileDescriptor.pipe()
let transport = StdioTransport(input: input, output: output, logger: nil)
try await transport.connect()
// Test sending a simple message
let message = #"{"key":"value"}"#
try await transport.send(message.data(using: .utf8)!)
// Read and verify the output
var buffer = [UInt8](repeating: 0, count: 1024)
let bytesRead = try buffer.withUnsafeMutableBufferPointer { pointer in
try reader.read(into: UnsafeMutableRawBufferPointer(pointer))
}
let data = Data(buffer[..<bytesRead])
let expectedOutput = message.data(using: .utf8)! + "\n".data(using: .utf8)!
#expect(data == expectedOutput)
await transport.disconnect()
}
@Test("Receive Message")
func testStdioTransportReceiveMessage() async throws {
let (input, writer) = try FileDescriptor.pipe()
let (_, output) = try FileDescriptor.pipe()
let transport = StdioTransport(input: input, output: output, logger: nil)
try await transport.connect()
// Write test message to input pipe
let message = ["key": "value"]
let messageData = try JSONEncoder().encode(message) + "\n".data(using: .utf8)!
try writer.writeAll(messageData)
try writer.close()
// Start receiving messages
let stream: AsyncThrowingStream<Data, Swift.Error> = await transport.receive()
var iterator = stream.makeAsyncIterator()
// Get first message
let received = try await iterator.next()
#expect(received == #"{"key":"value"}"#.data(using: .utf8)!)
await transport.disconnect()
}
@Test("Invalid JSON")
func testStdioTransportInvalidJSON() async throws {
let (input, writer) = try FileDescriptor.pipe()
let (_, output) = try FileDescriptor.pipe()
let transport = StdioTransport(input: input, output: output, logger: nil)
try await transport.connect()
// Write invalid JSON to input pipe
let invalidJSON = #"{ invalid json }"#
try writer.writeAll(invalidJSON.data(using: .utf8)!)
try writer.close()
let stream: AsyncThrowingStream<Data, Swift.Error> = await transport.receive()
var iterator = stream.makeAsyncIterator()
_ = try await iterator.next()
await transport.disconnect()
}
@Test("Send Error")
func testStdioTransportSendError() async throws {
let (input, _) = try FileDescriptor.pipe()
let transport = StdioTransport(
input: input,
output: FileDescriptor(rawValue: -1), // Invalid fd
logger: nil
)
do {
try await transport.connect()
#expect(Bool(false), "Expected connect to throw an error")
} catch {
#expect(error is MCPError)
}
await transport.disconnect()
}
@Test("Receive Error")
func testStdioTransportReceiveError() async throws {
let (_, output) = try FileDescriptor.pipe()
let transport = StdioTransport(
input: FileDescriptor(rawValue: -1), // Invalid fd
output: output,
logger: nil
)
do {
try await transport.connect()
#expect(Bool(false), "Expected connect to throw an error")
} catch {
#expect(error is MCPError)
}
await transport.disconnect()
}
}