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

RadrootsBackgroundTransferTesting.swift (8708B)


      1 import Foundation
      2 import RadrootsKit
      3 
      4 public actor RadrootsInMemoryBackgroundTransferStore: RadrootsBackgroundTransferStore {
      5     private var admissions: Set<RadrootsBackgroundTransferIdentifier> = []
      6 
      7     public func withAdmission<Result: Sendable>(
      8         for identifier: RadrootsBackgroundTransferIdentifier,
      9         operation: @escaping @Sendable () async throws -> Result
     10     ) async throws -> Result {
     11         guard admissions.insert(identifier).inserted else { throw RadrootsBackgroundTransferError.invalidRequest }
     12         defer { admissions.remove(identifier) }
     13         return try await operation()
     14     }
     15 
     16     public func admissionIsActive(for identifier: RadrootsBackgroundTransferIdentifier) async throws -> Bool {
     17         admissions.contains(identifier)
     18     }
     19 
     20     private var snapshotsByIdentifier: [RadrootsBackgroundTransferIdentifier: RadrootsBackgroundTransferSnapshot]
     21 
     22     public func compareExchangeSnapshot(
     23         expected: RadrootsBackgroundTransferSnapshot?, desired: RadrootsBackgroundTransferSnapshot
     24     ) async throws -> Bool {
     25         guard expected == nil || expected?.identifier == desired.identifier else {
     26             throw RadrootsBackgroundTransferError.invalidRequest
     27         }
     28         guard snapshotsByIdentifier[desired.identifier] == expected else { return false }
     29         snapshotsByIdentifier[desired.identifier] = desired
     30         return true
     31     }
     32 
     33     public init(snapshots: [RadrootsBackgroundTransferSnapshot] = []) {
     34         snapshotsByIdentifier = Dictionary(uniqueKeysWithValues: snapshots.map { ($0.identifier, $0) })
     35     }
     36 
     37     public func loadSnapshots() async throws -> [RadrootsBackgroundTransferSnapshot] {
     38         snapshotsByIdentifier.values.sorted { left, right in
     39             left.identifier < right.identifier
     40         }
     41     }
     42 
     43     public func saveSnapshot(_ snapshot: RadrootsBackgroundTransferSnapshot) async throws {
     44         snapshotsByIdentifier[snapshot.identifier] = snapshot
     45     }
     46 
     47     public func removeSnapshot(for identifier: RadrootsBackgroundTransferIdentifier) async throws {
     48         snapshotsByIdentifier.removeValue(forKey: identifier)
     49     }
     50 
     51     public func removeAllSnapshots() async throws {
     52         snapshotsByIdentifier.removeAll()
     53     }
     54 }
     55 
     56 public actor RadrootsFakeBackgroundTransfer: RadrootsBackgroundTransfer {
     57     private let store: any RadrootsBackgroundTransferStore
     58     private var enqueueOutcome: Result<Void, RadrootsBackgroundTransferError>
     59     private var enqueuedRequestsValue: [RadrootsBackgroundTransferRequest]
     60     private var cancelledIdentifiersValue: [RadrootsBackgroundTransferIdentifier]
     61     private var handledBackgroundEventIdentifiersValue: [String]
     62     private let updatedAt: Date
     63 
     64     public init(
     65         store: any RadrootsBackgroundTransferStore = RadrootsInMemoryBackgroundTransferStore(),
     66         enqueueOutcome: Result<Void, RadrootsBackgroundTransferError> = .success(()),
     67         updatedAt: Date = Date(timeIntervalSince1970: 0)
     68     ) {
     69         self.store = store
     70         self.enqueueOutcome = enqueueOutcome
     71         enqueuedRequestsValue = []
     72         cancelledIdentifiersValue = []
     73         handledBackgroundEventIdentifiersValue = []
     74         self.updatedAt = updatedAt
     75     }
     76 
     77     public func setEnqueueOutcome(_ outcome: Result<Void, RadrootsBackgroundTransferError>) {
     78         enqueueOutcome = outcome
     79     }
     80 
     81     public func enqueue(_ request: RadrootsBackgroundTransferRequest) async throws
     82         -> RadrootsBackgroundTransferHandle
     83     {
     84         enqueuedRequestsValue.append(request)
     85         switch enqueueOutcome {
     86         case .success:
     87             let snapshot = try RadrootsBackgroundTransferSnapshot(
     88                 request: request,
     89                 state: .queued,
     90                 updatedAt: updatedAt
     91             )
     92             try await store.saveSnapshot(snapshot)
     93             return RadrootsBackgroundTransferHandle(request: request)
     94         case let .failure(error):
     95             throw error
     96         }
     97     }
     98 
     99     public func retry(_ request: RadrootsBackgroundTransferRequest) async throws
    100         -> RadrootsBackgroundTransferHandle
    101     {
    102         try await enqueue(request)
    103     }
    104 
    105     public func cancel(_ identifier: RadrootsBackgroundTransferIdentifier) async throws {
    106         cancelledIdentifiersValue.append(identifier)
    107         if let existing = try await store.loadSnapshots().first(where: { $0.identifier == identifier }) {
    108             let snapshot = try RadrootsBackgroundTransferSnapshot(
    109                 request: existing.request,
    110                 state: .cancelled,
    111                 progress: existing.progress,
    112                 updatedAt: updatedAt
    113             )
    114             try await store.saveSnapshot(snapshot)
    115         }
    116     }
    117 
    118     public func expire(_ identifier: RadrootsBackgroundTransferIdentifier) async throws {
    119         cancelledIdentifiersValue.append(identifier)
    120         if let existing = try await store.loadSnapshots().first(where: { $0.identifier == identifier }) {
    121             try await store.saveSnapshot(
    122                 RadrootsBackgroundTransferSnapshot(
    123                     request: existing.request,
    124                     state: .expired,
    125                     progress: existing.progress,
    126                     failure: .expired,
    127                     updatedAt: updatedAt
    128                 )
    129             )
    130         }
    131     }
    132 
    133     public func settle(
    134         _ identifier: RadrootsBackgroundTransferIdentifier,
    135         verification: RadrootsBackgroundTransferVerification
    136     ) async throws {
    137         guard
    138             let existing = try await store.loadSnapshots().first(where: { $0.identifier == identifier })
    139         else {
    140             throw RadrootsBackgroundTransferError.transferFailure
    141         }
    142         switch verification {
    143         case .accepted:
    144             try await store.saveSnapshot(
    145                 RadrootsBackgroundTransferSnapshot(
    146                     request: existing.request,
    147                     state: .completed,
    148                     progress: existing.progress,
    149                     response: existing.response,
    150                     downloadedArtifact: existing.downloadedArtifact,
    151                     updatedAt: updatedAt
    152                 )
    153             )
    154         case let .rejected(failure):
    155             try await store.saveSnapshot(
    156                 RadrootsBackgroundTransferSnapshot(
    157                     request: existing.request,
    158                     state: .failed,
    159                     progress: existing.progress,
    160                     failure: failure,
    161                     updatedAt: updatedAt
    162                 )
    163             )
    164         }
    165     }
    166 
    167     public func complete(_ identifier: RadrootsBackgroundTransferIdentifier) async throws {
    168         guard
    169             let existing = try await store.loadSnapshots().first(where: { $0.identifier == identifier })
    170         else {
    171             throw RadrootsBackgroundTransferError.transferFailure
    172         }
    173         let snapshot = try RadrootsBackgroundTransferSnapshot(
    174             request: existing.request,
    175             state: .completed,
    176             progress: existing.progress,
    177             updatedAt: updatedAt
    178         )
    179         try await store.saveSnapshot(snapshot)
    180     }
    181 
    182     public func fail(
    183         _ identifier: RadrootsBackgroundTransferIdentifier,
    184         failure: RadrootsBackgroundTransferFailure
    185     ) async throws {
    186         guard
    187             let existing = try await store.loadSnapshots().first(where: { $0.identifier == identifier })
    188         else {
    189             throw RadrootsBackgroundTransferError.transferFailure
    190         }
    191         let snapshot = try RadrootsBackgroundTransferSnapshot(
    192             request: existing.request,
    193             state: .failed,
    194             progress: existing.progress,
    195             failure: failure,
    196             updatedAt: updatedAt
    197         )
    198         try await store.saveSnapshot(snapshot)
    199     }
    200 
    201     public func snapshot(for identifier: RadrootsBackgroundTransferIdentifier) async throws
    202         -> RadrootsBackgroundTransferSnapshot?
    203     {
    204         try await store.loadSnapshots().first { $0.identifier == identifier }
    205     }
    206 
    207     public func snapshots() async throws -> [RadrootsBackgroundTransferSnapshot] {
    208         try await store.loadSnapshots()
    209     }
    210 
    211     public func handleEventsForBackgroundURLSession(
    212         identifier: String,
    213         completionHandler: @escaping @Sendable () -> Void
    214     ) async {
    215         handledBackgroundEventIdentifiersValue.append(identifier)
    216         completionHandler()
    217     }
    218 
    219     public var enqueuedRequests: [RadrootsBackgroundTransferRequest] {
    220         enqueuedRequestsValue
    221     }
    222 
    223     public var cancelledIdentifiers: [RadrootsBackgroundTransferIdentifier] {
    224         cancelledIdentifiersValue
    225     }
    226 
    227     public var handledBackgroundEventIdentifiers: [String] {
    228         handledBackgroundEventIdentifiersValue
    229     }
    230 }