Files
openclaw/src/cli/program.smoke.test.ts
Jason (Json) 3553aa3763 fix(tui): bound standalone exit
Fix standalone TUI shutdown so `/exit` cannot leave onboarding-launched TUI child processes alive after `runTui` returns.

Adds the maintainer changelog entry and keeps embedded `runTui` callers unaffected by making the post-return process-exit guard CLI-only.

Verification:
- `git diff --check origin/main...HEAD`
- `node scripts/run-vitest.mjs src/tui/tui.test.ts src/tui/tui-command-handlers.test.ts src/cli/program.smoke.test.ts src/tui/tui-launch.test.ts`
- `.agents/skills/autoreview/scripts/autoreview --mode branch --base origin/main --parallel-tests "node scripts/run-vitest.mjs src/tui/tui.test.ts src/tui/tui-command-handlers.test.ts src/cli/program.smoke.test.ts src/tui/tui-launch.test.ts"`
- GitHub exact-head checks passed for `2413f48fc89b4deb9c1923ff63085c3cc2a69522`: CI `26023555738`, CodeQL `26023555736`, CodeQL Critical Quality `26023555785`, Real behavior proof `26023571192`, OpenGrep PR Diff `26023555745`, Workflow Sanity `26023555925`.
- `checks-node-core-fast` initially timed out in unrelated `src/infra/outbound/source-delivery-plan.test.ts`; rerunning failed jobs passed on the same head SHA.
2026-05-18 17:07:32 +08:00

97 lines
2.8 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from "vitest";
import { buildProgram } from "./program.js";
import {
configureCommand,
ensureConfigReady,
installBaseProgramMocks,
installSmokeProgramMocks,
runCrestodian,
runTui,
runtime,
setupCommand,
setupWizardCommand,
} from "./program.test-mocks.js";
installBaseProgramMocks();
installSmokeProgramMocks();
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);
runCrestodian.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;
forceProcessExitOnReturn?: boolean;
};
expect(options?.timeoutMs).toBe(45000);
expect(options?.forceProcessExitOnReturn).toBe(true);
});
it("runs crestodian one-shot requests", async () => {
await runProgram(["crestodian", "--message", "status"]);
const options = firstMockArg(runCrestodian) as {
message?: string;
yes?: boolean;
json?: boolean;
};
expect(options?.message).toBe("status");
expect(options?.yes).toBe(false);
expect(options?.json).toBe(false);
});
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("runs setup wizard when wizard flags are present", async () => {
await runProgram(["setup", "--remote-url", "ws://example"]);
expect(setupCommand).not.toHaveBeenCalled();
expect(setupWizardCommand).toHaveBeenCalledTimes(1);
});
});