Skip to content

Commit a34532d

Browse files
committed
Add test coverage for Client with HTTPClientTransport
1 parent 046fa3e commit a34532d

File tree

1 file changed

+141
-7
lines changed

1 file changed

+141
-7
lines changed

Tests/MCPTests/HTTPClientTransportTests.swift

Lines changed: 141 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)
@@ -414,6 +413,141 @@ import Testing
414413
#expect(receivedData == expectedData)
415414
}
416415
#endif // !canImport(FoundationNetworking)
417-
}
418416

417+
@Test(
418+
"Client with HTTP Transport complete flow", .httpClientTransportSetup,
419+
.timeLimit(.minutes(1)))
420+
func testClientFlow() async throws {
421+
let configuration = URLSessionConfiguration.ephemeral
422+
configuration.protocolClasses = [MockURLProtocol.self]
423+
424+
let transport = HTTPClientTransport(
425+
endpoint: testEndpoint,
426+
configuration: configuration,
427+
streaming: false,
428+
logger: nil
429+
)
430+
431+
let client = Client(name: "TestClient", version: "1.0.0")
432+
433+
// Use an actor to track request sequence
434+
actor RequestTracker {
435+
enum RequestType {
436+
case initialize
437+
case callTool
438+
}
439+
440+
private(set) var lastRequest: RequestType?
441+
442+
func setRequest(_ type: RequestType) {
443+
lastRequest = type
444+
}
445+
446+
func getLastRequest() -> RequestType? {
447+
return lastRequest
448+
}
449+
}
450+
451+
let tracker = RequestTracker()
452+
453+
// Setup mock responses
454+
await MockURLProtocol.requestHandlerStorage.setHandler {
455+
[testEndpoint, tracker] (request: URLRequest) in
456+
switch request.httpMethod {
457+
case "GET":
458+
#expect(request.allHTTPHeaderFields?["Accept"]?.contains("text/event-stream") == true)
459+
case "POST":
460+
#expect(request.allHTTPHeaderFields?["Accept"]?.contains("application/json") == true)
461+
default:
462+
Issue.record("Unsupported HTTP method \(String(describing: request.httpMethod))")
463+
}
464+
465+
466+
#expect(request.url == testEndpoint)
467+
468+
let bodyData = request.readBody()
469+
470+
guard let bodyData = bodyData,
471+
let json = try? JSONSerialization.jsonObject(with: bodyData) as? [String: Any],
472+
let method = json["method"] as? String,
473+
let requestID = json["id"] as? String
474+
else {
475+
throw NSError(
476+
domain: "MockURLProtocolError", code: 0,
477+
userInfo: [
478+
NSLocalizedDescriptionKey: "Invalid JSON-RPC message \(#file):\(#line)"
479+
])
480+
}
481+
482+
if method == "initialize" {
483+
await tracker.setRequest(.initialize)
484+
485+
let result = Initialize.Result(
486+
protocolVersion: Version.latest,
487+
capabilities: .init(tools: .init()),
488+
serverInfo: .init(name: "Mock Server", version: "0.0.1"),
489+
instructions: nil
490+
)
491+
let response = Initialize.response(id: .string(requestID), result: result)
492+
let responseData = try JSONEncoder().encode(response)
493+
494+
let httpResponse = HTTPURLResponse(
495+
url: testEndpoint, statusCode: 200, httpVersion: "HTTP/1.1",
496+
headerFields: ["Content-Type": "application/json"])!
497+
return (httpResponse, responseData)
498+
} else if method == "tools/call" {
499+
// Verify initialize was called first
500+
if let lastRequest = await tracker.getLastRequest(), lastRequest != .initialize
501+
{
502+
#expect(Bool(false), "Initialize should be called before callTool")
503+
}
504+
505+
await tracker.setRequest(.callTool)
506+
507+
let params = json["params"] as? [String: Any]
508+
let toolName = params?["name"] as? String
509+
#expect(toolName == "calculator")
510+
511+
let result = CallTool.Result(content: [.text("42")])
512+
let response = CallTool.response(id: .string(requestID), result: result)
513+
let responseData = try JSONEncoder().encode(response)
514+
515+
let httpResponse = HTTPURLResponse(
516+
url: testEndpoint, statusCode: 200, httpVersion: "HTTP/1.1",
517+
headerFields: ["Content-Type": "application/json"])!
518+
return (httpResponse, responseData)
519+
}
520+
521+
throw NSError(
522+
domain: "MockURLProtocolError", code: 0,
523+
userInfo: [
524+
NSLocalizedDescriptionKey:
525+
"Unexpected request method: \(method) \(#file):\(#line)"
526+
])
527+
}
528+
529+
// Execute the complete flow
530+
try await client.connect(transport: transport)
531+
532+
// Step 1: Initialize client
533+
let initResult = try await client.initialize()
534+
#expect(initResult.protocolVersion == Version.latest)
535+
#expect(initResult.capabilities.tools != nil)
536+
537+
// Step 2: Call a tool
538+
let toolResult = try await client.callTool(name: "calculator")
539+
#expect(toolResult.content.count == 1)
540+
if case let .text(text) = toolResult.content[0] {
541+
#expect(text == "42")
542+
} else {
543+
#expect(Bool(false), "Expected text content")
544+
}
545+
546+
// Step 3: Verify request sequence
547+
#expect(await tracker.getLastRequest() == .callTool)
548+
549+
// Step 4: Disconnect
550+
await client.disconnect()
551+
}
552+
}
419553
#endif // swift(>=6.1)

0 commit comments

Comments
 (0)