@@ -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,141 @@ 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( request. allHTTPHeaderFields ? [ " Accept " ] ? . contains ( " text/event-stream " ) == true )
493+ case " POST " :
494+ #expect( request. allHTTPHeaderFields ? [ " Accept " ] ? . contains ( " application/json " ) == true )
495+ default :
496+ Issue . record ( " Unsupported HTTP method \( String ( describing: request. httpMethod) ) " )
497+ }
498+
499+
500+ #expect( request. url == testEndpoint)
501+
502+ let bodyData = request. readBody ( )
503+
504+ guard let bodyData = bodyData,
505+ let json = try ? JSONSerialization . jsonObject ( with: bodyData) as? [ String : Any ] ,
506+ let method = json [ " method " ] as? String ,
507+ let requestID = json [ " id " ] as? String
508+ else {
509+ throw NSError (
510+ domain: " MockURLProtocolError " , code: 0 ,
511+ userInfo: [
512+ NSLocalizedDescriptionKey: " Invalid JSON-RPC message \( #file) : \( #line) "
513+ ] )
514+ }
515+
516+ if method == " initialize " {
517+ await tracker. setRequest ( . initialize)
518+
519+ let result = Initialize . Result (
520+ protocolVersion: Version . latest,
521+ capabilities: . init( tools: . init( ) ) ,
522+ serverInfo: . init( name: " Mock Server " , version: " 0.0.1 " ) ,
523+ instructions: nil
524+ )
525+ let response = Initialize . response ( id: . string( requestID) , result: result)
526+ let responseData = try JSONEncoder ( ) . encode ( response)
527+
528+ let httpResponse = HTTPURLResponse (
529+ url: testEndpoint, statusCode: 200 , httpVersion: " HTTP/1.1 " ,
530+ headerFields: [ " Content-Type " : " application/json " ] ) !
531+ return ( httpResponse, responseData)
532+ } else if method == " tools/call " {
533+ // Verify initialize was called first
534+ if let lastRequest = await tracker. getLastRequest ( ) , lastRequest != . initialize
535+ {
536+ #expect( Bool ( false ) , " Initialize should be called before callTool " )
537+ }
538+
539+ await tracker. setRequest ( . callTool)
540+
541+ let params = json [ " params " ] as? [ String : Any ]
542+ let toolName = params ? [ " name " ] as? String
543+ #expect( toolName == " calculator " )
544+
545+ let result = CallTool . Result ( content: [ . text( " 42 " ) ] )
546+ let response = CallTool . response ( id: . string( requestID) , result: result)
547+ let responseData = try JSONEncoder ( ) . encode ( response)
548+
549+ let httpResponse = HTTPURLResponse (
550+ url: testEndpoint, statusCode: 200 , httpVersion: " HTTP/1.1 " ,
551+ headerFields: [ " Content-Type " : " application/json " ] ) !
552+ return ( httpResponse, responseData)
553+ }
554+
555+ throw NSError (
556+ domain: " MockURLProtocolError " , code: 0 ,
557+ userInfo: [
558+ NSLocalizedDescriptionKey:
559+ " Unexpected request method: \( method) \( #file) : \( #line) "
560+ ] )
561+ }
562+
563+ // Execute the complete flow
564+ try await client. connect ( transport: transport)
565+
566+ // Step 1: Initialize client
567+ let initResult = try await client. initialize ( )
568+ #expect( initResult. protocolVersion == Version . latest)
569+ #expect( initResult. capabilities. tools != nil )
570+
571+ // Step 2: Call a tool
572+ let toolResult = try await client. callTool ( name: " calculator " )
573+ #expect( toolResult. content. count == 1 )
574+ if case let . text( text) = toolResult. content [ 0 ] {
575+ #expect( text == " 42 " )
576+ } else {
577+ #expect( Bool ( false ) , " Expected text content " )
578+ }
579+
580+ // Step 3: Verify request sequence
581+ #expect( await tracker. getLastRequest ( ) == . callTool)
582+
583+ // Step 4: Disconnect
584+ await client. disconnect ( )
585+ }
586+ }
453587#endif // swift(>=6.1)
0 commit comments