field_ios

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

TeraStoreRecoveryTests.swift (7103B)


      1 @testable import TeraApp
      2 import XCTest
      3 
      4 @MainActor
      5 final class TeraStoreRecoveryTests: XCTestCase {
      6   func testRecoveredTodayAddAndMeReadCurrentStateWithoutAnotherEvent() async throws {
      7     let backend = try TeraScopeBackend()
      8     let client = try await TeraScopeFixtures.client(backend)
      9     let retry = ResourceTestPause()
     10     let today = TeraTodayStore(runtimeClient: client, observationDelay: { _ in await retry.wait() })
     11     let add = TeraAddStore(runtimeClient: client, observationDelay: { _ in await retry.wait() })
     12     let me = TeraMeStore(runtimeClient: client, observationDelay: { _ in await retry.wait() })
     13     configure(today: today, add: add, me: me)
     14     await today.start()
     15     await add.start()
     16     await me.start()
     17     await TeraScopeFixtures.eventually {
     18       today.observationState == .active && add.observationState == .active && me.observationState == .active
     19     }
     20     await client.suspend()
     21     await TeraScopeFixtures.eventually {
     22       self.isRetrying(today.observationState) && self.isRetrying(add.observationState) && self.isRetrying(me.observationState)
     23     }
     24     await setCurrentState(backend, revision: 2)
     25     await retry.resume.open()
     26     await TeraScopeFixtures.eventually {
     27       today.cards.isEmpty && today.hasPendingContent && add.drafts.first?.revision == 2
     28         && me.snapshot?.cards.first?.id == "revision-2" && add.blossomEvidence?.observedAtUnixMilliseconds == 2
     29     }
     30     let subscriptions = await backend.counts[.subscribe]
     31     XCTAssertEqual(subscriptions, 6)
     32     today.stop()
     33     add.stop()
     34     me.stop()
     35     _ = try await client.stop()
     36   }
     37 
     38   func testFinalGapRefreshesAllStoreDomainsWithoutOverwritingTheEditingForm() async throws {
     39     let backend = try TeraScopeBackend()
     40     let client = try await TeraScopeFixtures.client(backend)
     41     let today = TeraTodayStore(runtimeClient: client)
     42     let add = TeraAddStore(runtimeClient: client)
     43     let me = TeraMeStore(runtimeClient: client)
     44     configure(today: today, add: add, me: me)
     45     await today.start()
     46     await add.start()
     47     await me.start()
     48     await TeraScopeFixtures.eventually {
     49       today.observationState == .active && add.observationState == .active && me.observationState == .active
     50     }
     51     add.updateForm(\.content, "Unsaved local editing")
     52     await setCurrentState(backend, revision: 9)
     53     await backend.emit(.initial, delivery: .resnapshotRequired)
     54     await TeraScopeFixtures.eventually {
     55       today.cards.isEmpty && today.hasPendingContent && add.drafts.first?.revision == 9
     56         && me.snapshot?.cards.first?.id == "revision-9" && add.blossomEvidence?.observedAtUnixMilliseconds == 9
     57     }
     58     XCTAssertEqual(add.form.content, "Unsaved local editing")
     59     let refreshes = await backend.counts[.refresh]
     60     XCTAssertEqual(refreshes, 1, "Observation recovery reads local state without another network refresh")
     61     today.stop()
     62     add.stop()
     63     me.stop()
     64     _ = try await client.stop()
     65   }
     66 
     67   func testTodayStormDuringSlowReadReconcilesTheFinalDurableVisibility() async throws {
     68     let backend = try TeraScopeBackend()
     69     let client = try await TeraScopeFixtures.client(backend)
     70     let store = TeraTodayStore(runtimeClient: client)
     71     store.configure(snapshot: TeraScopeFixtures.snapshot())
     72     await store.start()
     73     await TeraScopeFixtures.eventually { store.observationState == .active && !store.cards.isEmpty }
     74     let first = await backend.pause(.reconcile)
     75     await backend.emit(.today)
     76     await first.entered.wait()
     77     let before = await backend.counts[.reconcile, default: 0]
     78     await setCurrentState(backend, revision: 256)
     79     for _ in 1 ... 256 {
     80       await backend.emit(.today)
     81     }
     82     let paused = await backend.counts[.reconcile, default: 0]
     83     XCTAssertEqual(paused, before, "Only one observer query may execute at a time")
     84     await first.resume.open()
     85     await TeraScopeFixtures.eventually { store.cards.isEmpty && store.hasPendingContent }
     86     XCTAssertEqual(store.presentation.content, .empty)
     87     await store.reload(refreshProjection: false)
     88     XCTAssertEqual(store.cards.first?.id, "revision-256")
     89     store.stop()
     90     _ = try await client.stop()
     91   }
     92 
     93   func testOrdinaryMediaProgressPreservesLoadedPagesAndTheNextCursor() async throws {
     94     let backend = try TeraScopeBackend()
     95     let client = try await TeraScopeFixtures.client(backend)
     96     let subscription = await backend.pause(.subscribe)
     97     let initial = await backend.pause(.reconcile)
     98     await backend.setPage(page("one", next: "two"))
     99     await backend.setPage(page("two", next: "three"), cursor: "two")
    100     await backend.setPage(page("three"), cursor: "three")
    101     let store = TeraTodayStore(runtimeClient: client)
    102     store.configure(snapshot: TeraScopeFixtures.snapshot())
    103     await store.start()
    104     // The mandatory initial resnapshot has the same visible identities.
    105     await backend.setReconciliation(["one", "two", "three"].map(TeraScopeFixtures.card), generation: 1)
    106     await subscription.resume.open()
    107     await initial.entered.wait()
    108     await initial.resume.open()
    109     await TeraScopeFixtures.eventually { store.observationState == .active && !store.presentation.isReading }
    110     await store.loadNextPage()
    111     XCTAssertEqual(store.cards.map(\.id), ["one", "two"])
    112     let before = await backend.counts[.page, default: 0]
    113     // Space hints beyond one actor turn so this exercises ordinary progress,
    114     // independently of C029's intentionally stronger overflow resnapshot.
    115     for _ in 1 ... 8 {
    116       await backend.emit(.media)
    117       try await Task.sleep(for: .milliseconds(10))
    118     }
    119     let after = await backend.counts[.page, default: 0]
    120     XCTAssertEqual(after, before)
    121     XCTAssertEqual(store.cards.map(\.id), ["one", "two"])
    122     XCTAssertTrue(store.canLoadNextPage)
    123     await store.loadNextPage()
    124     XCTAssertEqual(store.cards.map(\.id), ["one", "two", "three"])
    125     store.stop()
    126     _ = try await client.stop()
    127   }
    128 
    129   private func configure(today: TeraTodayStore, add: TeraAddStore, me: TeraMeStore) {
    130     let snapshot = TeraScopeFixtures.snapshot()
    131     today.configure(snapshot: snapshot)
    132     add.configure(snapshot: snapshot)
    133     me.configure(context: .defaultContext(snapshot: snapshot))
    134   }
    135 
    136   private func setCurrentState(_ backend: TeraScopeBackend, revision: UInt64) async {
    137     await backend.setPage(page("revision-\(revision)"))
    138     await backend.setReconciliation([TeraScopeFixtures.card("revision-\(revision)")], generation: revision)
    139     await backend.setDrafts([TeraScopeFixtures.draft("revision-\(revision)", revision: revision)])
    140     await backend.setMeCards([TeraScopeFixtures.card("revision-\(revision)")])
    141     await backend.configure(TeraScopeFixtures.snapshot(evidence: TeraScopeFixtures.evidence(observedAt: revision)))
    142   }
    143 
    144   private func page(_ id: String, next: String? = nil) -> TeraTodayPage {
    145     TeraTodayPage(asOfUnixSeconds: 1, items: [TeraScopeFixtures.card(id)], nextCursor: next, projectionGeneration: 1, calendar: TeraScopeFixtures.viewerCalendar(asOf: 1))
    146   }
    147 
    148   private func isRetrying(_ value: TeraRuntimeObservationState) -> Bool {
    149     if case .retrying = value {
    150       return true
    151     }
    152     return false
    153   }
    154 }