Files
openclaw/src/gateway/server-methods/config.test.ts
2026-04-03 18:53:34 +01:00

102 lines
3.2 KiB
TypeScript

import { execFile } from "node:child_process";
import { afterEach, describe, expect, it, vi } from "vitest";
import { configHandlers, resolveConfigOpenCommand } from "./config.js";
import { createConfigHandlerHarness } from "./config.test-helpers.js";
vi.mock("node:child_process", async (importOriginal) => {
const { mockNodeBuiltinModule } = await import("../../../test/helpers/node-builtin-mocks.js");
return mockNodeBuiltinModule(importOriginal, {
execFile: vi.fn(),
});
});
function invokeExecFileCallback(args: unknown[], error: Error | null) {
const callback = args.at(-1);
expect(callback).toEqual(expect.any(Function));
(callback as (error: Error | null) => void)(error);
}
describe("resolveConfigOpenCommand", () => {
it("uses open on macOS", () => {
expect(resolveConfigOpenCommand("/tmp/openclaw.json", "darwin")).toEqual({
command: "open",
args: ["/tmp/openclaw.json"],
});
});
it("uses xdg-open on Linux", () => {
expect(resolveConfigOpenCommand("/tmp/openclaw.json", "linux")).toEqual({
command: "xdg-open",
args: ["/tmp/openclaw.json"],
});
});
it("uses a quoted PowerShell literal on Windows", () => {
expect(resolveConfigOpenCommand(String.raw`C:\tmp\o'hai & calc.json`, "win32")).toEqual({
command: "powershell.exe",
args: [
"-NoProfile",
"-NonInteractive",
"-Command",
String.raw`Start-Process -LiteralPath 'C:\tmp\o''hai & calc.json'`,
],
});
});
});
describe("config.openFile", () => {
afterEach(() => {
delete process.env.OPENCLAW_CONFIG_PATH;
vi.clearAllMocks();
});
it("opens the configured file without shell interpolation", async () => {
process.env.OPENCLAW_CONFIG_PATH = "/tmp/config $(touch pwned).json";
vi.mocked(execFile).mockImplementation(((...args: unknown[]) => {
expect(["open", "xdg-open", "powershell.exe"]).toContain(args[0]);
expect(args[1]).toEqual(["/tmp/config $(touch pwned).json"]);
invokeExecFileCallback(args, null);
return {} as never;
}) as unknown as typeof execFile);
const { options, respond } = createConfigHandlerHarness({ method: "config.openFile" });
await configHandlers["config.openFile"](options);
expect(respond).toHaveBeenCalledWith(
true,
{
ok: true,
path: "/tmp/config $(touch pwned).json",
},
undefined,
);
});
it("returns a generic error and logs details when the opener fails", async () => {
process.env.OPENCLAW_CONFIG_PATH = "/tmp/config.json";
vi.mocked(execFile).mockImplementation(((...args: unknown[]) => {
invokeExecFileCallback(
args,
Object.assign(new Error("spawn xdg-open ENOENT"), { code: "ENOENT" }),
);
return {} as never;
}) as unknown as typeof execFile);
const { options, respond, logGateway } = createConfigHandlerHarness({
method: "config.openFile",
});
await configHandlers["config.openFile"](options);
expect(respond).toHaveBeenCalledWith(
true,
{
ok: false,
path: "/tmp/config.json",
error: "failed to open config file",
},
undefined,
);
expect(logGateway.warn).toHaveBeenCalledWith(expect.stringContaining("spawn xdg-open ENOENT"));
});
});