Release recipient inbox SDKs 0.1.0 preview

This commit is contained in:
WRNexus SDK Publisher committed 2026-10-09 14:27:00 +05:30
commit d0781a9f3b
23 files changed
+1846

No files matched your search

+26
View File
@@ -0,0 +1,26 @@
import XCTest
@testable import WRNexusInbox
import Foundation
#if canImport(FoundationNetworking)
import FoundationNetworking
#endif
final class InboxTests: XCTestCase {
func testEndpointAndActionValidation() throws {
for text in ["http://example.com", "https://u:p@example.com", "https://example.com/a", "https://example.com?q=x"] { XCTAssertThrowsError(try InboxClient(endpoint:URL(string:text)!,token:{"recipient"})) }
XCTAssertNoThrow(try InboxClient(endpoint:URL(string:"http://127.0.0.1:1234")!,token:{"recipient"}))
XCTAssertNil(InboxClient.safeActionURL("javascript:alert(1)")); XCTAssertNil(InboxClient.safeActionURL("https://u:p@example.com")); XCTAssertNotNil(InboxClient.safeActionURL("https://example.com/path"))
}
func testSnakeCaseAndNulls() throws {
let text=#"{"messages":[{"id":"m","recipient_id":"r","title":"Title","body":"Body","purpose":"transactional","category":"orders","url":null,"actions":[],"created_at":"2026-10-09","read_at":null,"archived_at":null}],"unreadCount":1}"#
let value=try InboxClient.decoder().decode(InboxSnapshot.self,from:Data(text.utf8)); XCTAssertEqual(value.messages[0].recipientId,"r"); XCTAssertNil(value.messages[0].readAt)
let data=try JSONEncoder().encode(DeliveryRules(timezone:"UTC")); let rules=try JSONSerialization.jsonObject(with:data) as! [String:Any]; XCTAssertTrue(rules["quietHours"] is NSNull); XCTAssertTrue(rules["marketingDailyLimit"] is NSNull)
}
func testSSEFrames() throws {
var p=InboxEventParser(); XCTAssertNil(try p.line(": heartbeat")); XCTAssertNil(try p.line("")); XCTAssertNil(try p.line("event: inbox\r")); XCTAssertNil(try p.line(#"data: {"messages":[],"unreadCount":4}"#)); XCTAssertEqual(try p.line("")?.unreadCount,4)
_=try p.line("event: inbox"); _=try p.line("data: broken"); XCTAssertThrowsError(try p.line(""))
}
func testClosedClientCannotRequest() async throws {
let client=try InboxClient(token:{"recipient"}); client.close()
do {_=try await client.messages(); XCTFail("closed client requested")} catch let error as InboxError {XCTAssertEqual(error.code,"closed")}
}
}
@@ -0,0 +1,49 @@
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)
}
}