Skip to content

Commit b87b820

Browse files
committed
Add test coverage for Client with HTTPClientTransport
1 parent ccb070f commit b87b820

File tree

1 file changed

+152
-7
lines changed

1 file changed

+152
-7
lines changed

Tests/MCPTests/HTTPClientTransportTests.swift

Lines changed: 152 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,11 @@ import Testing
5151

5252
func executeHandler(for request: URLRequest) async throws -> (HTTPURLResponse, Data) {
5353
guard let handler = requestHandler else {
54-
throw MockURLProtocolError.noRequestHandler
54+
throw NSError(
55+
domain: "MockURLProtocolError", code: 0,
56+
userInfo: [
57+
NSLocalizedDescriptionKey: "No request handler set"
58+
])
5559
}
5660
return try await handler(request)
5761
}
@@ -123,11 +127,6 @@ import Testing
123127
override func stopLoading() {}
124128
}
125129

126-
enum MockURLProtocolError: Swift.Error {
127-
case noRequestHandler
128-
case invalidURL
129-
}
130-
131130
// MARK: -
132131

133132
@Suite("HTTP Client Transport Tests", .serialized)
@@ -448,6 +447,152 @@ import Testing
448447
#expect(receivedData == expectedData)
449448
}
450449
#endif // !canImport(FoundationNetworking)
451-
}
452450

451+
@Test(
452+
"Client with HTTP Transport complete flow", .httpClientTransportSetup,
453+
.timeLimit(.minutes(1)))
454+
func testClientFlow() async throws {
455+
let configuration = URLSessionConfiguration.ephemeral
456+
configuration.protocolClasses = [MockURLProtocol.self]
457+
458+
let transport = HTTPClientTransport(
459+
endpoint: testEndpoint,
460+
configuration: configuration,
461+
streaming: false,
462+
logger: nil
463+
)
464+
465+
let client = Client(name: "TestClient", version: "1.0.0")
466+
467+
// Use an actor to track request sequence
468+
actor RequestTracker {
469+
enum RequestType {
470+
case initialize
471+
case callTool
472+
}
473+
474+
private(set) var lastRequest: RequestType?
475+
476+
func setRequest(_ type: RequestType) {
477+
lastRequest = type
478+
}
479+
480+
func getLastRequest() -> RequestType? {
481+
return lastRequest
482+
}
483+
}
484+
485+
let tracker = RequestTracker()
486+
487+
// Setup mock responses
488+
await MockURLProtocol.requestHandlerStorage.setHandler {
489+
[testEndpoint, tracker] (request: URLRequest) in
490+
switch request.httpMethod {
491+
case "GET":
492+
#expect(
493+
request.allHTTPHeaderFields?["Accept"]?.contains("text/event-stream")
494+
== true)
495+
case "POST":
496+
#expect(
497+
request.allHTTPHeaderFields?["Accept"]?.contains("application/json") == true
498+
)
499+
default:
500+
Issue.record(
501+
"Unsupported HTTP method \(String(describing: request.httpMethod))")
502+
}
503+
504+
#expect(request.url == testEndpoint)
505+
506+
let bodyData = request.readBody()
507+
508+
guard let bodyData = bodyData,
509+
let json = try JSONSerialization.jsonObject(with: bodyData) as? [String: Any],
510+
let method = json["method"] as? String
511+
else {
512+
throw NSError(
513+
domain: "MockURLProtocolError", code: 0,
514+
userInfo: [
515+
NSLocalizedDescriptionKey: "Invalid JSON-RPC message \(#file):\(#line)"
516+
])
517+
}
518+
519+
if method == "initialize" {
520+
await tracker.setRequest(.initialize)
521+
522+
let requestID = json["id"] as! String
523+
let result = Initialize.Result(
524+
protocolVersion: Version.latest,
525+
capabilities: .init(tools: .init()),
526+
serverInfo: .init(name: "Mock Server", version: "0.0.1"),
527+
instructions: nil
528+
)
529+
let response = Initialize.response(id: .string(requestID), result: result)
530+
let responseData = try JSONEncoder().encode(response)
531+
532+
let httpResponse = HTTPURLResponse(
533+
url: testEndpoint, statusCode: 200, httpVersion: "HTTP/1.1",
534+
headerFields: ["Content-Type": "application/json"])!
535+
return (httpResponse, responseData)
536+
} else if method == "tools/call" {
537+
// Verify initialize was called first
538+
if let lastRequest = await tracker.getLastRequest(), lastRequest != .initialize
539+
{
540+
#expect(Bool(false), "Initialize should be called before callTool")
541+
}
542+
543+
await tracker.setRequest(.callTool)
544+
545+
let params = json["params"] as? [String: Any]
546+
let toolName = params?["name"] as? String
547+
#expect(toolName == "calculator")
548+
549+
let requestID = json["id"] as! String
550+
let result = CallTool.Result(content: [.text("42")])
551+
let response = CallTool.response(id: .string(requestID), result: result)
552+
let responseData = try JSONEncoder().encode(response)
553+
554+
let httpResponse = HTTPURLResponse(
555+
url: testEndpoint, statusCode: 200, httpVersion: "HTTP/1.1",
556+
headerFields: ["Content-Type": "application/json"])!
557+
return (httpResponse, responseData)
558+
} else if method == "notifications/initialized" {
559+
// Ignore initialized notifications
560+
let httpResponse = HTTPURLResponse(
561+
url: testEndpoint, statusCode: 200, httpVersion: "HTTP/1.1",
562+
headerFields: ["Content-Type": "application/json"])!
563+
return (httpResponse, Data())
564+
} else {
565+
throw NSError(
566+
domain: "MockURLProtocolError", code: 0,
567+
userInfo: [
568+
NSLocalizedDescriptionKey:
569+
"Unexpected request method: \(method) \(#file):\(#line)"
570+
])
571+
}
572+
}
573+
574+
// Execute the complete flow
575+
try await client.connect(transport: transport)
576+
577+
// Step 1: Initialize client
578+
let initResult = try await client.initialize()
579+
#expect(initResult.protocolVersion == Version.latest)
580+
#expect(initResult.capabilities.tools != nil)
581+
582+
// Step 2: Call a tool
583+
let toolResult = try await client.callTool(name: "calculator")
584+
#expect(toolResult.content.count == 1)
585+
if case let .text(text) = toolResult.content[0] {
586+
#expect(text == "42")
587+
} else {
588+
#expect(Bool(false), "Expected text content")
589+
}
590+
591+
// Step 3: Verify request sequence
592+
#expect(await tracker.getLastRequest() == .callTool)
593+
594+
// Step 4: Disconnect
595+
await client.disconnect()
596+
}
597+
}
453598
#endif // swift(>=6.1)

0 commit comments

Comments
 (0)