Files
openclaw/src/cli/cli-utils.test.ts
Gustavo Madeira Santana eff3c5c707 Session/Cron maintenance hardening and cleanup UX (#24753)
Merged via /review-pr -> /prepare-pr -> /merge-pr.

Prepared head SHA: 7533b85156
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Co-authored-by: shakkernerd <165377636+shakkernerd@users.noreply.github.com>
Reviewed-by: @shakkernerd
2026-02-23 22:39:48 +00:00

116 lines
3.8 KiB
TypeScript

import { Command } from "commander";
import { describe, expect, it, vi } from "vitest";
import { parseCanvasSnapshotPayload } from "./nodes-canvas.js";
import { parseByteSize } from "./parse-bytes.js";
import { parseDurationMs } from "./parse-duration.js";
import { shouldSkipRespawnForArgv } from "./respawn-policy.js";
import { waitForever } from "./wait.js";
const { registerDnsCli } = await import("./dns-cli.js");
describe("waitForever", () => {
it("creates an unref'ed interval and returns a pending promise", () => {
const setIntervalSpy = vi.spyOn(global, "setInterval");
const promise = waitForever();
expect(setIntervalSpy).toHaveBeenCalledWith(expect.any(Function), 1_000_000);
expect(promise).toBeInstanceOf(Promise);
setIntervalSpy.mockRestore();
});
});
describe("shouldSkipRespawnForArgv", () => {
it("skips respawn for help/version calls", () => {
const cases = [
["node", "openclaw", "--help"],
["node", "openclaw", "-V"],
] as const;
for (const argv of cases) {
expect(shouldSkipRespawnForArgv([...argv]), argv.join(" ")).toBe(true);
}
});
it("keeps respawn path for normal commands", () => {
expect(shouldSkipRespawnForArgv(["node", "openclaw", "status"])).toBe(false);
});
});
describe("nodes canvas helpers", () => {
it("parses canvas.snapshot payload", () => {
expect(parseCanvasSnapshotPayload({ format: "png", base64: "aGk=" })).toEqual({
format: "png",
base64: "aGk=",
});
});
it("rejects invalid canvas.snapshot payload", () => {
expect(() => parseCanvasSnapshotPayload({ format: "png" })).toThrow(
/invalid canvas\.snapshot payload/i,
);
});
});
describe("dns cli", () => {
it("prints setup info (no apply)", async () => {
const log = vi.spyOn(console, "log").mockImplementation(() => {});
try {
const program = new Command();
registerDnsCli(program);
await program.parseAsync(["dns", "setup", "--domain", "openclaw.internal"], { from: "user" });
const output = log.mock.calls.map((call) => call.join(" ")).join("\\n");
expect(output).toContain("DNS setup");
expect(output).toContain("openclaw.internal");
} finally {
log.mockRestore();
}
});
});
describe("parseByteSize", () => {
it("parses byte-size units and shorthand values", () => {
const cases = [
["parses 10kb", "10kb", 10 * 1024],
["parses 1mb", "1mb", 1024 * 1024],
["parses 2gb", "2gb", 2 * 1024 * 1024 * 1024],
["parses shorthand 5k", "5k", 5 * 1024],
["parses shorthand 1m", "1m", 1024 * 1024],
] as const;
for (const [name, input, expected] of cases) {
expect(parseByteSize(input), name).toBe(expected);
}
});
it("uses default unit when omitted", () => {
expect(parseByteSize("123")).toBe(123);
});
it("rejects invalid values", () => {
const cases = ["", "nope", "-5kb"] as const;
for (const input of cases) {
expect(() => parseByteSize(input), input || "<empty>").toThrow();
}
});
});
describe("parseDurationMs", () => {
it("parses duration strings", () => {
const cases = [
["parses bare ms", "10000", 10_000],
["parses seconds suffix", "10s", 10_000],
["parses minutes suffix", "1m", 60_000],
["parses hours suffix", "2h", 7_200_000],
["parses days suffix", "2d", 172_800_000],
["supports decimals", "0.5s", 500],
["parses composite hours+minutes", "1h30m", 5_400_000],
["parses composite with milliseconds", "2m500ms", 120_500],
] as const;
for (const [name, input, expected] of cases) {
expect(parseDurationMs(input), name).toBe(expected);
}
});
it("rejects invalid composite strings", () => {
expect(() => parseDurationMs("1h30")).toThrow();
expect(() => parseDurationMs("1h-30m")).toThrow();
});
});