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

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