field_ios

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

TeraRecoverySchedulingTests.swift (5284B)


      1 import Foundation
      2 @testable import TeraApp
      3 import XCTest
      4 
      5 @MainActor
      6 final class TeraRecoverySchedulingTests: XCTestCase {
      7   func testLifecycleBudgetContinuesUntilExhaustedAndNextResumeTraversesRemainder() async throws {
      8     let backend = try TeraScopeBackend()
      9     let client = try await TeraScopeFixtures.client(backend)
     10     let media = RecoverySchedulingMedia(pending: 1000)
     11     let store = TeraNativeRepairStore(client: client, media: media)
     12     for expected in [744, 488, 232, 0] {
     13       await store.reconcile()
     14       XCTAssertEqual(store.progress?.remaining, expected)
     15       XCTAssertLessThanOrEqual(store.issues.count, 64)
     16     }
     17     let calls = await media.calls
     18     XCTAssertEqual(calls, 16)
     19     store.stop()
     20     _ = try await client.stop()
     21   }
     22 
     23   func testStopRetainsSingleWorkerAndCoalescesResumeWithoutFreshNotification() async throws {
     24     let backend = try TeraScopeBackend()
     25     let client = try await TeraScopeFixtures.client(backend)
     26     let media = RecoverySchedulingMedia(pending: 1000)
     27     let pause = ResourceTestPause()
     28     await media.pauseNext(pause)
     29     let store = TeraNativeRepairStore(client: client, media: media)
     30     let running = Task { await store.reconcile() }
     31     await pause.entered.wait()
     32     for _ in 0 ..< 100 {
     33       store.retry()
     34     }
     35     store.stop()
     36     for _ in 0 ..< 100 {
     37       store.retry()
     38     }
     39     let during = await media.calls
     40     XCTAssertEqual(during, 1)
     41     XCTAssertTrue(store.isRunning)
     42     await pause.resume.open()
     43     _ = await running.value
     44     await TeraScopeFixtures.eventually { !store.isRunning }
     45     let final = await media.calls
     46     let maximum = await media.maximumActive
     47     XCTAssertEqual(final, 5, "One cancelled worker and one four-batch resumed request")
     48     XCTAssertEqual(maximum, 1)
     49     XCTAssertEqual(store.progress?.remaining, 744)
     50     store.stop()
     51     _ = try await client.stop()
     52   }
     53 
     54   func testForegroundResumeRetriesProtectedDataWithoutTransferOrDraftEvent() async throws {
     55     let backend = try TeraScopeBackend()
     56     let client = try await TeraScopeFixtures.client(backend)
     57     let media = RecoverySchedulingMedia(pending: 65, locked: true)
     58     let stores = TeraProductStores(runtimeClient: client, addMedia: media)
     59     stores.configure(snapshot: TeraScopeFixtures.snapshot())
     60     await stores.resume()
     61     XCTAssertEqual(stores.add.recovery.transfers.progress?.pause, .protectedData)
     62     XCTAssertEqual(stores.add.state, .ready)
     63     await media.unlock()
     64     await stores.resume()
     65     XCTAssertNil(stores.add.recovery.transfers.progress?.pause)
     66     XCTAssertEqual(stores.add.recovery.transfers.progress?.remaining, 0)
     67     let calls = await media.calls
     68     XCTAssertEqual(calls, 3)
     69     stores.suspend()
     70     await stores.resume()
     71     XCTAssertEqual(stores.add.state, .ready)
     72     stores.stop()
     73     _ = try await client.stop()
     74   }
     75 
     76   func testAlreadyCancelledCallerCannotCancelCurrentRecovery() async throws {
     77     let backend = try TeraScopeBackend()
     78     let client = try await TeraScopeFixtures.client(backend)
     79     let media = RecoverySchedulingMedia(pending: 65)
     80     let held = ResourceTestPause()
     81     await media.pauseNext(held)
     82     let store = TeraNativeRepairStore(client: client, media: media)
     83     let current = Task { await store.reconcile() }
     84     await held.entered.wait()
     85     let stalePause = ResourceTestPause()
     86     var staleReturned = false
     87     let stale = Task {
     88       await stalePause.wait()
     89       await store.reconcile()
     90       staleReturned = true
     91     }
     92     await stalePause.entered.wait()
     93     stale.cancel()
     94     await stalePause.resume.open()
     95     await TeraScopeFixtures.eventually { staleReturned }
     96     XCTAssertTrue(store.isRunning)
     97     await held.resume.open()
     98     _ = await current.value
     99     await stale.value
    100     XCTAssertEqual(store.progress?.remaining, 0)
    101     XCTAssertFalse(store.isRunning)
    102     store.stop()
    103     _ = try await client.stop()
    104   }
    105 }
    106 
    107 private actor RecoverySchedulingMedia: TeraAddMediaHandling {
    108   private var pending: Int
    109   private var locked: Bool
    110   private var pause: ResourceTestPause?
    111   private var active = 0
    112   private(set) var calls = 0
    113   private(set) var maximumActive = 0
    114 
    115   init(pending: Int, locked: Bool = false) {
    116     self.pending = pending; self.locked = locked
    117   }
    118 
    119   func pauseNext(_ value: ResourceTestPause) {
    120     pause = value
    121   }
    122 
    123   func unlock() {
    124     locked = false
    125   }
    126 
    127   func support() -> TeraAddMediaSupport {
    128     .unavailable
    129   }
    130 
    131   func recoverNativeUploads(client _: TeraRuntimeClient) async throws -> TeraNativeRecoveryProgress {
    132     calls += 1
    133     active += 1
    134     maximumActive = max(maximumActive, active)
    135     defer { active -= 1 }
    136     if let pause {
    137       self.pause = nil; await pause.wait()
    138     }
    139     try Task.checkCancellation()
    140     if locked {
    141       return .init(visited: 0, remaining: pending, needsAttention: false, pause: .protectedData)
    142     }
    143     let visited = min(64, pending)
    144     pending -= visited
    145     return .init(visited: visited, remaining: pending, needsAttention: false)
    146   }
    147 
    148   func importImages(limit _: Int) throws -> [TeraPreparedMedia] {
    149     throw TeraComposerAcknowledgment.unconfirmed
    150   }
    151 
    152   func captureImage() throws -> TeraPreparedMedia {
    153     throw TeraComposerAcknowledgment.unconfirmed
    154   }
    155 
    156   func open(_: [TeraPreparedMedia]) throws -> TeraOpenedMedia {
    157     throw TeraComposerAcknowledgment.unconfirmed
    158   }
    159 }