field_ios

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

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 }