RadrootsNativeDestinationCallbackTests.swift (6100B)
1 #if os(iOS) && targetEnvironment(simulator) 2 import Foundation 3 @testable import RadrootsKit 4 import RadrootsKitTesting 5 import Testing 6 7 @Test func nativeDestinationRejectsNewPublicEnqueueBeforeTaskCreation() async throws { 8 let roots = try appleTransferRoots() 9 defer { try? FileManager.default.removeItem(at: roots.dataRoot.deletingLastPathComponent()) } 10 let store = RadrootsInMemoryBackgroundTransferStore() 11 let adapters = try RadrootsAppleBackgroundTransferAdapters.live( 12 sessionIdentifier: "org.radroots.public-refusal.\(UUID().uuidString.lowercased())", store: store, 13 fileResolver: RadrootsAppleBackgroundTransferFileResolver(roots: roots), 14 downloadStagingRoot: roots.temporaryRoot 15 ) 16 let request = try RadrootsBackgroundTransferRequest( 17 identifier: RadrootsBackgroundTransferIdentifier("public.refused"), 18 remoteURL: #require(URL(string: "https://example.org/upload")), method: .put, 19 operation: .upload(source: .file(RadrootsFileReference(scope: .cache, relativePath: "absent"))) 20 ) 21 await #expect(throws: RadrootsBackgroundTransferError.unavailable) { 22 try await adapters.enqueue(request, UUID()) 23 } 24 #expect(try await adapters.activeTransferIdentifiers().isEmpty) 25 #expect(try await store.loadSnapshots().isEmpty) 26 } 27 28 @Test(arguments: ["same_host", "cross_host", "cross_port"]) 29 func nativeDestinationRefusesActualForegroundRedirect(_ kind: String) async throws { 30 let server = try NativeRedirectServer() 31 let target = try NativeRedirectServer() 32 defer { server.stop(); target.stop() } 33 let port = try await server.start() 34 let targetPort = try await target.start() 35 let destinations = ["same_host": "http://127.0.0.1:\(port)/other", 36 "cross_host": "http://localhost:\(targetPort)/other", 37 "cross_port": "http://127.0.0.1:\(targetPort)/other"] 38 try server.redirect(to: #require(destinations[kind])) 39 let roots = try appleTransferRoots() 40 defer { try? FileManager.default.removeItem(at: roots.dataRoot.deletingLastPathComponent()) } 41 let source = RadrootsFileReference(scope: .cache, relativePath: "redirect_body") 42 try RadrootsAppleFileAccess(roots: roots).write(.inline(Data("isolated fixture body".utf8)), to: source) 43 let store = RadrootsAppleBackgroundTransferStore(roots: roots) 44 let adapters = try RadrootsAppleBackgroundTransferAdapters.live( 45 sessionIdentifier: "org.radroots.redirect.\(UUID().uuidString.lowercased())", store: store, 46 fileResolver: RadrootsAppleBackgroundTransferFileResolver(roots: roots), 47 downloadStagingRoot: roots.temporaryRoot 48 ) 49 let transfer = RadrootsAppleBackgroundTransfer(store: store, adapters: adapters) 50 let request = try RadrootsBackgroundTransferRequest( 51 identifier: RadrootsBackgroundTransferIdentifier("redirect.\(kind)"), 52 remoteURL: #require(URL(string: "http://127.0.0.1:\(port)/upload")), method: .put, 53 operation: .upload(source: .file(source)), networkPolicy: .simulatorLoopbackHTTP 54 ) 55 _ = try await transfer.enqueue(request) 56 let deadline = Date().addingTimeInterval(15) 57 var snapshot = try await store.loadSnapshots().first 58 while snapshot?.state == .queued || snapshot?.state == .running { 59 guard Date() < deadline else { throw RadrootsBackgroundTransferError.transferFailure } 60 try await Task.sleep(for: .milliseconds(25)) 61 snapshot = try await store.loadSnapshots().first 62 } 63 #expect(snapshot?.state == .failed) 64 #expect(snapshot?.failure == .httpStatus) 65 #expect(server.requestCount == 1) 66 #expect(target.requestCount == 0) 67 } 68 69 @Test func nativeDestinationDelegateRejectsDowngradeAndResponseMismatch() throws { 70 let roots = try appleTransferRoots() 71 defer { try? FileManager.default.removeItem(at: roots.dataRoot.deletingLastPathComponent()) } 72 let coordinator = RadrootsTransferCoordinator(sessionIdentifier: "callback.tests", 73 store: RadrootsInMemoryBackgroundTransferStore(), 74 fileResolver: RadrootsAppleBackgroundTransferFileResolver( 75 roots: roots 76 )) 77 let delegate = RadrootsTransferSessionDelegate(coordinator: coordinator, 78 downloadStagingRoot: roots.temporaryRoot, fileManager: .default) 79 let session = URLSession(configuration: .ephemeral) 80 defer { session.invalidateAndCancel() } 81 let url = try #require(URL(string: "https://example.org/upload")) 82 let task = session.dataTask(with: url) 83 let redirect = try #require(HTTPURLResponse(url: url, statusCode: 307, httpVersion: nil, headerFields: nil)) 84 var called = false 85 let downgrade = try #require(URL(string: "http://example.org/upload")) 86 delegate.urlSession(session, task: task, willPerformHTTPRedirection: redirect, 87 newRequest: URLRequest(url: downgrade)) { 88 called = true 89 #expect($0 == nil) 90 } 91 #expect(called) 92 for raw in ["https://other.org/upload", "https://example.org/other", "http://example.org/upload"] { 93 let responseURL = try #require(URL(string: raw)) 94 let response = try #require(HTTPURLResponse(url: responseURL, statusCode: 200, 95 httpVersion: nil, headerFields: nil)) 96 var received = false 97 delegate.urlSession(session, dataTask: task, didReceive: response) { 98 received = true 99 #expect($0 == .cancel) 100 } 101 #expect(received) 102 } 103 } 104 105 #endif