import Foundation import Testing @testable import Sybil private final class PushURLProtocol: URLProtocol, @unchecked Sendable { static let lock = NSLock() nonisolated(unsafe) private static var handler: (@Sendable (URLRequest) throws -> (Int, Data))? static func install(_ value: @escaping @Sendable (URLRequest) throws -> (Int, Data)) { lock.withLock { handler = value } } override class func canInit(with request: URLRequest) -> Bool { true } override class func canonicalRequest(for request: URLRequest) -> URLRequest { request } override func startLoading() { do { let handler = Self.lock.withLock { Self.handler! } let (status, data) = try handler(request) client?.urlProtocol(self, didReceive: HTTPURLResponse(url: request.url!, statusCode: status, httpVersion: nil, headerFields: ["Location": "https://untrusted.test/"])!, cacheStoragePolicy: .notAllowed) client?.urlProtocol(self, didLoad: data); client?.urlProtocolDidFinishLoading(self) } catch { client?.urlProtocol(self, didFailWithError: error) } } override func stopLoading() {} } private func pushClient(_ url: String = "https://example.test/api", token: String? = "fixture-token") -> SybilAPIClient { let config = URLSessionConfiguration.ephemeral; config.protocolClasses = [PushURLProtocol.self] return SybilAPIClient(configuration: .init(baseURL: URL(string: url)!, authToken: token), session: URLSession(configuration: config)) } private func pushRequestBody(_ request: URLRequest) -> Data { if let body = request.httpBody { return body } guard let stream = request.httpBodyStream else { return Data() } stream.open(); defer { stream.close() } var bytes = Data(), buffer = [UInt8](repeating: 0, count: 1024) while stream.hasBytesAvailable { let count = stream.read(&buffer, maxLength: buffer.count); if count <= 0 { break }; bytes.append(contentsOf: buffer.prefix(count)) } return bytes } @Suite(.serialized) struct AgentPushAPITests { @Test func authenticatedBasePathRegistrationAndRevoke() async throws { let id = UUID() PushURLProtocol.install { request in #expect(request.url?.host == "example.test") #expect(request.url?.query == nil) #expect(request.cachePolicy == .reloadIgnoringLocalCacheData) #expect(request.value(forHTTPHeaderField: "Authorization") == "Bearer fixture-token") #expect(request.value(forHTTPHeaderField: "Cache-Control") == "no-store") #expect(request.timeoutInterval == 8) if request.httpMethod == "GET" { #expect(request.url?.path == "/api/v1/agent/push/config") return (200, Data(#"{"version":1,"enabled":true,"apnsAvailable":true,"webPushAvailable":false,"sourceId":"11111111-1111-4111-8111-111111111111","quarantined":false,"policy":{"future":1}}"#.utf8)) } #expect(request.url?.path == "/api/v1/agent/push/installations/" + id.uuidString.lowercased()) if request.httpMethod == "DELETE" { return (204, Data()) } let object = try #require(JSONSerialization.jsonObject(with: pushRequestBody(request)) as? [String: Any]) #expect(Set(object.keys) == Set(["transport", "enabled", "token", "environment", "topic"])) #expect(object["token"] as? String == "00010aff") #expect(object["environment"] as? String == "production") #expect(object["topic"] as? String == "net.buzzert.sybil2") return (200, try JSONEncoder().encode(AgentPushReceipt(installationId: id.uuidString, transport: "apns", enabled: true, version: 7))) } let client = pushClient() #expect(try await client.agentPushConfig().available) #expect(try await client.agentRegisterPush(id: id, registration: .init(token: "00010aff", environment: .production)).version == 7) try await client.agentRevokePush(id: id) } @Test func unsupportedRedirectAndReflectedSecretsAreNotExposed() async { for status in [302, 401, 404, 409, 503] { PushURLProtocol.install { _ in (status, Data(#"{"token":"do-not-reflect-this-token","message":"do-not-reflect-this-token"}"#.utf8)) } do { _ = try await pushClient().agentPushConfig(); Issue.record("Unexpected success") } catch { #expect(!error.localizedDescription.contains("do-not-reflect")) } } PushURLProtocol.install { _ in Issue.record("Unsafe request reached transport"); return (200, Data()) } for client in [pushClient(token: nil), pushClient("https://example.test/api?token=bad"), pushClient("https://user:password@example.test")] { do { _ = try await client.agentPushConfig(); Issue.record("Unsafe configuration accepted") } catch { } } } @Test func sourceAwareReadWritesAndLegacyFallback() async throws { let source = "11111111-1111-4111-8111-111111111111" for expectedSource: String? in [nil, source] { PushURLProtocol.install { request in #expect(request.url?.path == "/api/v1/agent/read-state") let object = try #require(JSONSerialization.jsonObject(with: pushRequestBody(request)) as? [String: String]) #expect(object["cursor"] == "4" && object["sourceId"] == expectedSource) return (200, try JSONEncoder().encode(AgentReadState(cursor: "4", sourceId: expectedSource))) } #expect(try await pushClient().agentMarkRead(cursor: "4", sourceID: expectedSource).sourceId == expectedSource) } } }