field_ios

In-the-field app for Radroots on iOS
git clone https://radroots.dev/git/field_ios.git
Log | Files | Refs | README | LICENSE

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 }