mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-14 22:20:46 +00:00
Fixes #59545.
Suppress the macOS General/Tailscale initial hydration apply path from rewriting openclaw.json when settings are unchanged, and add regression coverage for gateway/auth/meta/wizard preservation.
Verified on the retry head 8a30aa831c:
- GitHub CI completed successfully, including macos-node, macos-swift, check-docs, security, Workflow Sanity, and OpenGrep.
- Review threads were empty before merge.
- Duplicate sweep kept #59545 as the canonical standalone issue; no duplicate closures were appropriate.
132 lines
4.9 KiB
Swift
132 lines
4.9 KiB
Swift
import SwiftUI
|
|
import Testing
|
|
@testable import OpenClaw
|
|
|
|
@Suite(.serialized)
|
|
@MainActor
|
|
struct TailscaleIntegrationSectionTests {
|
|
@Test func `tailscale section builds body when not installed`() {
|
|
let service = TailscaleService(isInstalled: false, isRunning: false, statusError: "not installed")
|
|
var view = TailscaleIntegrationSection(connectionMode: .local, isPaused: false)
|
|
view.setTestingService(service)
|
|
view.setTestingState(mode: "off", requireCredentials: false, statusMessage: "Idle")
|
|
_ = view.body
|
|
}
|
|
|
|
@Test func `tailscale section builds body for serve mode`() {
|
|
let service = TailscaleService(
|
|
isInstalled: true,
|
|
isRunning: true,
|
|
tailscaleHostname: "openclaw.tailnet.ts.net",
|
|
tailscaleIP: "100.64.0.1")
|
|
var view = TailscaleIntegrationSection(connectionMode: .local, isPaused: false)
|
|
view.setTestingService(service)
|
|
view.setTestingState(
|
|
mode: "serve",
|
|
requireCredentials: true,
|
|
password: "secret",
|
|
statusMessage: "Running")
|
|
_ = view.body
|
|
}
|
|
|
|
@Test func `tailscale section builds body for funnel mode`() {
|
|
let service = TailscaleService(
|
|
isInstalled: true,
|
|
isRunning: false,
|
|
tailscaleHostname: nil,
|
|
tailscaleIP: nil,
|
|
statusError: "not running")
|
|
var view = TailscaleIntegrationSection(connectionMode: .remote, isPaused: false)
|
|
view.setTestingService(service)
|
|
view.setTestingState(
|
|
mode: "funnel",
|
|
requireCredentials: false,
|
|
statusMessage: "Needs start",
|
|
validationMessage: "Invalid token")
|
|
_ = view.body
|
|
}
|
|
|
|
@Test func `general tailscale hydration does not rewrite existing config`() async throws {
|
|
let stateDir = FileManager().temporaryDirectory
|
|
.appendingPathComponent("openclaw-state-\(UUID().uuidString)", isDirectory: true)
|
|
let configPath = stateDir.appendingPathComponent("openclaw.json")
|
|
|
|
defer { try? FileManager().removeItem(at: stateDir) }
|
|
|
|
try FileManager().createDirectory(at: stateDir, withIntermediateDirectories: true)
|
|
let initialConfig = """
|
|
{
|
|
"meta": {
|
|
"lastTouchedVersion": "2026.3.28",
|
|
"lastTouchedAt": "2026-03-31T13:15:24.532Z"
|
|
},
|
|
"wizard": {
|
|
"lastRunAt": "2026-03-30T14:24:54.570Z",
|
|
"lastRunVersion": "2026.3.24"
|
|
},
|
|
"gateway": {
|
|
"mode": "local",
|
|
"port": 18789,
|
|
"bind": "auto",
|
|
"tailscale": {
|
|
"mode": "serve"
|
|
},
|
|
"auth": {
|
|
"mode": "token",
|
|
"token": "existing-token"
|
|
}
|
|
}
|
|
}
|
|
"""
|
|
|
|
try initialConfig.write(to: configPath, atomically: true, encoding: .utf8)
|
|
|
|
try await TestIsolation.withEnvValues([
|
|
"OPENCLAW_STATE_DIR": stateDir.path,
|
|
"OPENCLAW_CONFIG_PATH": configPath.path,
|
|
]) {
|
|
let before = try Data(contentsOf: configPath)
|
|
let root = try #require(
|
|
JSONSerialization.jsonObject(with: before) as? [String: Any])
|
|
|
|
await TailscaleIntegrationSection.simulateHydrationApplyForTesting(
|
|
root: root,
|
|
connectionMode: .local,
|
|
isPaused: true,
|
|
saveRoot: { root in
|
|
OpenClawConfigFile.saveDict(root, allowGatewayAuthMutation: true)
|
|
})
|
|
|
|
let after = try Data(contentsOf: configPath)
|
|
#expect(after == before)
|
|
|
|
let afterRoot = try #require(
|
|
JSONSerialization.jsonObject(with: after) as? [String: Any])
|
|
let gateway = try #require(afterRoot["gateway"] as? [String: Any])
|
|
let auth = try #require(gateway["auth"] as? [String: Any])
|
|
let meta = try #require(afterRoot["meta"] as? [String: Any])
|
|
let wizard = try #require(afterRoot["wizard"] as? [String: Any])
|
|
|
|
#expect(gateway["bind"] as? String == "auto")
|
|
#expect(auth["mode"] as? String == "token")
|
|
#expect(auth["token"] as? String == "existing-token") // pragma: allowlist secret
|
|
#expect(meta["lastTouchedAt"] as? String == "2026-03-31T13:15:24.532Z")
|
|
#expect(wizard["lastRunAt"] as? String == "2026-03-30T14:24:54.570Z")
|
|
#expect(wizard["lastRunVersion"] as? String == "2026.3.24")
|
|
}
|
|
}
|
|
|
|
@Test func `unchanged tailscale apply clears stale messages`() {
|
|
let messages = TailscaleIntegrationSection.messagesForTesting(
|
|
didApply: false,
|
|
success: true,
|
|
connectionMode: .local,
|
|
isPaused: false)
|
|
|
|
#expect(messages.statusMessage == nil)
|
|
#expect(messages.validationMessage == nil)
|
|
#expect(messages.shouldRecordSuccess == false)
|
|
#expect(messages.shouldRestartGateway == false)
|
|
}
|
|
}
|