@@ -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