field_ios

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

TeraNativeExecutionTests.swift (7568B)


      1 import Foundation
      2 import RadrootsKit
      3 @testable import TeraApp
      4 import XCTest
      5 
      6 final class TeraNativeExecutionTests: XCTestCase {
      7   func testLostEnqueueReplyRejoinsExactReceiptWithoutAnotherEffect() async throws {
      8     let fixture = try BackgroundUploadFixture()
      9     defer { fixture.remove() }
     10     let transfer = NativeExecutionTransfer()
     11     let job = fixture.job(revision: 2, operation: String(repeating: "a", count: 32))
     12     let receipt = try await fixture.coordinator(transfer: transfer).uploadInBackground(job: job, media: fixture.media)
     13     XCTAssertEqual(receipt.identifier, job.transferIdentifier)
     14     XCTAssertEqual(receipt.expectedRevision, 2)
     15     let later = fixture.job(revision: 3, operation: String(repeating: "b", count: 32))
     16     let replay = try await fixture.coordinator(transfer: transfer).uploadInBackground(job: later, media: fixture.media)
     17     XCTAssertEqual(replay, receipt)
     18     let counts = await transfer.base.counts
     19     XCTAssertEqual(counts.enqueue, 1)
     20     XCTAssertEqual(counts.retry, 0)
     21     XCTAssertEqual(counts.acceptedSettlement, 0)
     22   }
     23 
     24   func testLostRetryReplyDoesNotScheduleAnotherRetry() async throws {
     25     let fixture = try BackgroundUploadFixture()
     26     defer { fixture.remove() }
     27     let transfer = NativeExecutionTransfer()
     28     let job = fixture.job(revision: 2, operation: String(repeating: "a", count: 32))
     29     try await transfer.base.seed(request: fixture.request(job: job), state: .interrupted)
     30     let receipt = try await fixture.coordinator(transfer: transfer).uploadInBackground(job: job, media: fixture.media)
     31     XCTAssertEqual(receipt.identifier, job.transferIdentifier)
     32     let counts = await transfer.base.counts
     33     XCTAssertEqual(counts.retry, 1)
     34     XCTAssertEqual(counts.enqueue, 0)
     35   }
     36 
     37   func testUnknownInventoryDoesNotEnqueueRetryOrSelectForegroundFallback() async throws {
     38     let fixture = try BackgroundUploadFixture()
     39     defer { fixture.remove() }
     40     let transfer = NativeExecutionTransfer(unknownInventory: true)
     41     let job = fixture.job(revision: 2, operation: String(repeating: "a", count: 32))
     42     try await transfer.base.seed(request: fixture.request(job: job), state: .running)
     43     let before = try await transfer.base.snapshots()
     44     let coordinator = fixture.coordinator(transfer: transfer)
     45     do {
     46       _ = try await coordinator.uploadInBackground(job: job, media: fixture.media)
     47       XCTFail("Unknown inventory cannot admit an upload")
     48     } catch let error as RadrootsBackgroundTransferError { XCTAssertEqual(error, .transferFailure) }
     49     do {
     50       _ = try await coordinator.prefersSharedForegroundUpload(ownerID: fixture.draftID)
     51       XCTFail("Unknown native inventory cannot authorize foreground fallback")
     52     } catch let error as RadrootsBackgroundTransferError { XCTAssertEqual(error, .transferFailure) }
     53     let after = try await transfer.base.snapshots()
     54     XCTAssertEqual(after, before)
     55     let counts = await transfer.base.counts
     56     XCTAssertEqual(counts.enqueue, 0)
     57     XCTAssertEqual(counts.retry, 0)
     58     XCTAssertEqual(counts.cancel, 0)
     59   }
     60 
     61   func testBoundedObservationPreservesRunningTransferAndLaterOriginalReceipt() async throws {
     62     let fixture = try BackgroundUploadFixture()
     63     defer { fixture.remove() }
     64     let transfer = BackgroundTransferHarness()
     65     let request = try fixture.request(job: fixture.job(revision: 2, operation: String(repeating: "a", count: 32)))
     66     try await transfer.seed(request: request, state: .running)
     67     let before = try await transfer.snapshots()
     68     do {
     69       _ = try await TeraBackgroundUploadWaiter.receipt(transfer: transfer, for: request.identifier,
     70                                                        draftID: fixture.draftID, expectedRevision: 3, request: request, waitNanoseconds: 1_000_000)
     71       XCTFail("A still-running transfer must remain unknown after the observation budget")
     72     } catch let failure as TeraRuntimeFailure { XCTAssertEqual(failure.code, "ios.add.background_upload_unknown") }
     73     let after = try await transfer.snapshots()
     74     XCTAssertEqual(after, before)
     75     try await transfer.setState(.awaitingVerification)
     76     let receipt = try await TeraBackgroundUploadWaiter.receipt(transfer: transfer, for: request.identifier,
     77                                                                draftID: fixture.draftID, expectedRevision: 3, request: request)
     78     XCTAssertEqual(receipt.expectedRevision, 2)
     79     let counts = await transfer.counts
     80     XCTAssertEqual(counts.cancel, 0)
     81     XCTAssertEqual(counts.retry, 0)
     82     XCTAssertEqual(counts.enqueue, 0)
     83     XCTAssertEqual(counts.acceptedSettlement, 0)
     84   }
     85 
     86   func testObservationRejectsExecutionReplacementWithoutMutatingNativeState() async throws {
     87     let fixture = try BackgroundUploadFixture()
     88     defer { fixture.remove() }
     89     let request = try fixture.request(job: fixture.job(revision: 2, operation: String(repeating: "a", count: 32)))
     90     for carryAdmission in [false, true] {
     91       let transfer = NativeExecutionTransfer(replaceExecution: true)
     92       try await transfer.base.seed(request: request, state: .running)
     93       let baseline = carryAdmission ? try await transfer.snapshot(for: request.identifier) : nil
     94       do {
     95         _ = try await TeraBackgroundUploadWaiter.receipt(transfer: transfer, for: request.identifier,
     96                                                          draftID: fixture.draftID, expectedRevision: 2, request: request, baseline: baseline)
     97         XCTFail("A replacement execution cannot supply this observation's receipt")
     98       } catch let failure as TeraRuntimeFailure { XCTAssertEqual(failure.code, "ios.add.background_upload_mismatch") }
     99       let counts = await transfer.base.counts
    100       XCTAssertEqual(counts.cancel, 0)
    101       XCTAssertEqual(counts.retry, 0)
    102       XCTAssertEqual(counts.acceptedSettlement, 0)
    103     }
    104   }
    105 
    106   func testRealNativeAdmissionRefusesDuplicateAfterLostReplyAndRelaunch() async throws {
    107     let fixture = try BackgroundUploadFixture()
    108     defer { fixture.remove() }
    109     let platform = LostNativeAdmission()
    110     let store = fixture.nativeStore
    111     let job = fixture.job(revision: 2, operation: String(repeating: "a", count: 32))
    112     for _ in 0 ..< 2 {
    113       let native = RadrootsAppleBackgroundTransfer(store: store, adapters: platform.adapters)
    114       do {
    115         _ = try await fixture.coordinator(transfer: native).uploadInBackground(job: job, media: fixture.media)
    116         XCTFail("An active task with a lost response is unknown")
    117       } catch let failure as TeraRuntimeFailure { XCTAssertEqual(failure.code, "ios.add.background_upload_unknown") }
    118     }
    119     let starts = await platform.starts
    120     XCTAssertEqual(starts, 1)
    121     let snapshots = try await store.loadSnapshots()
    122     let retained = try XCTUnwrap(snapshots.first)
    123     XCTAssertNotNil(retained.executionID)
    124     XCTAssertTrue(retained.possibleRemoteOrphan)
    125     let late = try RadrootsBackgroundTransferSnapshot(request: retained.request, state: .awaitingVerification,
    126                                                       response: RadrootsBackgroundTransferResponse(statusCode: 200, mediaType: "application/json", body: Data("{}".utf8)),
    127                                                       executionID: retained.executionID)
    128     try await store.saveSnapshot(late)
    129     let native = RadrootsAppleBackgroundTransfer(store: fixture.nativeStore, adapters: platform.adapters)
    130     let receipt = try await fixture.coordinator(transfer: native).uploadInBackground(job: job, media: fixture.media)
    131     XCTAssertEqual(receipt.identifier, job.transferIdentifier)
    132     let after = await platform.starts
    133     XCTAssertEqual(after, 1)
    134   }
    135 }