mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-26 12:26:38 -06:00
fix(cli): render cron edit JSON failures (#129022)
This commit is contained in:
committed by
GitHub
parent
aec1cd40c0
commit
5cb07fb93b
@@ -72,6 +72,23 @@ describe("cron edit command", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("rethrows contradictory options in JSON mode without accessing the Gateway", async () => {
|
||||
const originalArgv = process.argv;
|
||||
const exitSpy = vi.spyOn(defaultRuntime, "exit").mockImplementation((() => undefined) as never);
|
||||
process.argv = ["node", "openclaw", "cron", "edit", "job-1", "--json"];
|
||||
try {
|
||||
await expect(
|
||||
createCronProgram()
|
||||
.parseAsync(["edit", "job-1", "--enable", "--disable", "--json"], { from: "user" })
|
||||
.then(() => undefined),
|
||||
).rejects.toThrow("Choose --enable or --disable, not both");
|
||||
expect(callGatewayFromCli).not.toHaveBeenCalled();
|
||||
} finally {
|
||||
process.argv = originalArgv;
|
||||
exitSpy.mockRestore();
|
||||
}
|
||||
});
|
||||
|
||||
it("updates the human-readable display name without changing the job name", async () => {
|
||||
await createCronProgram().parseAsync(["edit", "job-1", "--display-name", "Daily summary"], {
|
||||
from: "user",
|
||||
|
||||
@@ -7,15 +7,10 @@ import {
|
||||
import type { Command } from "commander";
|
||||
import type { CronJob } from "../../cron/types.js";
|
||||
import { normalizeHttpWebhookUrl } from "../../cron/webhook-url.js";
|
||||
import { danger } from "../../globals.js";
|
||||
import { formatErrorMessage } from "../../infra/errors.js";
|
||||
import { sanitizeAgentId } from "../../routing/session-key.js";
|
||||
import { defaultRuntime } from "../../runtime.js";
|
||||
import {
|
||||
addGatewayClientOptions,
|
||||
callGatewayFromCli,
|
||||
type GatewayRpcOpts,
|
||||
} from "../gateway-rpc.js";
|
||||
import type { GatewayRpcOpts } from "../gateway-rpc.js";
|
||||
import { addGatewayClientOptions, callGatewayFromCli } from "../gateway-rpc.js";
|
||||
import { parseDurationMs } from "../parse-duration.js";
|
||||
import { isUnknownCronGetMethodError, listCronJobsFromGateway } from "./list-jobs.js";
|
||||
import { createCronOutputCommand } from "./output-mode.js";
|
||||
@@ -27,7 +22,11 @@ import {
|
||||
resolveCronEditScheduleRequest,
|
||||
validateStreamScheduleMetadata,
|
||||
} from "./schedule-options.js";
|
||||
import { getCronChannelOptions, warnIfCronSchedulerDisabled } from "./shared.js";
|
||||
import {
|
||||
getCronChannelOptions,
|
||||
handleCronCliError,
|
||||
warnIfCronSchedulerDisabled,
|
||||
} from "./shared.js";
|
||||
import { normalizeCronSessionTargetOption } from "./thread-id-shared.js";
|
||||
import { readCronTriggerScript } from "./trigger-options.js";
|
||||
|
||||
@@ -439,8 +438,7 @@ export function registerCronEditCommand(cron: Command) {
|
||||
defaultRuntime.writeJson(res);
|
||||
await warnIfCronSchedulerDisabled(opts);
|
||||
} catch (err) {
|
||||
defaultRuntime.error(danger(formatErrorMessage(err)));
|
||||
defaultRuntime.exit(1);
|
||||
handleCronCliError(err);
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -53,6 +53,117 @@ async function seedTrajectorySession(tempHome: string, sessionKey: string) {
|
||||
}
|
||||
|
||||
describe("cli json stdout contract", () => {
|
||||
it.each([
|
||||
{
|
||||
name: "implicit JSON",
|
||||
args: ["cron", "edit", "job-1", "--enable", "--disable"],
|
||||
},
|
||||
{
|
||||
name: "explicit JSON",
|
||||
args: ["cron", "edit", "job-1", "--enable", "--disable", "--json"],
|
||||
},
|
||||
{
|
||||
name: "automation alias implicit JSON",
|
||||
args: ["automations", "edit", "job-1", "--enable", "--disable"],
|
||||
},
|
||||
{
|
||||
name: "ordinary local validation failure",
|
||||
args: ["cron", "edit", "job-1", "--command-cwd", "", "--json"],
|
||||
message: "--command-cwd must not be blank",
|
||||
},
|
||||
{
|
||||
name: "Gateway failure implicit JSON",
|
||||
args: ["cron", "edit", "job-1", "--enable", "--port", "29793", "--token", "fixture-token"],
|
||||
gatewayRequest: true,
|
||||
},
|
||||
{
|
||||
name: "Gateway failure explicit JSON",
|
||||
args: [
|
||||
"cron",
|
||||
"edit",
|
||||
"job-1",
|
||||
"--enable",
|
||||
"--port",
|
||||
"29793",
|
||||
"--token",
|
||||
"fixture-token",
|
||||
"--json",
|
||||
],
|
||||
gatewayRequest: true,
|
||||
},
|
||||
{
|
||||
name: "forced Commander JSON",
|
||||
args: ["cron", "edit", "job-1", "--enable", "--disable", "--json"],
|
||||
commander: true,
|
||||
},
|
||||
{
|
||||
name: "dual-TTY JSON",
|
||||
args: ["cron", "edit", "job-1", "--enable", "--disable", "--json"],
|
||||
tty: true,
|
||||
},
|
||||
{
|
||||
name: "human-output sibling",
|
||||
args: ["cron", "list", "--agent", ""],
|
||||
message: "--agent must not be blank",
|
||||
human: true,
|
||||
},
|
||||
])("renders cron edit failures through the shared owner for $name", async (testCase) => {
|
||||
await withTempHome(
|
||||
async (tempHome) => {
|
||||
const configPath = path.join(tempHome, "missing-openclaw.json");
|
||||
const stateDir = path.join(tempHome, "isolated-state");
|
||||
const gatewayError = "AUTOQA_INJECTED_GATEWAY_FAILURE";
|
||||
const preload = Buffer.from(
|
||||
[
|
||||
'import net from "node:net";',
|
||||
`net.Socket.prototype.connect = function () { throw new Error(${JSON.stringify(gatewayError)}); };`,
|
||||
...("tty" in testCase
|
||||
? [
|
||||
'Object.defineProperty(process.stdout, "isTTY", { value: true, configurable: true });',
|
||||
'Object.defineProperty(process.stderr, "isTTY", { value: true, configurable: true });',
|
||||
]
|
||||
: []),
|
||||
].join("\n"),
|
||||
).toString("base64");
|
||||
const result = runBuiltCli(tempHome, testCase.args, {
|
||||
NODE_OPTIONS: `--import=data:text/javascript;base64,${preload}`,
|
||||
OPENCLAW_CONFIG_PATH: configPath,
|
||||
OPENCLAW_DISABLE_BUNDLED_PLUGINS: "1",
|
||||
OPENCLAW_STATE_DIR: stateDir,
|
||||
...("commander" in testCase ? { OPENCLAW_DISABLE_ROUTE_FIRST: "1" } : {}),
|
||||
...("tty" in testCase ? { FORCE_COLOR: "1" } : {}),
|
||||
});
|
||||
const message =
|
||||
"gatewayRequest" in testCase
|
||||
? gatewayError
|
||||
: (testCase.message ?? "Choose --enable or --disable, not both");
|
||||
|
||||
expect(result.status, result.stderr).toBe(1);
|
||||
expect(result.stdout, result.stderr).not.toMatch(/[\u001B\u0007]/u);
|
||||
if ("human" in testCase) {
|
||||
expect(result.stdout).toBe("");
|
||||
} else {
|
||||
expect(JSON.parse(result.stdout)).toEqual({
|
||||
ok: false,
|
||||
error: { type: "cli_error", message },
|
||||
});
|
||||
}
|
||||
expect(result.stderr).toContain(message);
|
||||
if ("gatewayRequest" in testCase) {
|
||||
expect(result.stderr).toContain(gatewayError);
|
||||
} else {
|
||||
expect(result.stderr).not.toContain(gatewayError);
|
||||
await expect(fs.stat(stateDir)).rejects.toMatchObject({ code: "ENOENT" });
|
||||
}
|
||||
if ("tty" in testCase) {
|
||||
expect(result.stderr).toContain("\u001B[?25h");
|
||||
}
|
||||
await expect(fs.stat(configPath)).rejects.toMatchObject({ code: "ENOENT" });
|
||||
},
|
||||
{ prefix: "openclaw-cron-edit-json-failure-e2e-" },
|
||||
);
|
||||
});
|
||||
|
||||
it.each([
|
||||
{
|
||||
name: "bare report with parent JSON",
|
||||
|
||||
Reference in New Issue
Block a user