openclaw / src /cli /program /configure-dispatch.test.ts
SaylorTwift's picture
SaylorTwift HF Staff
Add files using upload-large-folder tool
eb3f11e verified
Raw
History Blame Contribute Delete
5.59 kB
import { Command } from "commander";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { createDeferred } from "../../../test/helpers/promise.js";
import { configureCommandFromSectionsArg } from "../../commands/configure.commands.js";
import { defaultRuntime, ExitError } from "../../runtime.js";
import { registerConfigCli } from "../config-cli.js";
import { registerConfigureCommand } from "./register.configure.js";
const mocks = vi.hoisted(() => ({
interactive: vi.fn(),
runWizard: vi.fn(),
runtime: { log: vi.fn(), error: vi.fn(), exit: vi.fn() },
}));
vi.mock("../../commands/configure.wizard.js", () => ({ runConfigureWizard: mocks.runWizard }));
vi.mock("../terminal-interactivity.js", () => ({ isTerminalInteractive: mocks.interactive }));
vi.mock("../../runtime.js", async (importOriginal) => ({
...(await importOriginal<typeof import("../../runtime.js")>()),
defaultRuntime: mocks.runtime,
}));
const routes = ["configure", "config"] as const;
type Route = (typeof routes)[number];
async function parse(route: Route, options: string[] = []) {
const program = new Command();
if (route === "configure") {
registerConfigureCommand(program);
} else {
registerConfigCli(program);
}
return await program.parseAsync([route, ...options], { from: "user" });
}
describe("registered configure dispatch", { concurrent: false }, () => {
beforeEach(() => {
vi.resetAllMocks();
mocks.interactive.mockReturnValue(true);
mocks.runWizard.mockResolvedValue(undefined);
mocks.runtime.exit.mockImplementation((code: number) => {
throw new ExitError(code);
});
});
it.each(routes)("%s omits sections for the full chooser", async (route) => {
await parse(route);
expect(mocks.runWizard).toHaveBeenCalledExactlyOnceWith(
{ command: "configure" },
defaultRuntime,
);
expect(mocks.runWizard.mock.calls[0]?.[1]).toBe(defaultRuntime);
expect(mocks.runtime.exit).not.toHaveBeenCalled();
});
it.each(routes)("%s preserves repeated, trimmed section values", async (route) => {
await parse(route, ["--section", " channels ", "--section", "health", "--section", "channels"]);
expect(mocks.runWizard).toHaveBeenCalledExactlyOnceWith(
{ command: "configure", sections: ["channels", "health", "channels"] },
defaultRuntime,
);
expect(mocks.runWizard.mock.calls[0]?.[1]).toBe(defaultRuntime);
});
it.each(routes)("%s rejects a blank section before testing the terminal", async (route) => {
mocks.interactive.mockReturnValue(false);
await expect(parse(route, ["--section", " "])).rejects.toMatchObject({ code: 1 });
expect(mocks.runtime.error).toHaveBeenCalledOnce();
expect(mocks.runtime.error.mock.calls[0]?.[0]).toContain('Invalid --section: "".');
expect(mocks.interactive).not.toHaveBeenCalled();
expect(mocks.runWizard).not.toHaveBeenCalled();
});
it.each(routes)("%s rejects an unknown section without widening scope", async (route) => {
await expect(parse(route, ["--section", "not-a-section"])).rejects.toMatchObject({ code: 1 });
expect(mocks.runtime.error.mock.calls[0]?.[0]).toContain("Invalid --section: not-a-section.");
expect(mocks.runWizard).not.toHaveBeenCalled();
});
it.each(routes)("%s refuses a non-interactive terminal", async (route) => {
mocks.interactive.mockReturnValue(false);
await expect(parse(route, ["--section", "channels"])).rejects.toMatchObject({ code: 1 });
expect(mocks.runtime.error).toHaveBeenCalledOnce();
expect(mocks.runtime.error.mock.calls[0]?.[0]).toContain(
"requires an interactive terminal (TTY)",
);
expect(mocks.runWizard).not.toHaveBeenCalled();
});
it.each(routes)("%s waits for the admitted wizard to settle", async (route) => {
const entered = createDeferred();
const released = createDeferred();
mocks.runWizard.mockImplementationOnce(async () => {
entered.resolve(undefined);
await released.promise;
});
let settled = false;
const parsing = parse(route, ["--section", "channels"]);
void parsing.catch(entered.reject);
const observed = parsing.then(
() => {
settled = true;
},
() => {
settled = true;
},
);
try {
await entered.promise;
expect(settled).toBe(false);
} finally {
released.resolve(undefined);
await observed;
}
await parsing;
expect(settled).toBe(true);
expect(mocks.runWizard).toHaveBeenCalledOnce();
});
it.each(routes)("%s preserves its registered failure contract", async (route) => {
const failure = new Error("fixture wizard failure");
mocks.runWizard.mockRejectedValueOnce(failure);
if (route === "configure") {
await expect(parse(route)).rejects.toMatchObject({ code: 1 });
expect(mocks.runtime.error).toHaveBeenCalledWith("fixture wizard failure");
} else {
await expect(parse(route)).rejects.toBe(failure);
expect(mocks.runtime.error).not.toHaveBeenCalled();
expect(mocks.runtime.exit).not.toHaveBeenCalled();
}
expect(mocks.runWizard).toHaveBeenCalledOnce();
});
it("passes a caller-provided runtime by identity", async () => {
const runtime = { log: vi.fn(), error: vi.fn(), exit: vi.fn() };
await configureCommandFromSectionsArg(["channels"], runtime, { interactive: true });
expect(mocks.runWizard).toHaveBeenCalledExactlyOnceWith(
{ command: "configure", sections: ["channels"] },
runtime,
);
expect(mocks.runWizard.mock.calls[0]?.[1]).toBe(runtime);
});
});