@@ -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)
@@ -140,7 +139,11 @@ import Testing
140139 configuration. protocolClasses = [ MockURLProtocol . self]
141140
142141 let transport = HTTPClientTransport (
143- endpoint: testEndpoint, configuration: configuration, streaming: false , logger: nil )
142+ endpoint: testEndpoint,
143+ configuration: configuration,
144+ streaming: false ,
145+ logger: nil
146+ )
144147
145148 try await transport. connect ( )
146149 await transport. disconnect ( )
@@ -152,7 +155,11 @@ import Testing
152155 configuration. protocolClasses = [ MockURLProtocol . self]
153156
154157 let transport = HTTPClientTransport (
155- endpoint: testEndpoint, configuration: configuration, streaming: false , logger: nil )
158+ endpoint: testEndpoint,
159+ configuration: configuration,
160+ streaming: false ,
161+ logger: nil
162+ )
156163 try await transport. connect ( )
157164
158165 let messageData = #"{"jsonrpc":"2.0","method":"initialize","id":1}"# . data ( using: . utf8) !
@@ -190,7 +197,11 @@ import Testing
190197 configuration. protocolClasses = [ MockURLProtocol . self]
191198
192199 let transport = HTTPClientTransport (
193- endpoint: testEndpoint, configuration: configuration, streaming: false , logger: nil )
200+ endpoint: testEndpoint,
201+ configuration: configuration,
202+ streaming: false ,
203+ logger: nil
204+ )
194205 try await transport. connect ( )
195206
196207 let messageData = #"{"jsonrpc":"2.0","method":"initialize","id":1}"# . data ( using: . utf8) !
@@ -220,7 +231,11 @@ import Testing
220231 configuration. protocolClasses = [ MockURLProtocol . self]
221232
222233 let transport = HTTPClientTransport (
223- endpoint: testEndpoint, configuration: configuration, streaming: false , logger: nil )
234+ endpoint: testEndpoint,
235+ configuration: configuration,
236+ streaming: false ,
237+ logger: nil
238+ )
224239 try await transport. connect ( )
225240
226241 let initialSessionID = " existing-session-abc "
@@ -265,7 +280,11 @@ import Testing
265280 configuration. protocolClasses = [ MockURLProtocol . self]
266281
267282 let transport = HTTPClientTransport (
268- endpoint: testEndpoint, configuration: configuration)
283+ endpoint: testEndpoint,
284+ configuration: configuration,
285+ streaming: false ,
286+ logger: nil
287+ )
269288 try await transport. connect ( )
270289
271290 let messageData = #"{"jsonrpc":"2.0","method":"test","id":3}"# . data ( using: . utf8) !
@@ -298,7 +317,11 @@ import Testing
298317 configuration. protocolClasses = [ MockURLProtocol . self]
299318
300319 let transport = HTTPClientTransport (
301- endpoint: testEndpoint, configuration: configuration)
320+ endpoint: testEndpoint,
321+ configuration: configuration,
322+ streaming: false ,
323+ logger: nil
324+ )
302325 try await transport. connect ( )
303326
304327 let messageData = #"{"jsonrpc":"2.0","method":"test","id":4}"# . data ( using: . utf8) !
@@ -331,7 +354,11 @@ import Testing
331354 configuration. protocolClasses = [ MockURLProtocol . self]
332355
333356 let transport = HTTPClientTransport (
334- endpoint: testEndpoint, configuration: configuration)
357+ endpoint: testEndpoint,
358+ configuration: configuration,
359+ streaming: false ,
360+ logger: nil
361+ )
335362 try await transport. connect ( )
336363
337364 let initialSessionID = " expired-session-xyz "
@@ -385,8 +412,11 @@ import Testing
385412 configuration. protocolClasses = [ MockURLProtocol . self]
386413
387414 let transport = HTTPClientTransport (
388- endpoint: testEndpoint, configuration: configuration, streaming: true ,
389- logger: nil )
415+ endpoint: testEndpoint,
416+ configuration: configuration,
417+ streaming: true ,
418+ logger: nil
419+ )
390420
391421 let eventString = " id: event1 \n data: { \" key \" : \" value \" } \n \n "
392422 let sseEventData = eventString. data ( using: . utf8) !
@@ -419,8 +449,11 @@ import Testing
419449 configuration. protocolClasses = [ MockURLProtocol . self]
420450
421451 let transport = HTTPClientTransport (
422- endpoint: testEndpoint, configuration: configuration, streaming: true ,
423- logger: nil )
452+ endpoint: testEndpoint,
453+ configuration: configuration,
454+ streaming: true ,
455+ logger: nil
456+ )
424457
425458 let eventString = " id: event1 \r \n data: { \" key \" : \" value \" } \r \n \n "
426459 let sseEventData = eventString. data ( using: . utf8) !
@@ -448,6 +481,152 @@ import Testing
448481 #expect( receivedData == expectedData)
449482 }
450483 #endif // !canImport(FoundationNetworking)
451- }
452484
485+ @Test (
486+ " Client with HTTP Transport complete flow " , . httpClientTransportSetup,
487+ . timeLimit( . minutes( 1 ) ) )
488+ func testClientFlow( ) async throws {
489+ let configuration = URLSessionConfiguration . ephemeral
490+ configuration. protocolClasses = [ MockURLProtocol . self]
491+
492+ let transport = HTTPClientTransport (
493+ endpoint: testEndpoint,
494+ configuration: configuration,
495+ streaming: false ,
496+ logger: nil
497+ )
498+
499+ let client = Client ( name: " TestClient " , version: " 1.0.0 " )
500+
501+ // Use an actor to track request sequence
502+ actor RequestTracker {
503+ enum RequestType {
504+ case initialize
505+ case callTool
506+ }
507+
508+ private( set) var lastRequest : RequestType ?
509+
510+ func setRequest( _ type: RequestType ) {
511+ lastRequest = type
512+ }
513+
514+ func getLastRequest( ) -> RequestType ? {
515+ return lastRequest
516+ }
517+ }
518+
519+ let tracker = RequestTracker ( )
520+
521+ // Setup mock responses
522+ await MockURLProtocol . requestHandlerStorage. setHandler {
523+ [ testEndpoint, tracker] ( request: URLRequest ) in
524+ switch request. httpMethod {
525+ case " GET " :
526+ #expect(
527+ request. allHTTPHeaderFields ? [ " Accept " ] ? . contains ( " text/event-stream " )
528+ == true )
529+ case " POST " :
530+ #expect(
531+ request. allHTTPHeaderFields ? [ " Accept " ] ? . contains ( " application/json " ) == true
532+ )
533+ default :
534+ Issue . record (
535+ " Unsupported HTTP method \( String ( describing: request. httpMethod) ) " )
536+ }
537+
538+ #expect( request. url == testEndpoint)
539+
540+ let bodyData = request. readBody ( )
541+
542+ guard let bodyData = bodyData,
543+ let json = try JSONSerialization . jsonObject ( with: bodyData) as? [ String : Any ] ,
544+ let method = json [ " method " ] as? String
545+ else {
546+ throw NSError (
547+ domain: " MockURLProtocolError " , code: 0 ,
548+ userInfo: [
549+ NSLocalizedDescriptionKey: " Invalid JSON-RPC message \( #file) : \( #line) "
550+ ] )
551+ }
552+
553+ if method == " initialize " {
554+ await tracker. setRequest ( . initialize)
555+
556+ let requestID = json [ " id " ] as! String
557+ let result = Initialize . Result (
558+ protocolVersion: Version . latest,
559+ capabilities: . init( tools: . init( ) ) ,
560+ serverInfo: . init( name: " Mock Server " , version: " 0.0.1 " ) ,
561+ instructions: nil
562+ )
563+ let response = Initialize . response ( id: . string( requestID) , result: result)
564+ let responseData = try JSONEncoder ( ) . encode ( response)
565+
566+ let httpResponse = HTTPURLResponse (
567+ url: testEndpoint, statusCode: 200 , httpVersion: " HTTP/1.1 " ,
568+ headerFields: [ " Content-Type " : " application/json " ] ) !
569+ return ( httpResponse, responseData)
570+ } else if method == " tools/call " {
571+ // Verify initialize was called first
572+ if let lastRequest = await tracker. getLastRequest ( ) , lastRequest != . initialize
573+ {
574+ #expect( Bool ( false ) , " Initialize should be called before callTool " )
575+ }
576+
577+ await tracker. setRequest ( . callTool)
578+
579+ let params = json [ " params " ] as? [ String : Any ]
580+ let toolName = params ? [ " name " ] as? String
581+ #expect( toolName == " calculator " )
582+
583+ let requestID = json [ " id " ] as! String
584+ let result = CallTool . Result ( content: [ . text( " 42 " ) ] )
585+ let response = CallTool . response ( id: . string( requestID) , result: result)
586+ let responseData = try JSONEncoder ( ) . encode ( response)
587+
588+ let httpResponse = HTTPURLResponse (
589+ url: testEndpoint, statusCode: 200 , httpVersion: " HTTP/1.1 " ,
590+ headerFields: [ " Content-Type " : " application/json " ] ) !
591+ return ( httpResponse, responseData)
592+ } else if method == " notifications/initialized " {
593+ // Ignore initialized notifications
594+ let httpResponse = HTTPURLResponse (
595+ url: testEndpoint, statusCode: 200 , httpVersion: " HTTP/1.1 " ,
596+ headerFields: [ " Content-Type " : " application/json " ] ) !
597+ return ( httpResponse, Data ( ) )
598+ } else {
599+ throw NSError (
600+ domain: " MockURLProtocolError " , code: 0 ,
601+ userInfo: [
602+ NSLocalizedDescriptionKey:
603+ " Unexpected request method: \( method) \( #file) : \( #line) "
604+ ] )
605+ }
606+ }
607+
608+ // Execute the complete flow
609+ try await client. connect ( transport: transport)
610+
611+ // Step 1: Initialize client
612+ let initResult = try await client. initialize ( )
613+ #expect( initResult. protocolVersion == Version . latest)
614+ #expect( initResult. capabilities. tools != nil )
615+
616+ // Step 2: Call a tool
617+ let toolResult = try await client. callTool ( name: " calculator " )
618+ #expect( toolResult. content. count == 1 )
619+ if case let . text( text) = toolResult. content [ 0 ] {
620+ #expect( text == " 42 " )
621+ } else {
622+ #expect( Bool ( false ) , " Expected text content " )
623+ }
624+
625+ // Step 3: Verify request sequence
626+ #expect( await tracker. getLastRequest ( ) == . callTool)
627+
628+ // Step 4: Disconnect
629+ await client. disconnect ( )
630+ }
631+ }
453632#endif // swift(>=6.1)
0 commit comments