Files
openclaw/src/cli/gateway-rpc.runtime.test.ts
openclaw-clownfish[bot] 99d0bdc23a fix(cli): validate gateway RPC timeout inputs
Reject malformed or explicit empty Gateway RPC timeout values before opening Gateway calls, align the shared Gateway RPC omitted-timeout fallback with the 30000 ms CLI default, and validate explicit `cron add --timeout-seconds` values at the CLI boundary.

Carries forward the useful source work from #54646 and the earlier timeout-validation context from #40953. #60661 remains separate accepted-run timeout semantics work and is intentionally not folded into this change.

Validation:
- `npm run review-results -- /tmp/clownfish-check-27341769444`
- `git diff --check`
- OpenClaw PR checks on `ce7bd8b9388a5689b14ddc2b3a984f7b4647e5ca`: 132 pass, 0 pending, 0 failing
- ClawSweeper re-review: https://github.com/openclaw/clawsweeper/actions/runs/27344244608

Co-authored-by: RayRuan <43744645+ruanrrn@users.noreply.github.com>
Co-authored-by: Homeran <11574611+comeran@users.noreply.github.com>
2026-06-11 20:52:07 +09:00

77 lines
2.5 KiB
TypeScript

// Gateway RPC runtime tests cover CLI gateway RPC calls and runtime error handling.
import { beforeEach, describe, expect, it, vi } from "vitest";
const callGatewayMock = vi.fn(async () => ({ ok: true }));
vi.mock("../gateway/call.js", () => ({
callGateway: callGatewayMock,
}));
vi.mock("./progress.js", () => ({
withProgress: async (_options: unknown, action: () => Promise<unknown>) => await action(),
}));
const { callGatewayFromCliRuntime } = await import("./gateway-rpc.runtime.js");
describe("callGatewayFromCliRuntime", () => {
beforeEach(() => {
callGatewayMock.mockClear().mockResolvedValue({ ok: true });
});
it("uses the 30s Gateway RPC default timeout when --timeout is omitted", async () => {
await callGatewayFromCliRuntime("cron.status", {});
expect(callGatewayMock).toHaveBeenCalledWith(
expect.objectContaining({
method: "cron.status",
timeoutMs: 30_000,
}),
);
});
it.each([
["cron status", "cron.status"],
["cron list", "cron.list"],
["cron add", "cron.add"],
["cron update", "cron.update"],
["cron remove", "cron.remove"],
["cron get", "cron.get"],
["cron runs", "cron.runs"],
["cron run", "cron.run"],
["logs", "logs.tail"],
["secrets reload", "secrets.reload"],
])("rejects malformed shared --timeout before gateway call for %s", async (_name, method) => {
await expect(callGatewayFromCliRuntime(method, { timeout: "10ms" })).rejects.toThrow(
'Invalid --timeout. Use a positive millisecond value, e.g. --timeout 30000. Received: "10ms".',
);
expect(callGatewayMock).not.toHaveBeenCalled();
});
it.each(["", " "])("rejects explicit empty shared --timeout value %j", async (timeout) => {
await expect(callGatewayFromCliRuntime("cron.status", { timeout })).rejects.toThrow(
"Invalid --timeout",
);
expect(callGatewayMock).not.toHaveBeenCalled();
});
it.each(["0", "-1", "1.5"])("rejects invalid shared --timeout value %j", async (timeout) => {
await expect(callGatewayFromCliRuntime("cron.status", { timeout })).rejects.toThrow(
`Received: "${timeout}"`,
);
expect(callGatewayMock).not.toHaveBeenCalled();
});
it("passes strict integer timeouts to the gateway call", async () => {
await callGatewayFromCliRuntime("cron.status", { timeout: "15000" });
expect(callGatewayMock).toHaveBeenCalledWith(
expect.objectContaining({
method: "cron.status",
timeoutMs: 15_000,
}),
);
});
});