field_ios

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

TeraUploadRenewalExecutionTests.swift (7661B)


      1 import Foundation
      2 import RadrootsKit
      3 @testable import TeraApp
      4 import XCTest
      5 
      6 final class TeraUploadRenewalExecutionTests: XCTestCase {
      7   func testFreshNativeIdentifierEnqueuesWhileOldAdmissionRemainsHeld() async throws {
      8     let fixture = try BackgroundUploadFixture()
      9     defer { fixture.remove() }
     10     let prior = try fixture.request(job: fixture.job(revision: 2, operation: String(repeating: "a", count: 32)))
     11     let store = fixture.nativeStore
     12     let original = try RadrootsBackgroundTransferSnapshot(request: prior, state: .expired)
     13     try await store.saveSnapshot(original)
     14     let inert = RadrootsAppleBackgroundTransferAdapters(enqueue: { _, _ in XCTFail("Old authority was replayed") },
     15                                                         cancel: { _ in XCTFail("Unexpected cancellation") }, activeTransferIdentifiers: { [] }, handleBackgroundEvents: { _, done in done() })
     16     let other = RadrootsAppleBackgroundTransfer(store: fixture.nativeStore, adapters: inert)
     17     let backend = RenewalExecutionBackend(fixture: fixture)
     18     let adapters = RadrootsAppleBackgroundTransferAdapters(enqueue: { request, executionID in
     19       XCTAssertNotEqual(request.identifier, prior.identifier)
     20       XCTAssertEqual(request.identifier.rawValue, fixture.job(revision: 3, operation: String(repeating: "b", count: 32)).transferIdentifier)
     21       XCTAssertEqual(request.headers["Authorization"], "Nostr test-authorization")
     22       do { _ = try await other.retry(prior); XCTFail("Old admission released before fresh enqueue") } catch {}
     23       let queued = try await store.loadSnapshots().first { $0.identifier == request.identifier }
     24       let pending = try RadrootsBackgroundTransferSnapshot(request: XCTUnwrap(queued).request, state: .awaitingVerification,
     25                                                            response: RadrootsBackgroundTransferResponse(statusCode: 200, mediaType: "application/json", body: Data("{}".utf8)), executionID: executionID)
     26       try await store.saveSnapshot(pending)
     27     }, cancel: { _ in XCTFail("Unexpected cancellation") }, activeTransferIdentifiers: { [] }, handleBackgroundEvents: { _, done in done() })
     28     let transfer = RadrootsAppleBackgroundTransfer(store: store, adapters: adapters)
     29     let configuration = TeraRuntimeClientTests().makeConfiguration(generation: "36")
     30     let client = TeraRuntimeClient(factory: { _ in .init(backend: backend, snapshot: TeraScopeFixtures.snapshot()) })
     31     _ = try await client.start(configuration: configuration)
     32     let initial = await backend.status(revision: 2)
     33     let result = try await fixture.coordinator(transfer: transfer).renewSubmissionUpload(initial, media: fixture.media, client: client)
     34     XCTAssertEqual(result.intentID, initial.intentID)
     35     XCTAssertEqual(result.operationID, initial.operationID)
     36     XCTAssertEqual(result.captured, initial.captured)
     37     let counts = await backend.counts
     38     XCTAssertEqual(counts, [1, 1])
     39     let unchanged = try await store.loadSnapshots().first { $0.identifier == prior.identifier }
     40     XCTAssertEqual(unchanged, original)
     41     let snapshots = try await store.loadSnapshots()
     42     XCTAssertEqual(snapshots.filter { $0.state == .completed }.count, 1)
     43     _ = try await client.stop()
     44   }
     45 }
     46 
     47 /// Tests native scheduling only; Rust authority and canonical byte verification
     48 /// are exercised independently through the actual generated runtime.
     49 private actor RenewalExecutionBackend: TeraRuntimeBackend {
     50   let fixture: BackgroundUploadFixture
     51   var revision: UInt64 = 2
     52   var counts = [0, 0]
     53   init(fixture: BackgroundUploadFixture) {
     54     self.fixture = fixture
     55   }
     56 
     57   func status(revision: UInt64) -> TeraSubmissionStatus {
     58     let draft = fixture.draft(revision: revision, stage: revision == 4 ? .verified : .uploading)
     59     let scope = TeraComposerScope(authorPublicKey: String(repeating: "a", count: 64), localNetworkID: "nearby")
     60     let request = TeraSubmissionRequest(commandID: String(repeating: "3", count: 32), scope: scope,
     61                                         composerID: String(repeating: "4", count: 32), expectedRevision: 1)
     62     let old = TeraUploadAttemptIdentity(operationID: String(repeating: "a", count: 32), revision: 2, expirationUnixSeconds: 100)
     63     let next = TeraUploadAttemptIdentity(operationID: String(repeating: "b", count: 32), revision: 3, expirationUnixSeconds: 200)
     64     return TeraSubmissionStatus(request: request, intentID: fixture.draftID, operationID: String(repeating: "5", count: 32),
     65                                 revision: revision, captured: TeraComposerDraft(scope: scope, id: request.composerID, revision: 1, editSequence: 1,
     66                                                                                 form: TeraComposerForm(editing: draft.form!)), state: revision == 4 ? .readyToSign : .mediaUploading,
     67                                 committedAtUnixMilliseconds: 1, updatedAtUnixMilliseconds: 2,
     68                                 media: [.init(opaqueReference: fixture.media.opaqueReference, progress: draft.media[0], authorizations: revision == 2 ? [old] : [old, next])],
     69                                 settlement: TeraOperationSettlement(artifacts: 1, signed: 0, admitted: 0, pending: 1, retryable: 0,
     70                                                                     indeterminate: 0, failedTerminal: 0, cancelled: 0, deliveryPlans: 1, deliverySatisfied: 0, deliveryPending: 1,
     71                                                                     deliveryRetryable: 0, deliveryExhausted: 0, deliveryFailedTerminal: 0, deliveryCancelled: 0),
     72                                 delivery: TeraPublicationEvidence(state: .notIssued, stopRequestedAtUnixMilliseconds: nil,
     73                                                                   schedulingRevision: revision, retainedFacts: 0, recordedAttempts: 0, unresolvedClaims: false),
     74                                 targetDetails: .fixture(), retry: .ready)
     75   }
     76 
     77   func renewSubmissionUpload(input: TeraSubmissionMediaRequest, renewal: TeraSubmissionUploadRenewal) throws -> TeraSubmissionUploadJob {
     78     XCTAssertEqual(input.expectedRevision, 2)
     79     XCTAssertEqual(renewal, .init(priorRevision: 2, priorAttempt: String(repeating: "a", count: 32), nativeFailed: true))
     80     counts[0] += 1; revision = 3
     81     return .init(submission: status(revision: revision), transfer: fixture.job(revision: revision, operation: String(repeating: "b", count: 32)).transfer)
     82   }
     83 
     84   func submissionStatus(request _: TeraSubmissionRequest) -> TeraSubmissionStatus {
     85     status(revision: revision)
     86   }
     87 
     88   func completeSubmissionUpload(input: TeraSubmissionMediaRequest, response: TeraAddBackgroundUploadReceipt) -> TeraSubmissionStatus {
     89     XCTAssertEqual(input.expectedRevision, 3)
     90     XCTAssertEqual(response.expectedRevision, 3)
     91     XCTAssertEqual(response.identifier, fixture.job(revision: 3, operation: String(repeating: "b", count: 32)).transferIdentifier)
     92     counts[1] += 1; revision = 4
     93     return status(revision: revision)
     94   }
     95 
     96   func snapshot() -> TeraRuntimeSnapshot {
     97     TeraScopeFixtures.snapshot()
     98   }
     99 
    100   func todayPage(request _: TeraTodayPageRequest) throws -> TeraTodayPage {
    101     throw TeraComposerAcknowledgment.unconfirmed
    102   }
    103 
    104   func refreshToday(context _: TeraLocalNetwork, nowUnixSeconds _: UInt64, update _: TeraTodayProjectionUpdate, backfillCursor _: String?) throws -> TeraTodaySyncReceipt {
    105     throw TeraComposerAcknowledgment.unconfirmed
    106   }
    107 
    108   func subscribe(bufferCapacity _: Int, receive _: @escaping @Sendable (TeraRuntimeChange) async -> Void) -> any TeraRuntimeSubscriptionToken {
    109     ResourceTestToken()
    110   }
    111 
    112   func shutdown() -> TeraRuntimeShutdownReceipt {
    113     .init(state: "closed", alreadyClosed: false)
    114   }
    115 }