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

RadrootsBackgroundTransferTestSupport.swift (6050B)


      1 import Darwin
      2 import Foundation
      3 @testable import RadrootsKit
      4 import RadrootsKitTesting
      5 import Testing
      6 
      7 actor RadrootsAppleBackgroundTransferProbe {
      8     private let nowValue: Date
      9     private let enqueueOutcome: Result<Void, RadrootsBackgroundTransferError>
     10     private var activeIdentifiersValue: Set<RadrootsBackgroundTransferIdentifier>
     11     private var enqueuedRequestsValue: [RadrootsBackgroundTransferRequest]
     12     private var cancelledIdentifiersValue: [RadrootsBackgroundTransferIdentifier]
     13     private var handledBackgroundEventIdentifiersValue: [String]
     14 
     15     init(
     16         now: Date = Date(timeIntervalSince1970: 0),
     17         enqueueOutcome: Result<Void, RadrootsBackgroundTransferError> = .success(()),
     18         activeIdentifiers: Set<RadrootsBackgroundTransferIdentifier> = []
     19     ) {
     20         nowValue = now
     21         self.enqueueOutcome = enqueueOutcome
     22         activeIdentifiersValue = activeIdentifiers
     23         enqueuedRequestsValue = []
     24         cancelledIdentifiersValue = []
     25         handledBackgroundEventIdentifiersValue = []
     26     }
     27 
     28     nonisolated func adapters() -> RadrootsAppleBackgroundTransferAdapters {
     29         RadrootsAppleBackgroundTransferAdapters(
     30             now: { self.nowValue }, enqueue: { request, _ in try await self.enqueue(request) },
     31             cancel: { identifier in await self.cancel(identifier) },
     32             activeTransferIdentifiers: { await self.activeIdentifiers() },
     33             handleBackgroundEvents: { identifier, completionHandler in
     34                 await self.handleBackgroundEvents(
     35                     identifier: identifier, completionHandler: completionHandler
     36                 )
     37             }
     38         )
     39     }
     40 
     41     private func enqueue(_ request: RadrootsBackgroundTransferRequest) throws {
     42         enqueuedRequestsValue.append(request)
     43         switch enqueueOutcome {
     44         case .success: activeIdentifiersValue.insert(request.identifier)
     45         case let .failure(error): throw error
     46         }
     47     }
     48 
     49     private func cancel(_ identifier: RadrootsBackgroundTransferIdentifier) {
     50         cancelledIdentifiersValue.append(identifier)
     51         activeIdentifiersValue.remove(identifier)
     52     }
     53 
     54     private func activeIdentifiers() -> Set<RadrootsBackgroundTransferIdentifier> {
     55         activeIdentifiersValue
     56     }
     57 
     58     private func handleBackgroundEvents(
     59         identifier: String, completionHandler: @escaping @Sendable () -> Void
     60     ) {
     61         handledBackgroundEventIdentifiersValue.append(identifier)
     62         completionHandler()
     63     }
     64 
     65     var enqueuedRequests: [RadrootsBackgroundTransferRequest] {
     66         enqueuedRequestsValue
     67     }
     68 
     69     var cancelledIdentifiers: [RadrootsBackgroundTransferIdentifier] {
     70         cancelledIdentifiersValue
     71     }
     72 
     73     var handledBackgroundEventIdentifiers: [String] {
     74         handledBackgroundEventIdentifiersValue
     75     }
     76 }
     77 
     78 final class RadrootsCompletionProbe: @unchecked Sendable {
     79     private let lock = NSLock()
     80     private var completionCountValue = 0
     81 
     82     func markCompleted() {
     83         lock.lock()
     84         completionCountValue += 1
     85         lock.unlock()
     86     }
     87 
     88     var completed: Bool {
     89         completionCount > 0
     90     }
     91 
     92     var completionCount: Int {
     93         lock.lock()
     94         defer { lock.unlock() }
     95         return completionCountValue
     96     }
     97 }
     98 
     99 final class RadrootsProtectedDataProbe: @unchecked Sendable {
    100     private let lock = NSLock()
    101     private var stateValue: RadrootsProtectedDataState
    102 
    103     init(state: RadrootsProtectedDataState) {
    104         stateValue = state
    105     }
    106 
    107     var state: RadrootsProtectedDataState {
    108         get {
    109             lock.lock()
    110             defer { lock.unlock() }
    111             return stateValue
    112         }
    113         set {
    114             lock.lock()
    115             stateValue = newValue
    116             lock.unlock()
    117         }
    118     }
    119 }
    120 
    121 func appleTransferRequest(identifier: String) throws -> RadrootsBackgroundTransferRequest {
    122     try RadrootsBackgroundTransferRequest(
    123         identifier: RadrootsBackgroundTransferIdentifier(identifier),
    124         remoteURL: URL(string: "https://radroots.org/\(identifier).json")!,
    125         method: .get,
    126         operation: .download(
    127             destination: .file(RadrootsFileReference(scope: .cache, relativePath: "\(identifier).json"))
    128         )
    129     )
    130 }
    131 
    132 func appleUploadRequest(
    133     identifier: String,
    134     responsePolicy: RadrootsBackgroundTransferResponsePolicy = .discard,
    135     maximumTransferBytes: UInt64 = RadrootsBackgroundTransferRequest.defaultMaximumTransferBytes
    136 ) throws
    137     -> RadrootsBackgroundTransferRequest {
    138     try RadrootsBackgroundTransferRequest(
    139         identifier: RadrootsBackgroundTransferIdentifier(identifier),
    140         remoteURL: URL(string: "https://radroots.org/\(identifier).json")!,
    141         method: .put,
    142         operation: .upload(
    143             source: .file(RadrootsFileReference(scope: .cache, relativePath: "\(identifier).json"))
    144         ),
    145         responsePolicy: responsePolicy, maximumTransferBytes: maximumTransferBytes
    146     )
    147 }
    148 
    149 func successfulHTTPResult() -> RadrootsBackgroundHTTPResult {
    150     RadrootsBackgroundHTTPResult(statusCode: 200, mediaType: nil, body: nil, bodyExceeded: false)
    151 }
    152 
    153 func appleTransferRoots() throws -> RadrootsAppleFileRoots {
    154     let unresolvedRoot = FileManager.default.temporaryDirectory.appendingPathComponent(
    155         "radroots-apple-background-transfer-\(UUID().uuidString)", isDirectory: true
    156     )
    157     try FileManager.default.createDirectory(at: unresolvedRoot, withIntermediateDirectories: false)
    158     guard let resolvedPointer = unresolvedRoot.path.withCString({ Darwin.realpath($0, nil) }) else {
    159         throw RadrootsBackgroundTransferError.persistenceFailure
    160     }
    161     defer { Darwin.free(resolvedPointer) }
    162     let root = URL(fileURLWithPath: String(cString: resolvedPointer), isDirectory: true)
    163     return try RadrootsAppleFileRoots(
    164         appIdentifier: "org.radroots.tests",
    165         dataRoot: root.appendingPathComponent("data", isDirectory: true),
    166         cacheRoot: root.appendingPathComponent("cache", isDirectory: true),
    167         temporaryRoot: root.appendingPathComponent("tmp", isDirectory: true)
    168     )
    169 }