mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
055a2dc6ce
* feat(cli): ingest session targets * refactor(ui): remove gateway scope shim * docs(cli): document session targets * fix(cli): classify session target failures * fix(cli): keep session target result private * fix(cli): simplify timeout option warning * build: declare session URL contract dependency * fix(cli): parse bare session URL options symmetrically * fix(cli): preserve command-owned URL arguments * build: keep session URL contract build-only * fix: address session URL review findings * test: preserve session key mock exports * fix: keep session URL helpers internal * fix(tui): preserve URL agent for global sessions * fix(tui): keep URL agent input internal * fix(gateway): reconcile websocket protocol owner * fix(attach): preserve global session agent ownership * fix(attach): enforce global owner at grant boundary
189 lines
5.9 KiB
TypeScript
189 lines
5.9 KiB
TypeScript
// Program smoke tests cover core CLI command registration and startup behavior.
|
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
import { buildProgram } from "./program.js";
|
|
import {
|
|
callGateway,
|
|
configureCommand,
|
|
ensureConfigReady,
|
|
runSystemAgentWithInference,
|
|
runTui,
|
|
runtime,
|
|
setupCommand,
|
|
setupWizardCommand,
|
|
} from "./program.test-mocks.js";
|
|
|
|
vi.mock("./config-cli.js", () => ({
|
|
registerConfigCli: (program: {
|
|
command: (name: string) => { action: (fn: () => unknown) => void };
|
|
}) => {
|
|
program.command("config").action(() => configureCommand({}, runtime));
|
|
},
|
|
runConfigGet: vi.fn(),
|
|
runConfigUnset: vi.fn(),
|
|
}));
|
|
|
|
describe("cli program (smoke)", () => {
|
|
let program = createProgram();
|
|
|
|
function createProgram() {
|
|
return buildProgram();
|
|
}
|
|
|
|
async function runProgram(argv: string[]) {
|
|
await program.parseAsync(argv, { from: "user" });
|
|
}
|
|
|
|
function firstMockArg(mock: { mock: { calls: ReadonlyArray<ReadonlyArray<unknown>> } }): unknown {
|
|
const call = mock.mock.calls[0];
|
|
if (!call) {
|
|
throw new Error("expected mock to have at least one call");
|
|
}
|
|
return call[0];
|
|
}
|
|
|
|
beforeEach(() => {
|
|
program = createProgram();
|
|
vi.clearAllMocks();
|
|
runTui.mockResolvedValue(undefined);
|
|
runSystemAgentWithInference.mockResolvedValue(undefined);
|
|
ensureConfigReady.mockResolvedValue(undefined);
|
|
});
|
|
|
|
it("registers message + status commands", () => {
|
|
const names = program.commands.map((command) => command.name());
|
|
expect(names).toContain("message");
|
|
expect(names).toContain("status");
|
|
});
|
|
|
|
it("runs tui with explicit timeout override", async () => {
|
|
await runProgram(["tui", "--timeout-ms", "45000"]);
|
|
const options = firstMockArg(runTui) as {
|
|
timeoutMs?: number;
|
|
historyLimit?: number;
|
|
forceProcessExitOnReturn?: boolean;
|
|
};
|
|
expect(options?.timeoutMs).toBe(45000);
|
|
expect(options?.historyLimit).toBe(200);
|
|
expect(options?.forceProcessExitOnReturn).toBe(true);
|
|
});
|
|
|
|
it("resolves a positional tui short reference before launch", async () => {
|
|
callGateway.mockResolvedValue({ ok: true, key: "agent:main:thread:resolved" });
|
|
|
|
await runProgram(["tui", "movies-a1166b81"]);
|
|
|
|
expect(callGateway).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
method: "sessions.resolve",
|
|
params: { shortId: "a1166b81", slugHint: "movies" },
|
|
}),
|
|
);
|
|
expect(firstMockArg(runTui)).toMatchObject({
|
|
local: false,
|
|
session: "agent:main:thread:resolved",
|
|
});
|
|
});
|
|
|
|
it("preserves a global-scope URL main session when launching tui", async () => {
|
|
callGateway.mockResolvedValue({
|
|
defaultId: "main",
|
|
mainKey: "main",
|
|
scope: "global",
|
|
agents: [],
|
|
});
|
|
|
|
await runProgram(["tui", "https://gateway.example/dashboard/ops"]);
|
|
|
|
expect(callGateway).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
method: "agents.list",
|
|
params: {},
|
|
}),
|
|
);
|
|
expect(firstMockArg(runTui)).toMatchObject({
|
|
local: false,
|
|
session: "global",
|
|
agentId: "ops",
|
|
});
|
|
});
|
|
|
|
it("leaves tui agent inference unchanged without a URL agent", async () => {
|
|
await runProgram(["tui"]);
|
|
|
|
expect(firstMockArg(runTui)).not.toHaveProperty("agentId");
|
|
});
|
|
|
|
it("rejects a URL target combined with --url", async () => {
|
|
await expect(
|
|
runProgram([
|
|
"tui",
|
|
"https://gateway.example/dashboard/main/movies-a1166b81",
|
|
"--url",
|
|
"wss://other.example",
|
|
]),
|
|
).rejects.toThrow("exit");
|
|
|
|
expect(runtime.error).toHaveBeenCalledWith(expect.stringContaining("pass one target"));
|
|
expect(runTui).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("runs setup one-shot requests", async () => {
|
|
await runProgram(["setup", "--message", "status"]);
|
|
const options = firstMockArg(runSystemAgentWithInference) as {
|
|
message?: string;
|
|
yes?: boolean;
|
|
json?: boolean;
|
|
};
|
|
expect(options?.message).toBe("status");
|
|
expect(options?.yes).toBe(false);
|
|
expect(options?.json).toBe(false);
|
|
expect(runSystemAgentWithInference).toHaveBeenCalledWith(options, runtime);
|
|
});
|
|
|
|
it("warns and ignores invalid tui timeout override", async () => {
|
|
await runProgram(["tui", "--timeout-ms", "nope"]);
|
|
expect(runtime.error).toHaveBeenCalledWith('warning: invalid --timeout-ms "nope"; ignoring');
|
|
const options = firstMockArg(runTui) as { timeoutMs?: number };
|
|
expect(options?.timeoutMs).toBeUndefined();
|
|
});
|
|
|
|
it("rejects partial tui history limits", async () => {
|
|
await expect(runProgram(["tui", "--history-limit", "10x"])).rejects.toThrow("exit");
|
|
expect(runtime.error).toHaveBeenCalledWith(
|
|
"Error: --history-limit must be a positive integer.",
|
|
);
|
|
expect(runTui).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("accepts the maximum Gateway tui history limit", async () => {
|
|
await runProgram(["tui", "--history-limit", "1000"]);
|
|
|
|
expect(firstMockArg(runTui)).toMatchObject({ local: false, historyLimit: 1000 });
|
|
});
|
|
|
|
it.each([
|
|
{ entryPoint: "tui --local", args: ["tui", "--local"] },
|
|
{ entryPoint: "terminal", args: ["terminal"] },
|
|
{ entryPoint: "chat", args: ["chat"] },
|
|
])("preserves oversized history limits for local $entryPoint", async ({ args }) => {
|
|
await runProgram([...args, "--history-limit", "1001"]);
|
|
|
|
expect(firstMockArg(runTui)).toMatchObject({ local: true, historyLimit: 1001 });
|
|
expect(runtime.error).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("rejects tui history limits above the Gateway maximum", async () => {
|
|
await expect(runProgram(["tui", "--history-limit", "1001"])).rejects.toThrow("exit");
|
|
|
|
expect(runtime.error).toHaveBeenCalledWith("Error: --history-limit must be at most 1000.");
|
|
expect(runTui).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("runs setup wizard when wizard flags are present", async () => {
|
|
await runProgram(["setup", "--remote-url", "ws://example"]);
|
|
|
|
expect(setupCommand).not.toHaveBeenCalled();
|
|
expect(setupWizardCommand).toHaveBeenCalledTimes(1);
|
|
});
|
|
});
|