RadrootsNativeResponseTests.swift (3631B)
1 #if os(iOS) && targetEnvironment(simulator) 2 import Foundation 3 @testable import RadrootsKit 4 import Testing 5 6 @Test(arguments: ["valid", "declared", "chunked", "missing", "status", "type", "shape", "encoded"]) 7 func nativeResponseEnforcesActualTransportBounds(_ name: String) async throws { 8 let server = try NativeRedirectServer() 9 defer { server.stop() } 10 let port = try await server.start() 11 server.respond(with: nativeResponseWire(name)) 12 let roots = try appleTransferRoots() 13 defer { try? FileManager.default.removeItem(at: roots.dataRoot.deletingLastPathComponent()) } 14 let source = RadrootsFileReference(scope: .cache, relativePath: "response_body") 15 try RadrootsAppleFileAccess(roots: roots).write(.inline(Data("isolated fixture body".utf8)), to: source) 16 let store = RadrootsAppleBackgroundTransferStore(roots: roots) 17 let adapters = try RadrootsAppleBackgroundTransferAdapters.live( 18 sessionIdentifier: "org.radroots.response.\(UUID().uuidString.lowercased())", store: store, 19 fileResolver: RadrootsAppleBackgroundTransferFileResolver(roots: roots), 20 downloadStagingRoot: roots.temporaryRoot 21 ) 22 let transfer = RadrootsAppleBackgroundTransfer(store: store, adapters: adapters) 23 let request = try RadrootsBackgroundTransferRequest( 24 identifier: RadrootsBackgroundTransferIdentifier("response.\(name)"), 25 remoteURL: #require(URL(string: "http://127.0.0.1:\(port)/upload")), method: .put, 26 operation: .upload(source: .file(source)), networkPolicy: .simulatorLoopbackHTTP, 27 responsePolicy: .boundedJSON(maximumBodyBytes: 32) 28 ) 29 _ = try await transfer.enqueue(request) 30 let deadline = Date().addingTimeInterval(15) 31 var snapshot = try await store.loadSnapshots().first 32 while snapshot?.state == .queued || snapshot?.state == .running { 33 guard Date() < deadline else { throw RadrootsBackgroundTransferError.transferFailure } 34 try await Task.sleep(for: .milliseconds(25)) 35 snapshot = try await store.loadSnapshots().first 36 } 37 let failures: [String: RadrootsBackgroundTransferFailure] = [ 38 "declared": .responseTooLarge, "chunked": .responseTooLarge, "missing": .responseMissing, 39 "status": .httpStatus, "type": .responseMediaType, "shape": .responseInvalid, 40 "encoded": .responseContentEncoding 41 ] 42 #expect(snapshot?.state == (name == "valid" ? .awaitingVerification : .failed)) 43 #expect(snapshot?.failure == failures[name]) 44 #expect(server.requestCount == 1) 45 if name == "valid" { 46 #expect(snapshot?.response?.body == Data("{}".utf8)) 47 } 48 } 49 50 private func nativeResponseWire(_ name: String) -> Data { 51 let status = name == "status" ? "503 Unavailable" : "200 OK" 52 let type = name == "type" ? "image/jpeg" : "application/json" 53 var headers = "HTTP/1.1 \(status)\r\nContent-Type: \(type)\r\nConnection: close\r\n" 54 if name == "encoded" { 55 headers += "Content-Encoding: gzip\r\n" 56 } 57 if name == "chunked" { 58 return Data((headers + "Transfer-Encoding: chunked\r\n\r\n20\r\n" + String(repeating: "x", count: 32) 59 + "\r\n1\r\nx\r\n0\r\n\r\n").utf8) 60 } 61 let body = name == "missing" ? "" : name == "shape" ? "[" : "{}" 62 let length = name == "declared" ? 1024 : body.utf8.count 63 return Data((headers + "Content-Length: \(length)\r\n\r\n" + body).utf8) 64 } 65 #endif