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 }