field_ios

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

TeraRecoveryCompletionTests.swift (7431B)


      1 import Foundation
      2 import RadrootsKit
      3 @testable import TeraApp
      4 import TeraKitBindings
      5 import XCTest
      6 
      7 final class TeraRecoveryCompletionTests: XCTestCase {
      8   @MainActor
      9   func testLegacyCompletionErrorsPreserveNativeEvidenceForExactRecovery() async throws {
     10     let fixture = try BackgroundUploadFixture()
     11     defer { fixture.remove() }
     12     let backend = try TeraScopeBackend()
     13     let client = try await TeraScopeFixtures.client(backend)
     14     let transfer = BackgroundTransferHarness()
     15     let opened = try await fixture.coordinator(transfer: transfer).open([fixture.media])
     16     defer { opened.close() }
     17     let handle = try XCTUnwrap(opened.handles.first)
     18     let media = AddMediaHarness()
     19     let response = TeraAddBackgroundUploadReceipt(identifier: "retained", draftID: fixture.draftID, expectedRevision: 2,
     20                                                   statusCode: 200, mediaType: "application/json", contentEncoding: nil, body: Data("{}".utf8))
     21     for code in ["ios.runtime.timeout", "storage_failure", "invalid_native_upload_completion"] {
     22       await backend.failLegacyCompletion(with: .local(operation: "test.legacy", code: code, safeMessage: "Unconfirmed completion."))
     23       do {
     24         _ = try await TeraAddUploadCompletion.complete(response, handle: handle, media: media, runtimeClient: client)
     25         XCTFail("Completion should fail without settling")
     26       } catch {}
     27       let settlements = await media.settlementValues()
     28       XCTAssertEqual(settlements, [])
     29     }
     30     _ = try await client.stop()
     31   }
     32 
     33   @MainActor
     34   func testPendingReceiptWaitsForDurableCompletionBeforeSettlement() async throws {
     35     let fixture = try BackgroundUploadFixture()
     36     defer { fixture.remove() }
     37     let transfer = BackgroundTransferHarness()
     38     let job = fixture.job(revision: 2, operation: String(repeating: "a", count: 32))
     39     try await transfer.seed(request: fixture.request(job: job), state: .awaitingVerification)
     40     let backend = try TeraScopeBackend()
     41     await backend.setDrafts([job.draft])
     42     let pause = ResourceTestPause()
     43     await backend.setRecoveryCompletion { input, handle in
     44       XCTAssertEqual(handle.media, fixture.media)
     45       XCTAssertEqual(input.revision, 2)
     46       XCTAssertEqual(input.attempt, job.operationID)
     47       await pause.wait()
     48       return Self.verified(input)
     49     }
     50     let client = try await TeraScopeFixtures.client(backend)
     51     let coordinator = fixture.coordinator(transfer: transfer)
     52     let task = Task { try await coordinator.recoverNativeUploads(client: client) }
     53     await pause.entered.wait()
     54     let pending = await transfer.counts
     55     XCTAssertEqual(pending.acceptedSettlement, 0)
     56     await pause.resume.open()
     57     let progress = try await task.value
     58     XCTAssertEqual(progress, .init(visited: 1, remaining: 0, needsAttention: false))
     59     let counts = await transfer.counts
     60     XCTAssertEqual(counts.acceptedSettlement, 1)
     61     XCTAssertEqual(counts.enqueue, 0)
     62     XCTAssertEqual(counts.retry, 0)
     63     _ = try await client.stop()
     64   }
     65 
     66   @MainActor
     67   func testCancelledCompletionRetainsNativeReceiptAndRestartCompletesWithoutUpload() async throws {
     68     let fixture = try BackgroundUploadFixture()
     69     defer { fixture.remove() }
     70     let transfer = BackgroundTransferHarness()
     71     let job = fixture.job(revision: 2, operation: String(repeating: "a", count: 32))
     72     try await transfer.seed(request: fixture.request(job: job), state: .awaitingVerification)
     73     let backend = try TeraScopeBackend()
     74     await backend.setDrafts([job.draft])
     75     let pause = ResourceTestPause()
     76     await backend.setRecoveryCompletion { input, _ in
     77       await pause.wait()
     78       return Self.verified(input)
     79     }
     80     let client = try await TeraScopeFixtures.client(backend)
     81     let coordinator = fixture.coordinator(transfer: transfer)
     82     let task = Task { try await coordinator.recoverNativeUploads(client: client) }
     83     await pause.entered.wait()
     84     task.cancel()
     85     await pause.resume.open()
     86     do { _ = try await task.value; XCTFail("Cancelled recovery must retain the receipt") } catch is CancellationError {}
     87     let counts = await transfer.counts
     88     XCTAssertEqual(counts.acceptedSettlement, 0)
     89     await backend.setRecoveryCompletion { input, _ in Self.verified(input) }
     90     let restarted = fixture.coordinator(transfer: transfer)
     91     let progress = try await restarted.recoverNativeUploads(client: client)
     92     XCTAssertFalse(progress.needsAttention)
     93     let final = await transfer.counts
     94     XCTAssertEqual(final.acceptedSettlement, 1)
     95     XCTAssertEqual(final.enqueue, 0)
     96     XCTAssertEqual(final.retry, 0)
     97     _ = try await client.stop()
     98   }
     99 
    100   @MainActor
    101   func testUnconfirmedRustReceiptCannotSettleNativeEvidence() async throws {
    102     let fixture = try BackgroundUploadFixture()
    103     defer { fixture.remove() }
    104     let transfer = BackgroundTransferHarness()
    105     let job = fixture.job(revision: 2, operation: String(repeating: "a", count: 32))
    106     try await transfer.seed(request: fixture.request(job: job), state: .awaitingVerification)
    107     let backend = try TeraScopeBackend()
    108     await backend.setDrafts([job.draft])
    109     await backend.setRecoveryCompletion { input, _ in
    110       .init(parent: String(repeating: "b", count: 32), attempt: input.attempt, canonicalURL: input.media.remoteURL ?? "",
    111             sha256: input.media.sha256, mediaType: input.media.mediaType, byteSize: input.media.byteSize, verifiedAtUnixMS: 1)
    112     }
    113     let client = try await TeraScopeFixtures.client(backend)
    114     let result = try await fixture.coordinator(transfer: transfer).recoverNativeUploads(client: client)
    115     XCTAssertTrue(result.needsAttention)
    116     let counts = await transfer.counts
    117     XCTAssertEqual(counts.acceptedSettlement, 0)
    118     XCTAssertEqual(counts.cancel, 0)
    119     _ = try await client.stop()
    120   }
    121 
    122   func testGeneratedCompletionRejectsUnsupportedNativeEvidenceBeforeMediaConversion() async throws {
    123     let fixture = try MediaOwnershipFixture()
    124     defer { fixture.remove() }
    125     let runtime = try await fixture.runtime()
    126     let opened = try TeraMediaFileFixture.open([fixture.media], bytes: fixture.bytes)
    127     defer { opened.close() }
    128     var media = try XCTUnwrap(opened.handles.first).generatedValue
    129     media.schemaVersion = 0 // If converted first, this would return a different error.
    130     let receipt = FfiRecoveryUploadReceipt(schemaVersion: 2, parent: String(repeating: "1", count: 32), revision: 2,
    131                                            attempt: String(repeating: "a", count: 32), uploadUrl: "http://127.0.0.1:3000/upload",
    132                                            sha256: fixture.media.sha256, mediaType: "image/png", byteSize: fixture.media.byteSize,
    133                                            response: .init(schemaVersion: 1, statusCode: 200, mediaType: "application/json", contentEncoding: nil, body: Data("{}".utf8)))
    134     do {
    135       _ = try await runtime.recoverNativeUpload(receipt: receipt, media: media)
    136       XCTFail("Unsupported receipt must fail")
    137     } catch let TeraAppError.Failure(report) {
    138       XCTAssertEqual(report.code, "invalid_recovery_receipt")
    139     }
    140     _ = try await runtime.shutdown()
    141   }
    142 
    143   private static func verified(_ input: TeraRecoveryUploadReceipt) -> TeraRecoveryCompletionReceipt {
    144     .init(parent: input.parent, attempt: input.attempt, canonicalURL: input.media.remoteURL ?? "",
    145           sha256: input.media.sha256, mediaType: input.media.mediaType, byteSize: input.media.byteSize, verifiedAtUnixMS: 1_800_000_000_000)
    146   }
    147 }