field_ios

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

TeraRuntimeClientFixtures.swift (7728B)


      1 import Foundation
      2 @testable import TeraApp
      3 
      4 struct TestRuntimeSigner: TeraRuntimeSigner {
      5     func availability() async -> TeraRuntimeSignerAvailability {
      6         .ready
      7     }
      8 
      9     func sign(_: TeraRuntimeSigningRequest) async -> TeraRuntimeSigningOutcome {
     10         .failed
     11     }
     12 }
     13 
     14 actor RuntimeHarness {
     15     private var configurations: [TeraRuntimeLaunchConfiguration] = []
     16 
     17     func launchConfigurations() -> [TeraRuntimeLaunchConfiguration] {
     18       configurations
     19     }
     20 
     21     private let startDelayNanoseconds: UInt64
     22     private let snapshotDelayNanoseconds: UInt64
     23     private let shutdownDelayNanoseconds: UInt64
     24     private let shutdownFailure: TeraRuntimeFailure?
     25     private var subscriptionFailures: Int
     26     private var starts = 0
     27     private var shutdowns = 0
     28     private var cancels = 0
     29     private var subscriptionAttempts = 0
     30     private var receivers: [UUID: @Sendable (TeraRuntimeChange) async -> Void] = [:]
     31 
     32     init(
     33       startDelayNanoseconds: UInt64 = 0,
     34       snapshotDelayNanoseconds: UInt64 = 0,
     35       shutdownDelayNanoseconds: UInt64 = 10_000_000,
     36       subscriptionFailures: Int = 0,
     37       shutdownFailure: TeraRuntimeFailure? = nil
     38     ) {
     39         self.startDelayNanoseconds = startDelayNanoseconds
     40         self.snapshotDelayNanoseconds = snapshotDelayNanoseconds
     41         self.shutdownDelayNanoseconds = shutdownDelayNanoseconds
     42         self.subscriptionFailures = subscriptionFailures
     43         self.shutdownFailure = shutdownFailure
     44     }
     45 
     46     func start(
     47         configuration: TeraRuntimeLaunchConfiguration
     48     ) async throws -> TeraRuntimeBackendStart {
     49         configurations.append(configuration)
     50         starts += 1
     51         if startDelayNanoseconds > 0 {
     52             await Task.detached { [startDelayNanoseconds] in
     53                 try? await Task.sleep(nanoseconds: startDelayNanoseconds)
     54             }.value
     55         }
     56         let backend = TestRuntimeBackend(
     57           harness: self,
     58           publicKeyHex: configuration.publicKeyHex,
     59           snapshotDelayNanoseconds: snapshotDelayNanoseconds,
     60           shutdownDelayNanoseconds: shutdownDelayNanoseconds,
     61           shutdownFailure: shutdownFailure
     62         )
     63         return await TeraRuntimeBackendStart(
     64           backend: backend,
     65           snapshot: backend.snapshotValue()
     66         )
     67     }
     68 
     69     func addReceiver(
     70         _ receive: @escaping @Sendable (TeraRuntimeChange) async -> Void
     71     ) throws -> UUID {
     72         subscriptionAttempts += 1
     73         if subscriptionFailures > 0 {
     74             subscriptionFailures -= 1
     75             throw TeraRuntimeFailure.local(
     76               operation: "test.subscribe",
     77               code: "test.subscribe_retryable",
     78               safeMessage: "The test subscription is temporarily unavailable."
     79             )
     80         }
     81         let id = UUID()
     82         receivers[id] = receive
     83         return id
     84     }
     85 
     86     func removeReceiver(id: UUID) {
     87         if receivers.removeValue(forKey: id) != nil {
     88             cancels += 1
     89         }
     90     }
     91 
     92     func emit(_ change: TeraRuntimeChange) async {
     93         for receive in receivers.values {
     94             await receive(change)
     95         }
     96     }
     97 
     98     func emitRevision(_ revision: UInt64) async {
     99         guard let configuration = configurations.last else {
    100             preconditionFailure("Start the fixture runtime before emitting changes")
    101         }
    102         await emit(TeraRuntimeChange(
    103           schemaVersion: 3,
    104           scope: TeraRuntimeChangeScope(publicKey: configuration.publicKeyHex,
    105                                         sourceGeneration: configuration.sourceGenerationHex, context: nil),
    106           epoch: String(repeating: "1", count: 32),
    107           revision: TeraProjectionRevision(rawValue: revision), delivery: .change, kind: .today, entityID: "card-\(revision)"
    108         ))
    109     }
    110 
    111     func recordShutdown() {
    112         shutdowns += 1
    113     }
    114 
    115     func startCount() -> Int {
    116         starts
    117     }
    118 
    119     func shutdownCount() -> Int {
    120         shutdowns
    121     }
    122 
    123     func cancelCount() -> Int {
    124         cancels
    125     }
    126 
    127     func subscriptionAttemptCount() -> Int {
    128         subscriptionAttempts
    129     }
    130 }
    131 
    132 private actor TestRuntimeBackend: TeraRuntimeBackend {
    133     private let harness: RuntimeHarness
    134     private let snapshotValueStored: TeraRuntimeSnapshot
    135     private let snapshotDelayNanoseconds: UInt64
    136     private let shutdownDelayNanoseconds: UInt64
    137     private let shutdownFailure: TeraRuntimeFailure?
    138     private var closed = false
    139 
    140     init(
    141       harness: RuntimeHarness,
    142       publicKeyHex: String,
    143       snapshotDelayNanoseconds: UInt64,
    144       shutdownDelayNanoseconds: UInt64,
    145       shutdownFailure: TeraRuntimeFailure?
    146     ) {
    147         self.harness = harness
    148         self.snapshotDelayNanoseconds = snapshotDelayNanoseconds
    149         self.shutdownDelayNanoseconds = shutdownDelayNanoseconds
    150         self.shutdownFailure = shutdownFailure
    151         snapshotValueStored = TeraRuntimeSnapshot(
    152           identity: TeraRuntimeIdentity(
    153             publicKeyHex: publicKeyHex,
    154             hostSignerConfigured: true
    155           ),
    156           relay: nil,
    157           blossomConfiguration: nil,
    158           blossomEvidence: nil,
    159           crateName: "tera_ffi",
    160           crateVersion: "0.1.0-alpha",
    161           isClosed: false
    162         )
    163     }
    164 
    165     func snapshotValue() -> TeraRuntimeSnapshot {
    166         snapshotValueStored
    167     }
    168 
    169     func snapshot() async throws -> TeraRuntimeSnapshot {
    170         if snapshotDelayNanoseconds > 0 {
    171             await Task.detached { [snapshotDelayNanoseconds] in
    172                 try? await Task.sleep(nanoseconds: snapshotDelayNanoseconds)
    173             }.value
    174         }
    175         if closed {
    176             throw TeraRuntimeFailure.local(
    177               operation: "test.snapshot",
    178               code: "test.closed",
    179               safeMessage: "The test runtime is closed."
    180             )
    181         }
    182         return snapshotValueStored
    183     }
    184 
    185     func todayPage(request _: TeraTodayPageRequest) throws -> TeraTodayPage {
    186         throw TeraRuntimeFailure.local(
    187           operation: "test.today.page",
    188           code: "test.unsupported",
    189           safeMessage: "Today is not configured for this lifecycle test."
    190         )
    191     }
    192 
    193     func refreshToday(
    194       context _: TeraLocalNetwork,
    195       nowUnixSeconds _: UInt64,
    196       update _: TeraTodayProjectionUpdate,
    197       backfillCursor _: String?
    198     ) throws -> TeraTodaySyncReceipt {
    199         throw TeraRuntimeFailure.local(
    200           operation: "test.today.refresh",
    201           code: "test.unsupported",
    202           safeMessage: "Today is not configured for this lifecycle test."
    203         )
    204     }
    205 
    206     func subscribe(
    207       bufferCapacity _: Int,
    208       receive: @escaping @Sendable (TeraRuntimeChange) async -> Void
    209     ) async throws -> any TeraRuntimeSubscriptionToken {
    210         let id = try await harness.addReceiver(receive)
    211         return TestSubscriptionToken(harness: harness, id: id)
    212     }
    213 
    214     func shutdown() async throws -> TeraRuntimeShutdownReceipt {
    215         await harness.recordShutdown()
    216         await Task.detached { [shutdownDelayNanoseconds] in
    217             try? await Task.sleep(nanoseconds: shutdownDelayNanoseconds)
    218         }.value
    219         if let shutdownFailure {
    220             throw shutdownFailure
    221         }
    222         let alreadyClosed = closed
    223         closed = true
    224         return TeraRuntimeShutdownReceipt(state: "closed", alreadyClosed: alreadyClosed)
    225     }
    226 }
    227 
    228 private actor TestSubscriptionToken: TeraRuntimeSubscriptionToken {
    229     private let harness: RuntimeHarness
    230     private let id: UUID
    231     private var cancelled = false
    232 
    233     init(harness: RuntimeHarness, id: UUID) {
    234         self.harness = harness
    235         self.id = id
    236     }
    237 
    238     func cancel() async {
    239         guard !cancelled else { return }
    240         cancelled = true
    241         await harness.removeReceiver(id: id)
    242     }
    243 }