apple_kit

Apple-native services for Radroots iOS and macOS apps
git clone https://radroots.dev/git/apple_kit.git
Log | Files | Refs | README | LICENSE

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 }