Files
bot/apps/ios/Tests/GatewaySettingsStoreTests.swift
zeekay acaeda400a Eradicate OpenClaw — resolve merge conflicts and finish the rebrand
Three coordinated things:

1. Resolve the committed upstream/main merge conflicts. 94 files were
   sitting with raw <<<<<<< HEAD ... >>>>>>> upstream/main blocks. Every
   conflict had HanzoBot/Bot on HEAD and OpenClaw on upstream. Resolved
   by keeping HEAD everywhere — 540 conflict blocks.

2. Rewrite remaining OpenClaw text refs.
     OPENCLAW   → HANZO_BOT
     OpenClaw   → HanzoBot          (PascalCase type identifiers)
     openclaw   → bot               (codebase convention — botToken,
                                    botUsername etc.; avoids invalid
                                    hyphenated JS identifiers)
     openclaw[_]/openclaw[A-Z] → bot[_]/bot[A-Z]   (compound)
     ai.openclaw.x → ai.hanzo.bot.x  (JVM package path)

3. Delete or rename openclaw-named file paths (116 of them). Dead
   duplicates with a Bot-named canonical from the partial migration:
   deleted. Otherwise renamed.
     ai/openclaw/**           → deleted (ai/hanzo/bot/** canonical)
     Sources/OpenClaw*/       → deleted (Sources/Bot* canonical)
     Tests/OpenClawIPCTests/  → deleted (Tests/BotIPCTests canonical)
     OpenClawKit/             → deleted (BotKit canonical)
     openclaw-tools.*.ts      → deleted (bot-tools.*.ts canonical)
     openclaw-root.ts etc.    → deleted (bot-root.ts canonical)
     types.openclaw.ts        → deleted (types.bot.ts canonical)
     extensions/*/openclaw.plugin.json → renamed hanzo-bot.plugin.json
     docs/start/openclaw.md   → renamed hanzo-bot.md (+ zh-CN)
     docs/assets/openclaw-*.png, whatsapp-openclaw*.jpg → deleted
     scripts/.../openclaw-*   → deleted (bot/hanzo-bot parallels)
     openclaw.mjs             → deleted (hanzo-bot.mjs is package.bin)

Then patched 65 broken JS/TS import strings where hanzo-bot had ended
up inside an import path ('./types.hanzo-bot.js' → './types.bot.js'
etc., since the on-disk filename uses the brand-neutral bot- prefix).

Pre-existing lint debt cleaned up to get oxlint --type-aware to 0/0:
removed the dead i18n test that referenced a path no longer existing;
defined the missing DIDConfig and WalletConfig types in types.base.ts
(they were imported but never declared); dropped unused imports;
collapsed redundant type assertion and a tautological meta.bot lookup
that the openclaw→bot rename made redundant.

The a2ui.bundle.js generated artifact is kept at its pre-rebrand
contents — it gets regenerated by 'pnpm canvas:a2ui:bundle' from
sources I cannot rebuild in this commit; the source side is clean.

Verified: 0 occurrences of openclaw (case-insensitive) in tree source
content, 0 file or directory paths with openclaw in the name; oxlint
--type-aware src test reports 0/0.
2026-05-11 17:56:29 -07:00

175 lines
6.3 KiB
Swift

import Foundation
import Testing
@testable import HanzoBot
private struct KeychainEntry: Hashable {
let service: String
let account: String
}
private let gatewayService = "ai.hanzo.bot.gateway"
private let nodeService = "ai.hanzo.bot.node"
private let instanceIdEntry = KeychainEntry(service: nodeService, account: "instanceId")
private let preferredGatewayEntry = KeychainEntry(service: gatewayService, account: "preferredStableID")
private let lastGatewayEntry = KeychainEntry(service: gatewayService, 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)
}
private func withBootstrapSnapshots(_ body: () -> Void) {
let defaultsSnapshot = snapshotDefaults(bootstrapDefaultsKeys)
let keychainSnapshot = snapshotKeychain(bootstrapKeychainEntries)
defer {
restoreDefaults(defaultsSnapshot)
restoreKeychain(keychainSnapshot)
}
body()
}
private func withLastGatewaySnapshot(_ body: () -> Void) {
let defaultsSnapshot = snapshotDefaults(lastGatewayDefaultsKeys)
let keychainSnapshot = snapshotKeychain([lastGatewayKeychainEntry])
defer {
restoreDefaults(defaultsSnapshot)
restoreKeychain(keychainSnapshot)
}
body()
}
@Suite(.serialized) struct GatewaySettingsStoreTests {
@Test func bootstrapCopiesDefaultsToKeychainWhenMissing() {
withBootstrapSnapshots {
applyDefaults([
"node.instanceId": "node-test",
"gateway.preferredStableID": "preferred-test",
"gateway.lastDiscoveredStableID": "last-test",
])
applyKeychain([
instanceIdEntry: nil,
preferredGatewayEntry: nil,
lastGatewayEntry: nil,
])
GatewaySettingsStore.bootstrapPersistence()
#expect(KeychainStore.loadString(service: nodeService, account: "instanceId") == "node-test")
#expect(KeychainStore.loadString(service: gatewayService, account: "preferredStableID") == "preferred-test")
#expect(KeychainStore.loadString(service: gatewayService, account: "lastDiscoveredStableID") == "last-test")
}
}
@Test func bootstrapCopiesKeychainToDefaultsWhenMissing() {
withBootstrapSnapshots {
applyDefaults([
"node.instanceId": nil,
"gateway.preferredStableID": nil,
"gateway.lastDiscoveredStableID": nil,
])
applyKeychain([
instanceIdEntry: "node-from-keychain",
preferredGatewayEntry: "preferred-from-keychain",
lastGatewayEntry: "last-from-keychain",
])
GatewaySettingsStore.bootstrapPersistence()
let defaults = UserDefaults.standard
#expect(defaults.string(forKey: "node.instanceId") == "node-from-keychain")
#expect(defaults.string(forKey: "gateway.preferredStableID") == "preferred-from-keychain")
#expect(defaults.string(forKey: "gateway.lastDiscoveredStableID") == "last-from-keychain")
}
}
@Test func lastGateway_manualRoundTrip() {
withLastGatewaySnapshot {
GatewaySettingsStore.saveLastGatewayConnectionManual(
host: "example.com",
port: 443,
useTLS: true,
stableID: "manual|example.com|443")
let loaded = GatewaySettingsStore.loadLastGatewayConnection()
#expect(loaded == .manual(host: "example.com", port: 443, useTLS: true, stableID: "manual|example.com|443"))
}
}
@Test func lastGateway_discoveredOverwritesManual() {
withLastGatewaySnapshot {
GatewaySettingsStore.saveLastGatewayConnectionManual(
host: "10.0.0.99",
port: 18789,
useTLS: true,
stableID: "manual|10.0.0.99|18789")
GatewaySettingsStore.saveLastGatewayConnectionDiscovered(stableID: "gw|abc", useTLS: true)
#expect(GatewaySettingsStore.loadLastGatewayConnection() == .discovered(stableID: "gw|abc", useTLS: true))
}
}
@Test func lastGateway_migratesFromUserDefaults() {
withLastGatewaySnapshot {
// Clear Keychain entry and plant legacy UserDefaults values.
applyKeychain([lastGatewayKeychainEntry: nil])
applyDefaults([
"gateway.last.kind": nil,
"gateway.last.host": "example.org",
"gateway.last.port": 18789,
"gateway.last.tls": false,
"gateway.last.stableID": "manual|example.org|18789",
])
let loaded = GatewaySettingsStore.loadLastGatewayConnection()
#expect(loaded == .manual(host: "example.org", port: 18789, useTLS: false, stableID: "manual|example.org|18789"))
// Legacy keys should be cleaned up after migration.
let defaults = UserDefaults.standard
#expect(defaults.object(forKey: "gateway.last.stableID") == nil)
#expect(defaults.object(forKey: "gateway.last.host") == nil)
}
}
}