* fix(cron): handle undefined sessionTarget in list output (#9649) When sessionTarget is undefined, pad() would crash with 'Cannot read properties of undefined (reading trim)'. Use '-' as fallback value. * test(cron): add regression test for undefined sessionTarget (#9649) Verifies that printCronList handles jobs with undefined sessionTarget without crashing. Test fails on main branch, passes with the fix. * fix: use correct CronSchedule format in tests (#9752) (thanks @lailoo) Tests were using { kind: 'at', atMs: number } but the CronSchedule type requires { kind: 'at', at: string } where 'at' is an ISO date string. --------- Co-authored-by: damaozi <1811866786@qq.com> Co-authored-by: Tyler Yust <TYTYYUST@YAHOO.COM>
This commit is contained in:
63
src/cli/cron-cli/shared.test.ts
Normal file
63
src/cli/cron-cli/shared.test.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { CronJob } from "../../cron/types.js";
|
||||
import type { RuntimeEnv } from "../../runtime.js";
|
||||
import { printCronList } from "./shared.js";
|
||||
|
||||
describe("printCronList", () => {
|
||||
it("handles job with undefined sessionTarget (#9649)", () => {
|
||||
const logs: string[] = [];
|
||||
const mockRuntime = {
|
||||
log: (msg: string) => logs.push(msg),
|
||||
error: () => {},
|
||||
exit: () => {},
|
||||
} as RuntimeEnv;
|
||||
|
||||
// Simulate a job without sessionTarget (as reported in #9649)
|
||||
const jobWithUndefinedTarget = {
|
||||
id: "test-job-id",
|
||||
agentId: "main",
|
||||
name: "Test Job",
|
||||
enabled: true,
|
||||
createdAtMs: Date.now(),
|
||||
updatedAtMs: Date.now(),
|
||||
schedule: { kind: "at", at: new Date(Date.now() + 3600000).toISOString() },
|
||||
// sessionTarget is intentionally omitted to simulate the bug
|
||||
wakeMode: "next-heartbeat",
|
||||
payload: { kind: "systemEvent", text: "test" },
|
||||
state: { nextRunAtMs: Date.now() + 3600000 },
|
||||
} as CronJob;
|
||||
|
||||
// This should not throw "Cannot read properties of undefined (reading 'trim')"
|
||||
expect(() => printCronList([jobWithUndefinedTarget], mockRuntime)).not.toThrow();
|
||||
|
||||
// Verify output contains the job
|
||||
expect(logs.length).toBeGreaterThan(1);
|
||||
expect(logs.some((line) => line.includes("test-job-id"))).toBe(true);
|
||||
});
|
||||
|
||||
it("handles job with defined sessionTarget", () => {
|
||||
const logs: string[] = [];
|
||||
const mockRuntime = {
|
||||
log: (msg: string) => logs.push(msg),
|
||||
error: () => {},
|
||||
exit: () => {},
|
||||
} as RuntimeEnv;
|
||||
|
||||
const jobWithTarget: CronJob = {
|
||||
id: "test-job-id-2",
|
||||
agentId: "main",
|
||||
name: "Test Job 2",
|
||||
enabled: true,
|
||||
createdAtMs: Date.now(),
|
||||
updatedAtMs: Date.now(),
|
||||
schedule: { kind: "at", at: new Date(Date.now() + 3600000).toISOString() },
|
||||
sessionTarget: "isolated",
|
||||
wakeMode: "next-heartbeat",
|
||||
payload: { kind: "systemEvent", text: "test" },
|
||||
state: { nextRunAtMs: Date.now() + 3600000 },
|
||||
};
|
||||
|
||||
expect(() => printCronList([jobWithTarget], mockRuntime)).not.toThrow();
|
||||
expect(logs.some((line) => line.includes("isolated"))).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -197,7 +197,7 @@ export function printCronList(jobs: CronJob[], runtime = defaultRuntime) {
|
||||
const lastLabel = pad(formatRelative(job.state.lastRunAtMs, now), CRON_LAST_PAD);
|
||||
const statusRaw = formatStatus(job);
|
||||
const statusLabel = pad(statusRaw, CRON_STATUS_PAD);
|
||||
const targetLabel = pad(job.sessionTarget, CRON_TARGET_PAD);
|
||||
const targetLabel = pad(job.sessionTarget ?? "-", CRON_TARGET_PAD);
|
||||
const agentLabel = pad(truncate(job.agentId ?? "default", CRON_AGENT_PAD), CRON_AGENT_PAD);
|
||||
|
||||
const coloredStatus = (() => {
|
||||
|
||||
Reference in New Issue
Block a user