254 lines
7.2 KiB
TypeScript
254 lines
7.2 KiB
TypeScript
import { Command } from "commander";
|
|
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
const setVerboseMock = vi.fn();
|
|
const emitCliBannerMock = vi.fn();
|
|
const ensureConfigReadyMock = vi.fn(async () => {});
|
|
const ensurePluginRegistryLoadedMock = vi.fn();
|
|
|
|
const runtimeMock = {
|
|
log: vi.fn(),
|
|
error: vi.fn(),
|
|
exit: vi.fn(),
|
|
};
|
|
|
|
vi.mock("../../globals.js", () => ({
|
|
setVerbose: setVerboseMock,
|
|
}));
|
|
|
|
vi.mock("../../runtime.js", () => ({
|
|
defaultRuntime: runtimeMock,
|
|
}));
|
|
|
|
vi.mock("../banner.js", () => ({
|
|
emitCliBanner: emitCliBannerMock,
|
|
}));
|
|
|
|
vi.mock("../cli-name.js", () => ({
|
|
resolveCliName: () => "openclaw",
|
|
}));
|
|
|
|
vi.mock("./config-guard.js", () => ({
|
|
ensureConfigReady: ensureConfigReadyMock,
|
|
}));
|
|
|
|
vi.mock("../plugin-registry.js", () => ({
|
|
ensurePluginRegistryLoaded: ensurePluginRegistryLoadedMock,
|
|
}));
|
|
|
|
let registerPreActionHooks: typeof import("./preaction.js").registerPreActionHooks;
|
|
let originalProcessArgv: string[];
|
|
let originalProcessTitle: string;
|
|
let originalNodeNoWarnings: string | undefined;
|
|
let originalHideBanner: string | undefined;
|
|
|
|
beforeAll(async () => {
|
|
({ registerPreActionHooks } = await import("./preaction.js"));
|
|
});
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
originalProcessArgv = [...process.argv];
|
|
originalProcessTitle = process.title;
|
|
originalNodeNoWarnings = process.env.NODE_NO_WARNINGS;
|
|
originalHideBanner = process.env.OPENCLAW_HIDE_BANNER;
|
|
delete process.env.NODE_NO_WARNINGS;
|
|
delete process.env.OPENCLAW_HIDE_BANNER;
|
|
});
|
|
|
|
afterEach(() => {
|
|
process.argv = originalProcessArgv;
|
|
process.title = originalProcessTitle;
|
|
if (originalNodeNoWarnings === undefined) {
|
|
delete process.env.NODE_NO_WARNINGS;
|
|
} else {
|
|
process.env.NODE_NO_WARNINGS = originalNodeNoWarnings;
|
|
}
|
|
if (originalHideBanner === undefined) {
|
|
delete process.env.OPENCLAW_HIDE_BANNER;
|
|
} else {
|
|
process.env.OPENCLAW_HIDE_BANNER = originalHideBanner;
|
|
}
|
|
});
|
|
|
|
describe("registerPreActionHooks", () => {
|
|
function buildProgram() {
|
|
const program = new Command().name("openclaw");
|
|
program.command("status").action(() => {});
|
|
program.command("doctor").action(() => {});
|
|
program.command("completion").action(() => {});
|
|
program.command("secrets").action(() => {});
|
|
program
|
|
.command("update")
|
|
.command("status")
|
|
.option("--json")
|
|
.action(() => {});
|
|
const config = program.command("config");
|
|
config
|
|
.command("set")
|
|
.argument("<path>")
|
|
.argument("<value>")
|
|
.option("--json")
|
|
.action(() => {});
|
|
program.command("agents").action(() => {});
|
|
program.command("configure").action(() => {});
|
|
program.command("onboard").action(() => {});
|
|
program
|
|
.command("message")
|
|
.command("send")
|
|
.option("--json")
|
|
.action(() => {});
|
|
registerPreActionHooks(program, "9.9.9-test");
|
|
return program;
|
|
}
|
|
|
|
async function runCommand(
|
|
params: { parseArgv: string[]; processArgv?: string[] },
|
|
program = buildProgram(),
|
|
) {
|
|
process.argv = params.processArgv ?? [...params.parseArgv];
|
|
await program.parseAsync(params.parseArgv, { from: "user" });
|
|
}
|
|
|
|
it("emits banner, resolves config, and enables verbose from --debug", async () => {
|
|
await runCommand({
|
|
parseArgv: ["status"],
|
|
processArgv: ["node", "openclaw", "status", "--debug"],
|
|
});
|
|
|
|
expect(emitCliBannerMock).toHaveBeenCalledWith("9.9.9-test");
|
|
expect(setVerboseMock).toHaveBeenCalledWith(true);
|
|
expect(ensureConfigReadyMock).toHaveBeenCalledWith({
|
|
runtime: runtimeMock,
|
|
commandPath: ["status"],
|
|
});
|
|
expect(ensurePluginRegistryLoadedMock).not.toHaveBeenCalled();
|
|
expect(process.title).toBe("openclaw-status");
|
|
});
|
|
|
|
it("loads plugin registry for plugin-required commands", async () => {
|
|
await runCommand({
|
|
parseArgv: ["message", "send"],
|
|
processArgv: ["node", "openclaw", "message", "send"],
|
|
});
|
|
|
|
expect(setVerboseMock).toHaveBeenCalledWith(false);
|
|
expect(process.env.NODE_NO_WARNINGS).toBe("1");
|
|
expect(ensureConfigReadyMock).toHaveBeenCalledWith({
|
|
runtime: runtimeMock,
|
|
commandPath: ["message", "send"],
|
|
});
|
|
expect(ensurePluginRegistryLoadedMock).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it("loads plugin registry for configure/onboard/agents commands", async () => {
|
|
const commands = ["configure", "onboard", "agents"] as const;
|
|
const program = buildProgram();
|
|
for (const command of commands) {
|
|
vi.clearAllMocks();
|
|
await runCommand(
|
|
{
|
|
parseArgv: [command],
|
|
processArgv: ["node", "openclaw", command],
|
|
},
|
|
program,
|
|
);
|
|
expect(ensurePluginRegistryLoadedMock, command).toHaveBeenCalledTimes(1);
|
|
}
|
|
});
|
|
|
|
it("skips config guard for doctor, completion, and secrets commands", async () => {
|
|
const program = buildProgram();
|
|
await runCommand(
|
|
{
|
|
parseArgv: ["doctor"],
|
|
processArgv: ["node", "openclaw", "doctor"],
|
|
},
|
|
program,
|
|
);
|
|
await runCommand(
|
|
{
|
|
parseArgv: ["completion"],
|
|
processArgv: ["node", "openclaw", "completion"],
|
|
},
|
|
program,
|
|
);
|
|
await runCommand(
|
|
{
|
|
parseArgv: ["secrets"],
|
|
processArgv: ["node", "openclaw", "secrets"],
|
|
},
|
|
program,
|
|
);
|
|
|
|
expect(ensureConfigReadyMock).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("skips preaction work when argv indicates help/version", async () => {
|
|
await runCommand({
|
|
parseArgv: ["status"],
|
|
processArgv: ["node", "openclaw", "--version"],
|
|
});
|
|
|
|
expect(emitCliBannerMock).not.toHaveBeenCalled();
|
|
expect(setVerboseMock).not.toHaveBeenCalled();
|
|
expect(ensureConfigReadyMock).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("hides banner when OPENCLAW_HIDE_BANNER is truthy", async () => {
|
|
process.env.OPENCLAW_HIDE_BANNER = "1";
|
|
await runCommand({
|
|
parseArgv: ["status"],
|
|
processArgv: ["node", "openclaw", "status"],
|
|
});
|
|
|
|
expect(emitCliBannerMock).not.toHaveBeenCalled();
|
|
expect(ensureConfigReadyMock).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it("suppresses doctor stdout for any --json output command", async () => {
|
|
const program = buildProgram();
|
|
await runCommand(
|
|
{
|
|
parseArgv: ["message", "send", "--json"],
|
|
processArgv: ["node", "openclaw", "message", "send", "--json"],
|
|
},
|
|
program,
|
|
);
|
|
|
|
expect(ensureConfigReadyMock).toHaveBeenCalledWith({
|
|
runtime: runtimeMock,
|
|
commandPath: ["message", "send"],
|
|
suppressDoctorStdout: true,
|
|
});
|
|
|
|
vi.clearAllMocks();
|
|
|
|
await runCommand(
|
|
{
|
|
parseArgv: ["update", "status", "--json"],
|
|
processArgv: ["node", "openclaw", "update", "status", "--json"],
|
|
},
|
|
program,
|
|
);
|
|
|
|
expect(ensureConfigReadyMock).toHaveBeenCalledWith({
|
|
runtime: runtimeMock,
|
|
commandPath: ["update", "status"],
|
|
suppressDoctorStdout: true,
|
|
});
|
|
});
|
|
|
|
it("does not treat config set --json (strict-parse alias) as json output mode", async () => {
|
|
await runCommand({
|
|
parseArgv: ["config", "set", "gateway.auth.mode", "{bad", "--json"],
|
|
processArgv: ["node", "openclaw", "config", "set", "gateway.auth.mode", "{bad", "--json"],
|
|
});
|
|
|
|
expect(ensureConfigReadyMock).toHaveBeenCalledWith({
|
|
runtime: runtimeMock,
|
|
commandPath: ["config", "set"],
|
|
});
|
|
});
|
|
});
|