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