TeraTodaySyncReceiptTests.swift (6846B)
1 @testable import TeraApp 2 import TeraKitBindings 3 import XCTest 4 5 @MainActor 6 final class TeraTodaySyncReceiptTests: XCTestCase { 7 func testPartialAndOfflineReceiptsRemainIndependentOfCachedContentAndProjectionFreshness() async throws { 8 for state: TeraTodayRelaySyncState in [.partial, .offline] { 9 let backend = try TeraScopeBackend() 10 let receipt = TeraTodaySyncFixtures.receipt(state: state, targets: [target()]) 11 await backend.setSyncReceipt(receipt) 12 let client = try await TeraScopeFixtures.client(backend) 13 let store = TeraTodayStore(runtimeClient: client, clock: .fixed(unixSeconds: 1)) 14 store.configure(snapshot: TeraScopeFixtures.snapshot()) 15 await store.reload(refreshProjection: false) 16 let cached = store.cards 17 await store.reload() 18 XCTAssertEqual(store.cards, cached) 19 XCTAssertEqual(store.presentation.content, .available) 20 XCTAssertEqual(store.presentation.refresh, .completed) 21 XCTAssertEqual(store.presentation.relayReceipt, receipt) 22 XCTAssertEqual(store.presentation.freshness, .refreshed(contentGeneration: 1)) 23 XCTAssertTrue(store.presentation.accessibilityStatus.contains("Relay 1: Some requested posts were unavailable.")) 24 XCTAssertFalse(store.presentation.accessibilityStatus.contains("Refresh failed.")) 25 _ = try await client.stop() 26 } 27 } 28 29 func testLateReceiptFromOldContextCannotReplaceNewContextEvidence() async throws { 30 let backend = try TeraScopeBackend() 31 await backend.setSyncReceipt(TeraTodaySyncFixtures.receipt(state: .offline)) 32 let client = try await TeraScopeFixtures.client(backend) 33 let store = TeraTodayStore(runtimeClient: client, clock: .fixed(unixSeconds: 1)) 34 store.configure(snapshot: TeraScopeFixtures.snapshot()) 35 let pause = await backend.pause(.refresh) 36 let old = Task { await store.reload() } 37 await pause.entered.wait() 38 store.configure(snapshot: TeraScopeFixtures.snapshot(relay: "second")) 39 XCTAssertNil(store.presentation.relayReceipt) 40 let current = TeraTodaySyncFixtures.receipt(state: .partial, targets: [target()]) 41 await backend.setSyncReceipt(current) 42 await store.reload() 43 await pause.resume.open() 44 await old.value 45 XCTAssertEqual(store.presentation.relayReceipt, current) 46 XCTAssertEqual(store.cards.map(\.id), ["wss://second.example"]) 47 _ = try await client.stop() 48 } 49 50 func testCancelledRefreshCannotInstallALatePartialReceipt() async throws { 51 let backend = try TeraScopeBackend() 52 await backend.setSyncReceipt(TeraTodaySyncFixtures.receipt(state: .partial)) 53 let client = try await TeraScopeFixtures.client(backend) 54 let store = TeraTodayStore(runtimeClient: client, clock: .fixed(unixSeconds: 1)) 55 store.configure(snapshot: TeraScopeFixtures.snapshot()) 56 await store.reload(refreshProjection: false) 57 let cached = store.cards 58 let pause = await backend.pause(.refresh) 59 let task = Task { await store.reload() } 60 await pause.entered.wait() 61 task.cancel() 62 await task.value 63 await pause.resume.open() 64 XCTAssertNil(store.presentation.relayReceipt) 65 XCTAssertEqual(store.cards, cached) 66 XCTAssertEqual(store.presentation.refresh, .idle) 67 _ = try await client.stop() 68 } 69 70 func testGeneratedRecordRetainsCumulativeEvidenceAndProjectionCounts() { 71 let record = FfiTodaySyncRecord( 72 schemaVersion: 1, relayState: .partial, termination: .pageLimit, 73 targets: [FfiTodayTargetSyncRecord( 74 targetFingerprint: "opaque", finalState: .complete, 75 summary: FfiTodayTargetPageSummary( 76 pagesObserved: 8, incompletePages: 1, missingOutcomePages: 0, lastIncomplete: .partial 77 ) 78 )], pagesFetched: 8, eventsObserved: 500, eventsAdmitted: 490, eventsRejected: 10, 79 projection: FfiTodayRefreshRecord( 80 schemaVersion: 1, update: .rebuild, sourceEvents: 501, visibleCards: 400, 81 profiles: 20, threadEntries: 70, contentGeneration: 7, changed: true 82 ), 83 discovery: FfiTodayDiscoveryRecord(continuation: "opaque-backfill", hadIncompleteResponses: true) 84 ) 85 let receipt = record.appValue 86 XCTAssertEqual(receipt.discovery.continuation, "opaque-backfill") 87 XCTAssertTrue(receipt.discovery.hadIncompleteResponses) 88 XCTAssertEqual(receipt.relayState, .partial) 89 XCTAssertEqual(receipt.termination, .pageLimit) 90 XCTAssertEqual(receipt.targets.first?.finalState, .complete) 91 XCTAssertEqual(receipt.targets.first?.summary?.lastIncomplete, .partial) 92 XCTAssertEqual(receipt.targets.first?.summary?.pagesObserved, 8) 93 XCTAssertEqual(receipt.targets.first?.summary?.incompletePages, 1) 94 XCTAssertEqual(receipt.targets.first?.targetFingerprint, "opaque") 95 XCTAssertEqual(receipt.pagesFetched, 8) 96 XCTAssertEqual(receipt.eventsObserved, 500) 97 XCTAssertEqual(receipt.eventsAdmitted, 490) 98 XCTAssertEqual(receipt.eventsRejected, 10) 99 XCTAssertEqual(receipt.projection.update, .rebuild) 100 XCTAssertEqual(receipt.projection.sourceEvents, 501) 101 XCTAssertEqual(receipt.projection.visibleCards, 400) 102 XCTAssertEqual(receipt.projection.profiles, 20) 103 XCTAssertEqual(receipt.projection.threadEntries, 70) 104 XCTAssertEqual(receipt.projection.contentGeneration, 7) 105 XCTAssertTrue(receipt.projection.changed) 106 } 107 108 func testGeneratedEnumMappingsAndUnknownEvidenceDoNotInventCompletion() { 109 let states: [FfiTodayTargetSyncState] = [.complete, .partial, .unavailable, .failedRetryable, .failedTerminal, .cancelled] 110 XCTAssertEqual(states.map(\.appValue), [.complete, .partial, .unavailable, .failedRetryable, .failedTerminal, .cancelled]) 111 let ends: [FfiTodaySyncTermination] = [.complete, .pageLimit, .deadline, .cancelled, .sourceFailed] 112 XCTAssertEqual(ends.map(\.appValue), [.complete, .pageLimit, .deadline, .cancelled, .sourceFailed]) 113 let relayStates: [FfiTodayRelaySyncState] = [.complete, .partial, .offline] 114 XCTAssertEqual(relayStates.map(\.appValue), [.complete, .partial, .offline]) 115 let unknown = FfiTodayTargetSyncRecord(targetFingerprint: "opaque", finalState: nil, summary: nil).appValue 116 XCTAssertNil(unknown.summary) 117 XCTAssertNil(unknown.finalState) 118 XCTAssertEqual(unknown.statusMessage, "Response unconfirmed.") 119 let omitted = TeraTodayTargetSyncReceipt( 120 targetFingerprint: "opaque", finalState: .complete, 121 summary: TeraTodayTargetPageSummary(pagesObserved: 2, incompletePages: 0, missingOutcomePages: 1, lastIncomplete: nil) 122 ) 123 XCTAssertEqual(omitted.statusMessage, "Some responses unconfirmed.") 124 } 125 126 private func target() -> TeraTodayTargetSyncReceipt { 127 TeraTodayTargetSyncReceipt( 128 targetFingerprint: "opaque", finalState: .complete, 129 summary: TeraTodayTargetPageSummary( 130 pagesObserved: 2, incompletePages: 1, missingOutcomePages: 0, lastIncomplete: .partial 131 ) 132 ) 133 } 134 }