RadrootsResponseCollectorTests.swift (6234B)
1 import Foundation 2 @testable import RadrootsKit 3 import RadrootsKitTesting 4 import Testing 5 6 @Test func responseCollectorBoundsDeclaredChunkedAndDiscardedBodies() throws { 7 let url = try #require(URL(string: "https://example.org/upload")) 8 let collector = RadrootsTransferResponseCollector() 9 collector.register(8, taskIdentifier: 1) 10 let declared = try #require(HTTPURLResponse(url: url, statusCode: 200, httpVersion: nil, 11 headerFields: [ 12 "Content-Type": "application/json", 13 "Content-Length": "9" 14 ])) 15 #expect(!collector.begin(declared, taskIdentifier: 1, fallbackLimit: 8)) 16 #expect(collector.take(taskIdentifier: 1, response: declared, destinationMismatch: false).bodyExceeded) 17 collector.register(8, taskIdentifier: 2) 18 let chunked = try #require(HTTPURLResponse(url: url, statusCode: 200, httpVersion: nil, 19 headerFields: [ 20 "Content-Type": "application/json", 21 "Transfer-Encoding": "chunked" 22 ])) 23 #expect(collector.begin(chunked, taskIdentifier: 2, fallbackLimit: 8)) 24 #expect(!collector.append(Data(repeating: 1, count: 8), taskIdentifier: 2, fallbackLimit: 8)) 25 #expect(collector.append(Data([1]), taskIdentifier: 2, fallbackLimit: 8)) 26 let rejected = collector.take(taskIdentifier: 2, response: chunked, destinationMismatch: false) 27 #expect(rejected.body == nil && rejected.bodyExceeded) 28 collector.register(0, taskIdentifier: 3) 29 #expect(!collector.append(Data(repeating: 1, count: 65536), taskIdentifier: 3, fallbackLimit: 0)) 30 #expect(collector.append(Data([1]), taskIdentifier: 3, fallbackLimit: 0)) 31 #expect(collector.take(taskIdentifier: 3, response: chunked, destinationMismatch: false).body == nil) 32 } 33 34 @Test(arguments: ["gzip", "br", "identity, gzip", String(repeating: "x", count: 1024)]) 35 func responseCollectorRejectsEncodingBeforeCollecting(_ encoding: String) throws { 36 let url = try #require(URL(string: "https://example.org/upload")) 37 let response = try #require(HTTPURLResponse(url: url, statusCode: 200, httpVersion: nil, 38 headerFields: [ 39 "Content-Encoding": encoding, 40 "Content-Type": "application/json" 41 ])) 42 let collector = RadrootsTransferResponseCollector() 43 #expect(!collector.begin(response, taskIdentifier: 1, fallbackLimit: 32)) 44 #expect(collector.append(Data("{}".utf8), taskIdentifier: 1, fallbackLimit: 32)) 45 let result = collector.take(taskIdentifier: 1, response: response, destinationMismatch: false) 46 #expect(result.headerFailure == .responseContentEncoding && result.body == nil) 47 } 48 49 @Test(arguments: ["", "[]", "null", "\"scalar\"", "{broken"]) 50 func responseValidationRejectsMissingOrMalformedDescriptorShape(_ raw: String) async throws { 51 let roots = try appleTransferRoots() 52 defer { try? FileManager.default.removeItem(at: roots.dataRoot.deletingLastPathComponent()) } 53 let request = try appleUploadRequest(identifier: "response.shape", responsePolicy: .boundedJSON()) 54 let store = try RadrootsInMemoryBackgroundTransferStore(snapshots: [RadrootsBackgroundTransferSnapshot( 55 request: request, state: .running 56 )]) 57 let coordinator = RadrootsTransferCoordinator(sessionIdentifier: "shape.tests", store: store, 58 fileResolver: RadrootsAppleBackgroundTransferFileResolver( 59 roots: roots 60 )) 61 await coordinator.complete(identifier: request.identifier, completion: RadrootsTransferCompletion( 62 platformError: nil, stagedDownloadResult: nil, httpResult: RadrootsBackgroundHTTPResult( 63 statusCode: 200, 64 mediaType: "application/json", 65 body: Data(raw.utf8), 66 bodyExceeded: false 67 ), bytesTransferred: 10, 68 totalBytesExpected: 10 69 )) 70 #expect(try await store.loadSnapshots().first?.failure == .responseInvalid) 71 } 72 73 @Test func responseCollectorBoundsMetadataAndPreservesSafeVerificationFields() throws { 74 let url = try #require(URL(string: "https://example.org/upload")) 75 let collector = RadrootsTransferResponseCollector() 76 let oversized = try #require(HTTPURLResponse(url: url, statusCode: 200, httpVersion: nil, 77 headerFields: ["Content-Type": "application/json;" + String( 78 repeating: "x", 79 count: 1024 80 )])) 81 #expect(!collector.begin(oversized, taskIdentifier: 1, fallbackLimit: 32)) 82 #expect(collector.take(taskIdentifier: 1, response: oversized, destinationMismatch: false).mediaTypeWasMalformed) 83 let response = try #require(HTTPURLResponse(url: url, statusCode: 201, httpVersion: nil, 84 headerFields: [ 85 "Content-Type": "application/json", 86 "Content-Encoding": "identity", 87 "Set-Cookie": "secret-marker" 88 ])) 89 #expect(collector.begin(response, taskIdentifier: 2, fallbackLimit: 32)) 90 #expect(!collector.append(Data("{}".utf8), taskIdentifier: 2, fallbackLimit: 32)) 91 let receipt = collector.take(taskIdentifier: 2, response: response, destinationMismatch: false) 92 #expect(receipt.statusCode == 201 && receipt.mediaType == "application/json" && receipt 93 .contentEncoding == "identity") 94 #expect(receipt.body == Data("{}".utf8)) 95 #expect(!String(reflecting: receipt).contains("secret-marker")) 96 }