TeraRuntimeResourceFixtures.swift (7001B)
1 import Foundation 2 @testable import TeraApp 3 4 actor ResourceTestGate { 5 private var opened = false 6 private var waiters: [CheckedContinuation<Void, Never>] = [] 7 8 func wait() async { 9 guard !opened else { return } 10 await withCheckedContinuation { waiters.append($0) } 11 } 12 13 func open() { 14 guard !opened else { return } 15 opened = true 16 let pending = waiters 17 waiters.removeAll() 18 for waiter in pending { 19 waiter.resume() 20 } 21 } 22 } 23 24 struct ResourceTestPause: Sendable { 25 let entered = ResourceTestGate() 26 let resume = ResourceTestGate() 27 28 func wait() async { 29 await entered.open() 30 await resume.wait() 31 } 32 } 33 34 actor ResourceTestToken: TeraRuntimeSubscriptionToken { 35 let cancelled = ResourceTestGate() 36 private(set) var cancelCount = 0 37 38 func cancel() async { 39 cancelCount += 1 40 await cancelled.open() 41 } 42 } 43 44 actor ResourceTestBackend: TeraRuntimeBackend { 45 let token = ResourceTestToken() 46 let closed = ResourceTestGate() 47 private(set) var shutdownCount = 0 48 private let value: TeraRuntimeSnapshot 49 private var subscriptionPause: ResourceTestPause? 50 private var snapshotPause: ResourceTestPause? 51 private var settingsPause: ResourceTestPause? 52 private var profilePause: ResourceTestPause? 53 private var shutdownPause: ResourceTestPause? 54 private var shutdownFailure: TeraRuntimeFailure? 55 private var shutdownCompleted = false 56 private var removalPause: ResourceTestPause? 57 private var removalFailureID: String? 58 private(set) var preparedRemovals: [String] = [] 59 private(set) var profileMutations = 0 60 private(set) var identityCommands = 0 61 private var receive: (@Sendable (TeraRuntimeChange) async -> Void)? 62 63 init(publicKeyHex: String) { 64 value = TeraRuntimeSnapshot( 65 identity: TeraRuntimeIdentity(publicKeyHex: publicKeyHex, hostSignerConfigured: true), 66 relay: nil, blossomConfiguration: nil, blossomEvidence: nil, 67 crateName: "tera_ffi", crateVersion: "0.1.0-alpha", isClosed: false 68 ) 69 } 70 71 func start() -> TeraRuntimeBackendStart { 72 TeraRuntimeBackendStart(backend: self, snapshot: value) 73 } 74 75 func pauseSubscription(_ pause: ResourceTestPause) { 76 subscriptionPause = pause 77 } 78 79 func pauseSnapshot(_ pause: ResourceTestPause) { 80 snapshotPause = pause 81 } 82 83 func pauseSettings(_ pause: ResourceTestPause) { 84 settingsPause = pause 85 } 86 87 func pauseShutdown(_ pause: ResourceTestPause) { 88 shutdownPause = pause 89 } 90 91 func configureRemoval(pause: ResourceTestPause? = nil, failureID: String? = nil) { 92 removalPause = pause 93 removalFailureID = failureID 94 } 95 96 func prepareRetractionForKeyRemoval(_ request: TeraKeyRemovalRequest) async throws { 97 let pause = removalPause 98 removalPause = nil 99 await pause?.wait() 100 try Task.checkCancellation() 101 if request.id == removalFailureID { 102 throw unsupported() 103 } 104 preparedRemovals.append(request.id) 105 } 106 107 func failShutdownOnce(_ failure: TeraRuntimeFailure) { 108 shutdownFailure = failure 109 } 110 111 func pauseProfile(_ pause: ResourceTestPause) { 112 profilePause = pause 113 } 114 115 func saveProfileMetadata(input _: TeraProfileMetadataInput) async -> TeraProfileStatus { 116 await profileMutation() 117 } 118 119 func advanceProfile(operationID _: String) async -> TeraProfileStatus { 120 await profileMutation() 121 } 122 123 func cancelProfile(operationID _: String, expectedRevision _: UInt64) async -> TeraProfileStatus { 124 await profileMutation() 125 } 126 127 private func profileMutation() async -> TeraProfileStatus { 128 profileMutations += 1 129 let pause = profilePause 130 profilePause = nil 131 await pause?.wait() 132 return TeraProfileStatus( 133 id: String(repeating: "a", count: 32), revision: UInt64(profileMutations), 134 authorPublicKey: value.identity.publicKeyHex, state: .draft, deliveryID: nil, 135 createdAtUnixMilliseconds: 1_800_000_000_000, updatedAtUnixMilliseconds: 1_800_000_000_000, 136 settlement: nil 137 ) 138 } 139 140 func snapshot() async throws -> TeraRuntimeSnapshot { 141 let pause = snapshotPause 142 snapshotPause = nil 143 await pause?.wait() 144 guard shutdownCount == 0 else { throw unsupported() } 145 return value 146 } 147 148 func todayPage(request _: TeraTodayPageRequest) throws -> TeraTodayPage { 149 throw unsupported() 150 } 151 152 func refreshToday( 153 context _: TeraLocalNetwork, nowUnixSeconds _: UInt64, update _: TeraTodayProjectionUpdate, 154 backfillCursor _: String? 155 ) throws -> TeraTodaySyncReceipt { 156 throw unsupported() 157 } 158 159 func subscribe( 160 bufferCapacity _: Int, receive: @escaping @Sendable (TeraRuntimeChange) async -> Void 161 ) async -> any TeraRuntimeSubscriptionToken { 162 self.receive = receive 163 let pause = subscriptionPause 164 subscriptionPause = nil 165 await pause?.wait() 166 return token 167 } 168 169 func emit(_ revision: UInt64) async { 170 await receive?(TeraRuntimeChange( 171 schemaVersion: 3, 172 scope: TeraRuntimeChangeScope(publicKey: value.identity.publicKeyHex, sourceGeneration: value.identity.publicKeyHex, context: nil), 173 epoch: String(repeating: "1", count: 32), 174 revision: TeraProjectionRevision(rawValue: revision), delivery: .change, kind: .today, entityID: nil 175 )) 176 } 177 178 func shutdown() async throws -> TeraRuntimeShutdownReceipt { 179 shutdownCount += 1 180 let pause = shutdownPause 181 shutdownPause = nil 182 await pause?.wait() 183 if let failure = shutdownFailure { 184 shutdownFailure = nil 185 throw failure 186 } 187 let alreadyClosed = shutdownCompleted 188 shutdownCompleted = true 189 await closed.open() 190 return TeraRuntimeShutdownReceipt(state: "closed", alreadyClosed: alreadyClosed) 191 } 192 193 func mobileSettings() async -> TeraMobileSettings { 194 let pause = settingsPause 195 settingsPause = nil 196 await pause?.wait() 197 return settings() 198 } 199 200 func applyIdentityCommand( 201 expectedRevision _: UInt64, command _: TeraIdentityCommand 202 ) -> TeraSettingsTransition { 203 identityCommands += 1 204 return TeraSettingsTransition( 205 settings: settings(), runtimeRestartRequired: false, 206 outboxRequeueRequired: false, mediaCacheInvalidationRequired: false 207 ) 208 } 209 210 private func settings() -> TeraMobileSettings { 211 TeraMobileSettings( 212 revision: 1, 213 identity: TeraSettingsIdentityState( 214 identities: [TeraSettingsIdentity(id: "identity", publicKeyHex: value.identity.publicKeyHex)], 215 activeIdentityID: "identity", lockState: .unlocked, pendingImportOperationID: nil 216 ), 217 networkEnvironment: .simulator, 218 relays: [TeraRelayPreference(url: "ws://127.0.0.1:7447", access: .readWrite)], 219 blossomAuthority: .loopbackDevelopment, blossomPrimaryOrigin: "http://127.0.0.1:3000", 220 blossomFallbackOrigins: [], allowCellularDownloads: true, 221 allowCellularUploads: true, allowBackgroundTransfers: true, 222 mediaCacheBytes: 256 * 1_048_576, mediaCacheArtifacts: 1024 223 ) 224 } 225 226 private func unsupported() -> TeraRuntimeFailure { 227 .local(operation: "test.resource", code: "test.unavailable", safeMessage: "Test operation unavailable") 228 } 229 }