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 }