TeraUploadRenewalTests.swift (5797B)
1 import Foundation 2 import RadrootsKit 3 @testable import TeraApp 4 import XCTest 5 6 final class TeraUploadRenewalTests: XCTestCase { 7 func testLineageUsesExactSavedRevisionsAndRequiresEvidenceForLegacyAttempts() throws { 8 let fixture = try BackgroundUploadFixture() 9 defer { fixture.remove() } 10 let operation = String(repeating: "a", count: 32) 11 let job = fixture.job(revision: 2, operation: operation) 12 let prior = TeraUploadAttemptIdentity(operationID: operation, revision: nil, expirationUnixSeconds: 100) 13 XCTAssertThrowsError(try TeraUploadRenewal.identities([prior], parent: fixture.draftID, inventory: [])) 14 let snapshot = try RadrootsBackgroundTransferSnapshot(request: fixture.request(job: job), state: .expired) 15 let observed = try TeraUploadRenewal.identities([prior], parent: fixture.draftID, inventory: [snapshot]) 16 XCTAssertEqual(observed.map(\.revision), [2]) 17 XCTAssertEqual(observed.map(\.identifier.rawValue), [job.transferIdentifier]) 18 let incorrect = TeraUploadAttemptIdentity(operationID: operation, revision: 3, expirationUnixSeconds: 100) 19 XCTAssertThrowsError(try TeraUploadRenewal.identities([incorrect], parent: fixture.draftID, inventory: [snapshot])) 20 XCTAssertThrowsError(try TeraUploadRenewal.identities(Array(repeating: prior, count: 6), parent: fixture.draftID, inventory: [snapshot])) 21 let next = TeraUploadAttemptIdentity(operationID: String(repeating: "b", count: 32), revision: 4, expirationUnixSeconds: 200) 22 let lineage = try TeraUploadRenewal.identities([prior, next], parent: fixture.draftID, inventory: [snapshot]) 23 XCTAssertEqual(lineage.map(\.revision), [2, 4]) 24 XCTAssertNotEqual(lineage[0].identifier, lineage[1].identifier) 25 } 26 27 func testActualNativeGuardRefusesActiveUnknownAndReceiptBeforeRenewalBody() async throws { 28 let fixture = try BackgroundUploadFixture() 29 defer { fixture.remove() } 30 let request = try fixture.request(job: fixture.job(revision: 2, operation: String(repeating: "a", count: 32))) 31 let prior = TeraUploadRenewal.Prior(identifier: request.identifier, attempt: String(repeating: "a", count: 32), revision: 2) 32 for scenario in 0 ..< 3 { 33 let store = fixture.nativeStore 34 let snapshot = try RadrootsBackgroundTransferSnapshot(request: request, state: scenario == 2 ? .awaitingVerification : .expired) 35 try await store.saveSnapshot(snapshot) 36 let adapters = RadrootsAppleBackgroundTransferAdapters(enqueue: { _, _ in XCTFail("Unexpected enqueue") }, 37 cancel: { _ in XCTFail("Unexpected cancel") }, activeTransferIdentifiers: { 38 if scenario == 1 { 39 throw RadrootsBackgroundTransferError.transferFailure 40 } 41 return scenario == 0 ? [request.identifier] : [] 42 }, handleBackgroundEvents: { _, done in done() }) 43 let transfer = RadrootsAppleBackgroundTransfer(store: store, adapters: adapters) 44 do { 45 try await TeraUploadRenewal.holding([prior], transfer: transfer) { _ in XCTFail("Unsafe renewal admitted") } 46 XCTFail("Unreconciled execution must be refused") 47 } catch {} 48 let after = try await store.loadSnapshots() 49 XCTAssertEqual(after, [snapshot]) 50 } 51 } 52 53 func testAllPriorIdentifiersStayReservedWhileActualFileStoreRetainsLateBody() async throws { 54 let fixture = try BackgroundUploadFixture() 55 defer { fixture.remove() } 56 let store = fixture.nativeStore 57 let adapters = RadrootsAppleBackgroundTransferAdapters(enqueue: { _, _ in XCTFail("Unexpected enqueue") }, 58 cancel: { _ in XCTFail("Unexpected cancel") }, activeTransferIdentifiers: { [] }, handleBackgroundEvents: { _, done in done() }) 59 let transfer = RadrootsAppleBackgroundTransfer(store: store, adapters: adapters) 60 let other = RadrootsAppleBackgroundTransfer(store: fixture.nativeStore, adapters: adapters) 61 let requests = try ["a", "b"].enumerated().map { offset, char in 62 try fixture.request(job: fixture.job(revision: UInt64(offset + 2), operation: String(repeating: char, count: 32))) 63 } 64 let originals = try requests.map { try RadrootsBackgroundTransferSnapshot(request: $0, state: .expired, executionID: UUID()) } 65 for original in originals { 66 try await store.saveSnapshot(original) 67 } 68 let lineage = try originals.map { original -> TeraUploadRenewal.Prior in 69 let identity = try XCTUnwrap(TeraBackgroundUploadRequest.transferIdentity(original.identifier)) 70 return .init(identifier: original.identifier, attempt: identity.attempt, revision: identity.revision) 71 } 72 let late = try RadrootsBackgroundTransferSnapshot(request: requests[0], state: .awaitingVerification, 73 response: RadrootsBackgroundTransferResponse(statusCode: 200, mediaType: "application/json", body: Data("{\"late\":true}".utf8)), 74 executionID: originals[0].executionID) 75 try await TeraUploadRenewal.holding(lineage, transfer: transfer) { snapshots in 76 XCTAssertEqual(snapshots, originals) 77 for request in requests { 78 do { _ = try await other.retry(request); XCTFail("Prior identifier was not reserved") } catch {} 79 } 80 let saved = try await fixture.nativeStore.compareExchangeSnapshot(expected: originals[0], desired: late) 81 XCTAssertTrue(saved) 82 } 83 let retained = try await fixture.nativeStore.loadSnapshots().first { $0.identifier == requests[0].identifier } 84 XCTAssertEqual(retained, late) 85 do { 86 try await TeraUploadRenewal.holding(lineage, transfer: other) { _ in XCTFail("Late response cannot be replaced") } 87 XCTFail("Late receipt must be reconciled first") 88 } catch {} 89 } 90 }