diff --git a/apps/ios/Tests/BridgeDiscoveryModelTests.swift b/apps/ios/Tests/BridgeDiscoveryModelTests.swift new file mode 100644 index 000000000..299422931 --- /dev/null +++ b/apps/ios/Tests/BridgeDiscoveryModelTests.swift @@ -0,0 +1,22 @@ +import Testing +@testable import Clawdis + +@Suite(.serialized) struct BridgeDiscoveryModelTests { + @Test @MainActor func debugLoggingCapturesLifecycleAndResets() { + let model = BridgeDiscoveryModel() + + #expect(model.debugLog.isEmpty) + #expect(model.statusText == "Idle") + + model.setDebugLoggingEnabled(true) + #expect(model.debugLog.count >= 2) + + model.stop() + #expect(model.statusText == "Stopped") + #expect(model.bridges.isEmpty) + #expect(model.debugLog.count >= 3) + + model.setDebugLoggingEnabled(false) + #expect(model.debugLog.isEmpty) + } +} diff --git a/apps/ios/Tests/BridgeSettingsStoreTests.swift b/apps/ios/Tests/BridgeSettingsStoreTests.swift new file mode 100644 index 000000000..f9e064e30 --- /dev/null +++ b/apps/ios/Tests/BridgeSettingsStoreTests.swift @@ -0,0 +1,127 @@ +import Foundation +import Testing +@testable import Clawdis + +private struct KeychainEntry: Hashable { + let service: String + let account: String +} + +private let bridgeService = "com.steipete.clawdis.bridge" +private let nodeService = "com.steipete.clawdis.node" +private let instanceIdEntry = KeychainEntry(service: nodeService, account: "instanceId") +private let preferredBridgeEntry = KeychainEntry(service: bridgeService, account: "preferredStableID") +private let lastBridgeEntry = KeychainEntry(service: bridgeService, account: "lastDiscoveredStableID") + +private func snapshotDefaults(_ keys: [String]) -> [String: Any?] { + let defaults = UserDefaults.standard + var snapshot: [String: Any?] = [:] + for key in keys { + snapshot[key] = defaults.object(forKey: key) + } + return snapshot +} + +private func applyDefaults(_ values: [String: Any?]) { + let defaults = UserDefaults.standard + for (key, value) in values { + if let value { + defaults.set(value, forKey: key) + } else { + defaults.removeObject(forKey: key) + } + } +} + +private func restoreDefaults(_ snapshot: [String: Any?]) { + applyDefaults(snapshot) +} + +private func snapshotKeychain(_ entries: [KeychainEntry]) -> [KeychainEntry: String?] { + var snapshot: [KeychainEntry: String?] = [:] + for entry in entries { + snapshot[entry] = KeychainStore.loadString(service: entry.service, account: entry.account) + } + return snapshot +} + +private func applyKeychain(_ values: [KeychainEntry: String?]) { + for (entry, value) in values { + if let value { + _ = KeychainStore.saveString(value, service: entry.service, account: entry.account) + } else { + _ = KeychainStore.delete(service: entry.service, account: entry.account) + } + } +} + +private func restoreKeychain(_ snapshot: [KeychainEntry: String?]) { + applyKeychain(snapshot) +} + +@Suite(.serialized) struct BridgeSettingsStoreTests { + @Test func bootstrapCopiesDefaultsToKeychainWhenMissing() { + let defaultsKeys = [ + "node.instanceId", + "bridge.preferredStableID", + "bridge.lastDiscoveredStableID", + ] + let entries = [instanceIdEntry, preferredBridgeEntry, lastBridgeEntry] + let defaultsSnapshot = snapshotDefaults(defaultsKeys) + let keychainSnapshot = snapshotKeychain(entries) + defer { + restoreDefaults(defaultsSnapshot) + restoreKeychain(keychainSnapshot) + } + + applyDefaults([ + "node.instanceId": "node-test", + "bridge.preferredStableID": "preferred-test", + "bridge.lastDiscoveredStableID": "last-test", + ]) + applyKeychain([ + instanceIdEntry: nil, + preferredBridgeEntry: nil, + lastBridgeEntry: nil, + ]) + + BridgeSettingsStore.bootstrapPersistence() + + #expect(KeychainStore.loadString(service: nodeService, account: "instanceId") == "node-test") + #expect(KeychainStore.loadString(service: bridgeService, account: "preferredStableID") == "preferred-test") + #expect(KeychainStore.loadString(service: bridgeService, account: "lastDiscoveredStableID") == "last-test") + } + + @Test func bootstrapCopiesKeychainToDefaultsWhenMissing() { + let defaultsKeys = [ + "node.instanceId", + "bridge.preferredStableID", + "bridge.lastDiscoveredStableID", + ] + let entries = [instanceIdEntry, preferredBridgeEntry, lastBridgeEntry] + let defaultsSnapshot = snapshotDefaults(defaultsKeys) + let keychainSnapshot = snapshotKeychain(entries) + defer { + restoreDefaults(defaultsSnapshot) + restoreKeychain(keychainSnapshot) + } + + applyDefaults([ + "node.instanceId": nil, + "bridge.preferredStableID": nil, + "bridge.lastDiscoveredStableID": nil, + ]) + applyKeychain([ + instanceIdEntry: "node-from-keychain", + preferredBridgeEntry: "preferred-from-keychain", + lastBridgeEntry: "last-from-keychain", + ]) + + BridgeSettingsStore.bootstrapPersistence() + + let defaults = UserDefaults.standard + #expect(defaults.string(forKey: "node.instanceId") == "node-from-keychain") + #expect(defaults.string(forKey: "bridge.preferredStableID") == "preferred-from-keychain") + #expect(defaults.string(forKey: "bridge.lastDiscoveredStableID") == "last-from-keychain") + } +}