File size: 7,747 Bytes
fc93158 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 | import Darwin
import Foundation
import Testing
@testable import OpenClaw
@Suite(.serialized) struct CommandResolverTests {
private func makeDefaults() -> UserDefaults {
// Use a unique suite to avoid cross-suite concurrency on UserDefaults.standard.
UserDefaults(suiteName: "CommandResolverTests.\(UUID().uuidString)")!
}
private func makeLocalDefaults() -> UserDefaults {
let defaults = self.makeDefaults()
defaults.set(AppState.ConnectionMode.local.rawValue, forKey: connectionModeKey)
return defaults
}
private func makeProjectRootWithPnpm() throws -> (tmp: URL, pnpmPath: URL) {
let tmp = try makeTempDirForTests()
CommandResolver.setProjectRoot(tmp.path)
let pnpmPath = tmp.appendingPathComponent("node_modules/.bin/pnpm")
try makeExecutableForTests(at: pnpmPath)
return (tmp, pnpmPath)
}
@Test func `prefers open claw binary`() throws {
let defaults = self.makeLocalDefaults()
let tmp = try makeTempDirForTests()
CommandResolver.setProjectRoot(tmp.path)
let openclawPath = tmp.appendingPathComponent("node_modules/.bin/openclaw")
try makeExecutableForTests(at: openclawPath)
let cmd = CommandResolver.openclawCommand(subcommand: "gateway", defaults: defaults, configRoot: [:])
#expect(cmd.prefix(2).elementsEqual([openclawPath.path, "gateway"]))
}
@Test func `falls back to node and script`() throws {
let defaults = self.makeLocalDefaults()
let tmp = try makeTempDirForTests()
CommandResolver.setProjectRoot(tmp.path)
let nodePath = tmp.appendingPathComponent("node_modules/.bin/node")
let scriptPath = tmp.appendingPathComponent("bin/openclaw.js")
try makeExecutableForTests(at: nodePath)
try "#!/bin/sh\necho v22.0.0\n".write(to: nodePath, atomically: true, encoding: .utf8)
try FileManager().setAttributes([.posixPermissions: 0o755], ofItemAtPath: nodePath.path)
try makeExecutableForTests(at: scriptPath)
let cmd = CommandResolver.openclawCommand(
subcommand: "rpc",
defaults: defaults,
configRoot: [:],
searchPaths: [tmp.appendingPathComponent("node_modules/.bin").path])
#expect(cmd.count >= 3)
if cmd.count >= 3 {
#expect(cmd[0] == nodePath.path)
#expect(cmd[1] == scriptPath.path)
#expect(cmd[2] == "rpc")
}
}
@Test func `prefers open claw binary over pnpm`() throws {
let defaults = self.makeLocalDefaults()
let tmp = try makeTempDirForTests()
CommandResolver.setProjectRoot(tmp.path)
let binDir = tmp.appendingPathComponent("bin")
let openclawPath = binDir.appendingPathComponent("openclaw")
let pnpmPath = binDir.appendingPathComponent("pnpm")
try makeExecutableForTests(at: openclawPath)
try makeExecutableForTests(at: pnpmPath)
let cmd = CommandResolver.openclawCommand(
subcommand: "rpc",
defaults: defaults,
configRoot: [:],
searchPaths: [binDir.path])
#expect(cmd.prefix(2).elementsEqual([openclawPath.path, "rpc"]))
}
@Test func `uses open claw binary without node runtime`() throws {
let defaults = self.makeLocalDefaults()
let tmp = try makeTempDirForTests()
CommandResolver.setProjectRoot(tmp.path)
let binDir = tmp.appendingPathComponent("bin")
let openclawPath = binDir.appendingPathComponent("openclaw")
try makeExecutableForTests(at: openclawPath)
let cmd = CommandResolver.openclawCommand(
subcommand: "gateway",
defaults: defaults,
configRoot: [:],
searchPaths: [binDir.path])
#expect(cmd.prefix(2).elementsEqual([openclawPath.path, "gateway"]))
}
@Test func `falls back to pnpm`() throws {
let defaults = self.makeLocalDefaults()
let (tmp, pnpmPath) = try self.makeProjectRootWithPnpm()
let cmd = CommandResolver.openclawCommand(
subcommand: "rpc",
defaults: defaults,
configRoot: [:],
searchPaths: [tmp.appendingPathComponent("node_modules/.bin").path])
#expect(cmd.prefix(4).elementsEqual([pnpmPath.path, "--silent", "openclaw", "rpc"]))
}
@Test func `pnpm keeps extra args after subcommand`() throws {
let defaults = self.makeLocalDefaults()
let (tmp, pnpmPath) = try self.makeProjectRootWithPnpm()
let cmd = CommandResolver.openclawCommand(
subcommand: "health",
extraArgs: ["--json", "--timeout", "5"],
defaults: defaults,
configRoot: [:],
searchPaths: [tmp.appendingPathComponent("node_modules/.bin").path])
#expect(cmd.prefix(5).elementsEqual([pnpmPath.path, "--silent", "openclaw", "health", "--json"]))
#expect(cmd.suffix(2).elementsEqual(["--timeout", "5"]))
}
@Test func `preferred paths start with project node bins`() throws {
let tmp = try makeTempDirForTests()
CommandResolver.setProjectRoot(tmp.path)
let first = CommandResolver.preferredPaths().first
#expect(first == tmp.appendingPathComponent("node_modules/.bin").path)
}
@Test func `builds SSH command for remote mode`() {
let defaults = self.makeDefaults()
defaults.set(AppState.ConnectionMode.remote.rawValue, forKey: connectionModeKey)
defaults.set("openclaw@example.com:2222", forKey: remoteTargetKey)
defaults.set("/tmp/id_ed25519", forKey: remoteIdentityKey)
defaults.set("/srv/openclaw", forKey: remoteProjectRootKey)
let cmd = CommandResolver.openclawCommand(
subcommand: "status",
extraArgs: ["--json"],
defaults: defaults,
configRoot: [:])
#expect(cmd.first == "/usr/bin/ssh")
if let marker = cmd.firstIndex(of: "--") {
#expect(cmd[marker + 1] == "openclaw@example.com")
} else {
#expect(Bool(false))
}
#expect(cmd.contains("-i"))
#expect(cmd.contains("/tmp/id_ed25519"))
if let script = cmd.last {
#expect(script.contains("PRJ='/srv/openclaw'"))
#expect(script.contains("cd \"$PRJ\""))
#expect(script.contains("openclaw"))
#expect(script.contains("status"))
#expect(script.contains("--json"))
#expect(script.contains("CLI="))
}
}
@Test func `rejects unsafe SSH targets`() {
#expect(CommandResolver.parseSSHTarget("-oProxyCommand=calc") == nil)
#expect(CommandResolver.parseSSHTarget("host:-oProxyCommand=calc") == nil)
#expect(CommandResolver.parseSSHTarget("user@host:2222")?.port == 2222)
}
@Test func `config root local overrides remote defaults`() throws {
let defaults = self.makeDefaults()
defaults.set(AppState.ConnectionMode.remote.rawValue, forKey: connectionModeKey)
defaults.set("openclaw@example.com:2222", forKey: remoteTargetKey)
let tmp = try makeTempDirForTests()
CommandResolver.setProjectRoot(tmp.path)
let openclawPath = tmp.appendingPathComponent("node_modules/.bin/openclaw")
try makeExecutableForTests(at: openclawPath)
let cmd = CommandResolver.openclawCommand(
subcommand: "daemon",
defaults: defaults,
configRoot: ["gateway": ["mode": "local"]])
#expect(cmd.first == openclawPath.path)
#expect(cmd.count >= 2)
if cmd.count >= 2 {
#expect(cmd[1] == "daemon")
}
}
}
|