RadrootsInactiveExecutionTests.swift (8551B)
1 import Foundation 2 @testable import RadrootsKit 3 import RadrootsKitTesting 4 import Testing 5 6 @Test func inactiveExecutionRefusesActiveTerminalSnapshots() async throws { 7 let request = try appleUploadRequest(identifier: "renewal.active") 8 for state: RadrootsBackgroundTransferState in [.failed, .expired, .cancelled, .interrupted] { 9 let snapshot = try RadrootsBackgroundTransferSnapshot(request: request, state: state, executionID: UUID()) 10 let store = RadrootsInMemoryBackgroundTransferStore(snapshots: [snapshot]) 11 let probe = RadrootsAppleBackgroundTransferProbe(activeIdentifiers: [request.identifier]) 12 let transfer = RadrootsAppleBackgroundTransfer(store: store, adapters: probe.adapters()) 13 let called = RadrootsCompletionProbe() 14 await #expect(throws: RadrootsBackgroundTransferError.transferFailure) { 15 try await transfer.withInactiveExecution(for: request.identifier) { _ in called.markCompleted() } 16 } 17 #expect(!called.completed) 18 #expect(try await store.loadSnapshots() == [snapshot]) 19 #expect(await probe.enqueuedRequests.isEmpty) 20 #expect(await probe.cancelledIdentifiers.isEmpty) 21 #expect(try await !store.admissionIsActive(for: request.identifier)) 22 } 23 } 24 25 @Test func inactiveExecutionRefusesUnreconciledOrReceiptedSnapshots() async throws { 26 let request = try appleUploadRequest(identifier: "renewal.receipt") 27 for state: RadrootsBackgroundTransferState in [.queued, .running, .awaitingVerification, .completed] { 28 let snapshot = try RadrootsBackgroundTransferSnapshot(request: request, state: state) 29 let store = RadrootsInMemoryBackgroundTransferStore(snapshots: [snapshot]) 30 let transfer = RadrootsAppleBackgroundTransfer(store: store, adapters: RadrootsAppleBackgroundTransferProbe().adapters()) 31 let called = RadrootsCompletionProbe() 32 await #expect(throws: RadrootsBackgroundTransferError.transferFailure) { 33 try await transfer.withInactiveExecution(for: request.identifier) { _ in called.markCompleted() } 34 } 35 #expect(!called.completed) 36 #expect(try await store.loadSnapshots() == [snapshot]) 37 } 38 } 39 40 @Test func inactiveExecutionUnknownInventoryPreservesEvidence() async throws { 41 let request = try appleUploadRequest(identifier: "renewal.unknown") 42 let snapshot = try RadrootsBackgroundTransferSnapshot(request: request, state: .expired) 43 let store = RadrootsInMemoryBackgroundTransferStore(snapshots: [snapshot]) 44 let called = RadrootsCompletionProbe() 45 let transfer = RadrootsAppleBackgroundTransfer(store: store, adapters: .unavailable) 46 await #expect(throws: RadrootsBackgroundTransferError.unavailable) { 47 try await transfer.withInactiveExecution(for: request.identifier) { _ in called.markCompleted() } 48 } 49 #expect(!called.completed) 50 #expect(try await store.loadSnapshots() == [snapshot]) 51 #expect(try await !store.admissionIsActive(for: request.identifier)) 52 } 53 54 @Test func inactiveExecutionSuppliesExactRedactedSnapshotOrAbsenceThroughExistential() async throws { 55 let request = try appleUploadRequest(identifier: "renewal.inactive") 56 let snapshot = try RadrootsBackgroundTransferSnapshot(request: request, state: .expired, executionID: UUID()) 57 for snapshots in [[], [snapshot]] { 58 let store = RadrootsInMemoryBackgroundTransferStore(snapshots: snapshots) 59 let probe = RadrootsAppleBackgroundTransferProbe() 60 let transfer: any RadrootsBackgroundTransfer = RadrootsAppleBackgroundTransfer(store: store, adapters: probe.adapters()) 61 let result = try await transfer.withInactiveExecution(for: request.identifier) { observed in 62 #expect(observed == snapshots.first) 63 let reserved = try await store.admissionIsActive(for: request.identifier) 64 #expect(reserved) 65 return "caller-result" 66 } 67 #expect(result == "caller-result") 68 #expect(try await store.loadSnapshots() == snapshots) 69 #expect(await probe.enqueuedRequests.isEmpty) 70 #expect(await probe.cancelledIdentifiers.isEmpty) 71 #expect(try await !store.admissionIsActive(for: request.identifier)) 72 } 73 } 74 75 @Test func inactiveExecutionFencesIndependentOwnersAndRetainsLateReceipt() async throws { 76 let roots = try appleTransferRoots() 77 defer { try? FileManager.default.removeItem(at: roots.dataRoot.deletingLastPathComponent()) } 78 let request = try appleUploadRequest(identifier: "renewal.exclusive") 79 let snapshot = try RadrootsBackgroundTransferSnapshot(request: request, state: .interrupted, executionID: UUID()) 80 let late = try snapshot.transitioned(to: .awaitingVerification, at: Date()) 81 let firstStore = RadrootsAppleBackgroundTransferStore(roots: roots) 82 let otherStore = RadrootsAppleBackgroundTransferStore(roots: roots) 83 try await firstStore.saveSnapshot(snapshot) 84 let probe = RadrootsAppleBackgroundTransferProbe() 85 let first = RadrootsAppleBackgroundTransfer(store: firstStore, adapters: probe.adapters()) 86 let other = RadrootsAppleBackgroundTransfer(store: otherStore, adapters: probe.adapters()) 87 let result = try await first.withInactiveExecution(for: request.identifier) { observed in 88 #expect(observed == snapshot) 89 await #expect(throws: RadrootsBackgroundTransferError.invalidRequest) { _ = try await other.retry(request) } 90 await #expect(throws: RadrootsBackgroundTransferError.invalidRequest) { 91 try await other.withInactiveExecution(for: request.identifier) { _ in Issue.record("Competing renewal admitted") } 92 } 93 await #expect(throws: RadrootsBackgroundTransferError.invalidRequest) { _ = try await first.retry(request) } 94 // Delegate persistence remains available during the long reservation. 95 let retained = try await otherStore.compareExchangeSnapshot(expected: snapshot, desired: late) 96 #expect(retained) 97 return 42 98 } 99 #expect(result == 42) 100 #expect(try await otherStore.loadSnapshots() == [late]) 101 #expect(try await !otherStore.admissionIsActive(for: request.identifier)) 102 #expect(await probe.enqueuedRequests.isEmpty) 103 await #expect(throws: RadrootsBackgroundTransferError.transferFailure) { 104 try await other.withInactiveExecution(for: request.identifier) { _ in Issue.record("Receipt discarded") } 105 } 106 } 107 108 @Test func inactiveExecutionCallerDenialReleasesAdmissionAndPreservesError() async throws { 109 let request = try appleUploadRequest(identifier: "renewal.denied") 110 let snapshot = try RadrootsBackgroundTransferSnapshot(request: request, state: .expired) 111 let store = RadrootsInMemoryBackgroundTransferStore(snapshots: [snapshot]) 112 let probe = RadrootsAppleBackgroundTransferProbe() 113 let transfer = RadrootsAppleBackgroundTransfer(store: store, adapters: probe.adapters()) 114 await #expect(throws: RenewalTestDenial.denied) { 115 try await transfer.withInactiveExecution(for: request.identifier) { _ in throw RenewalTestDenial.denied } 116 } 117 #expect(try await store.loadSnapshots() == [snapshot]) 118 #expect(try await !store.admissionIsActive(for: request.identifier)) 119 _ = try await transfer.retry(request) 120 #expect(await probe.enqueuedRequests == [request]) 121 } 122 123 @Test func inactiveExecutionCancellationDuringQueryNeverCallsBody() async throws { 124 let request = try appleUploadRequest(identifier: "renewal.cancelled-query") 125 let store = RadrootsInMemoryBackgroundTransferStore() 126 let called = RadrootsCompletionProbe() 127 let (entered, signal) = AsyncStream<Void>.makeStream() 128 let transfer = RadrootsAppleBackgroundTransfer(store: store, adapters: RadrootsAppleBackgroundTransferAdapters( 129 enqueue: { _, _ in Issue.record("Unexpected enqueue") }, cancel: { _ in Issue.record("Unexpected cancel") }, 130 activeTransferIdentifiers: { 131 try await RadrootsBackgroundTaskQuery.read { _ in signal.yield(); signal.finish() } 132 }, handleBackgroundEvents: { _, completion in completion() } 133 )) 134 let task = Task { try await transfer.withInactiveExecution(for: request.identifier) { _ in called.markCompleted() } } 135 for await _ in entered { 136 break 137 } 138 task.cancel() 139 await #expect(throws: RadrootsBackgroundTransferError.transferFailure) { try await task.value } 140 #expect(!called.completed) 141 #expect(try await !store.admissionIsActive(for: request.identifier)) 142 #expect(try await store.loadSnapshots().isEmpty) 143 } 144 145 private enum RenewalTestDenial: Error, Equatable { case denied }