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 }