import XCTest @testable import WRNexusInbox import Foundation #if canImport(FoundationNetworking) import FoundationNetworking #endif private final class StubProtocol: URLProtocol { static var handler: ((URLRequest) -> (Int,String))! override class func canInit(with request:URLRequest)->Bool {true} override class func canonicalRequest(for request:URLRequest)->URLRequest {request} override func startLoading() { let (status,text)=Self.handler(request) client?.urlProtocol(self,didReceive:HTTPURLResponse(url:request.url!,statusCode:status,httpVersion:nil,headerFields:["Content-Type":"application/json"])!,cacheStoragePolicy:.notAllowed) client?.urlProtocol(self,didLoad:Data(text.utf8));client?.urlProtocolDidFinishLoading(self) } override func stopLoading() {} } private final class Counter: @unchecked Sendable {let lock=NSLock();var count=0;func next()->String{lock.lock();defer{lock.unlock()};count+=1;return "recipient-\(count)"}} final class TransportTests:XCTestCase { func testAllRecipientOperationsAndTokenRefresh() async throws { let config=URLSessionConfiguration.ephemeral;config.protocolClasses=[StubProtocol.self];let session=URLSession(configuration:config);defer{session.invalidateAndCancel()} var requests=[URLRequest]();let counter=Counter() StubProtocol.handler={request in requests.append(request) if request.httpMethod=="GET",request.url!.path.hasSuffix("messages") {return (200,#"{"messages":[],"unreadCount":2}"#)} if request.httpMethod=="GET",request.url!.path.hasSuffix("preferences") {return (200,##"{"branding":{"displayName":"Acme","brandColor":"#123456","brandTextColor":"#ffffff","logoUrl":null},"deliveryRules":{"timezone":"UTC","quietHours":null,"marketingDailyLimit":null},"preferences":[{"category":"*","in_app_enabled":1,"marketing_enabled":0}],"channelPreferences":[]}"##)} if request.httpMethod=="GET",request.url!.path.hasSuffix("devices") {return (200,#"{"devices":[],"mobileDevices":[],"connections":[],"mobileConnections":[]}"#)} return (200,#"{"ok":true,"id":"d","revision":2,"replayed":false}"#) } let c=try InboxClient(endpoint:URL(string:"http://localhost")!,session:session,token:{counter.next()}) let snapshot=try await c.messages(limit:20);XCTAssertEqual(snapshot.unreadCount,2) _=try await c.markRead("m");_=try await c.archive("m") let preferences=try await c.preferences();XCTAssertEqual(preferences.preferences[0].inAppEnabled,1);XCTAssertEqual(preferences.branding?.displayName,"Acme") try await c.savePreferences(PreferenceInput(category:"*",inAppEnabled:true,marketingEnabled:false,deliveryRules:DeliveryRules(timezone:"UTC"))) _=try await c.devices();let device=try await c.registerMobilePush(MobileRegistration(connectionId:"c",installationId:"i",name:"Phone",token:"rotated"));XCTAssertEqual(device.revision,2) _=try await c.unregisterMobilePush("d");_=try await c.unregisterWebPush("w") XCTAssertEqual(requests.count,9);XCTAssertEqual(requests[0].url?.query,"limit=20") for (i,r) in requests.enumerated(){XCTAssertEqual(r.value(forHTTPHeaderField:"Authorization"),"Bearer recipient-\(i+1)")} let encoded=try JSONSerialization.jsonObject(with:requests[4].httpBody!) as! [String:Any];let rules=encoded["deliveryRules"] as! [String:Any];XCTAssertTrue(rules["quietHours"] is NSNull);XCTAssertNil(encoded["externalChannels"]) let registration=try JSONSerialization.jsonObject(with:requests[6].httpBody!) as! [String:Any];XCTAssertEqual(registration["action"] as? String,"register-mobile");XCTAssertEqual(registration["installationId"] as? String,"i") c.close() } func testErrorHasCodeAndNoWriteRetry() async throws { var requests=0;StubProtocol.handler={_ in requests+=1;return (401,#"{"error":{"code":"recipient-token-invalid","message":"Expired"}}"#)} let config=URLSessionConfiguration.ephemeral;config.protocolClasses=[StubProtocol.self];let session=URLSession(configuration:config);defer{session.invalidateAndCancel()} let c=try InboxClient(session:session,token:{"expired"});defer{c.close()} do {_=try await c.archive("m");XCTFail()} catch let e as InboxError {XCTAssertEqual(e.status,401);XCTAssertEqual(e.code,"recipient-token-invalid")};XCTAssertEqual(requests,1) } }