import { describe, expect, it } from "vitest"; import { validateCronAddParams, validateCronGetParams, validateCronListParams, validateCronRemoveParams, validateCronRunParams, validateCronRunsParams, validateCronUpdateParams, } from "./index.js"; const minimalAddParams = { name: "daily-summary", schedule: { kind: "every", everyMs: 60_000 }, sessionTarget: "main", wakeMode: "next-heartbeat", payload: { kind: "systemEvent", text: "tick" }, } as const; describe("cron protocol validators", () => { it("accepts minimal add params", () => { expect(validateCronAddParams(minimalAddParams)).toBe(true); }); it("accepts current and custom session targets", () => { expect( validateCronAddParams({ ...minimalAddParams, sessionTarget: "current", payload: { kind: "agentTurn", message: "tick" }, }), ).toBe(true); expect( validateCronAddParams({ ...minimalAddParams, sessionTarget: "session:project-alpha", payload: { kind: "agentTurn", message: "tick" }, }), ).toBe(true); expect( validateCronUpdateParams({ id: "job-1", patch: { sessionTarget: "session:project-alpha" }, }), ).toBe(true); }); it("rejects add params when required scheduling fields are missing", () => { const { wakeMode: _wakeMode, ...withoutWakeMode } = minimalAddParams; expect(validateCronAddParams(withoutWakeMode)).toBe(false); }); it("accepts update params for id and jobId selectors", () => { expect(validateCronUpdateParams({ id: "job-1", patch: { enabled: false } })).toBe(true); expect(validateCronUpdateParams({ jobId: "job-2", patch: { enabled: true } })).toBe(true); }); it("accepts get params for id and jobId selectors", () => { expect(validateCronGetParams({ id: "job-1" })).toBe(true); expect(validateCronGetParams({ jobId: "job-2" })).toBe(true); expect(validateCronGetParams({})).toBe(false); expect(validateCronGetParams({ id: "" })).toBe(false); }); it("accepts delivery threadId on add and update params", () => { expect( validateCronAddParams({ ...minimalAddParams, delivery: { mode: "announce", channel: "telegram", to: "-100123", threadId: 42, }, }), ).toBe(true); expect( validateCronUpdateParams({ id: "job-1", patch: { delivery: { mode: "announce", channel: "telegram", to: "-100123", threadId: "topic-42", }, }, }), ).toBe(true); expect( validateCronUpdateParams({ id: "job-1", patch: { delivery: { threadId: 42, }, }, }), ).toBe(true); }); it("accepts remove params for id and jobId selectors", () => { expect(validateCronRemoveParams({ id: "job-1" })).toBe(true); expect(validateCronRemoveParams({ jobId: "job-2" })).toBe(true); }); it("accepts run params mode for id and jobId selectors", () => { expect(validateCronRunParams({ id: "job-1", mode: "force" })).toBe(true); expect(validateCronRunParams({ jobId: "job-2", mode: "due" })).toBe(true); }); it("accepts list paging/filter/sort params", () => { expect( validateCronListParams({ includeDisabled: true, limit: 50, offset: 0, query: "daily", enabled: "all", scheduleKind: "cron", lastRunStatus: "unknown", sortBy: "nextRunAtMs", sortDir: "asc", agentId: "ops", }), ).toBe(true); expect(validateCronListParams({ offset: -1 })).toBe(false); expect(validateCronListParams({ agentId: "" })).toBe(false); expect(validateCronListParams({ scheduleKind: "yearly" })).toBe(false); expect(validateCronListParams({ lastRunStatus: "pending" })).toBe(false); }); it("enforces runs limit minimum for id and jobId selectors", () => { expect(validateCronRunsParams({ id: "job-1", limit: 1 })).toBe(true); expect(validateCronRunsParams({ jobId: "job-2", limit: 1 })).toBe(true); expect(validateCronRunsParams({ id: "job-1", limit: 0 })).toBe(false); expect(validateCronRunsParams({ jobId: "job-2", limit: 0 })).toBe(false); }); it("rejects cron.runs path traversal ids", () => { expect(validateCronRunsParams({ id: "../job-1" })).toBe(false); expect(validateCronRunsParams({ id: "nested/job-1" })).toBe(false); expect(validateCronRunsParams({ jobId: "..\\job-2" })).toBe(false); expect(validateCronRunsParams({ jobId: "nested\\job-2" })).toBe(false); }); it("accepts runs paging/filter/sort params", () => { expect( validateCronRunsParams({ id: "job-1", runId: "manual:job-1:123:0", limit: 50, offset: 0, status: "error", query: "timeout", sortDir: "desc", }), ).toBe(true); expect(validateCronRunsParams({ id: "job-1", offset: -1 })).toBe(false); expect(validateCronRunsParams({ id: "job-1", runId: "" })).toBe(false); }); it("accepts all-scope runs with multi-select filters", () => { expect( validateCronRunsParams({ scope: "all", limit: 25, statuses: ["ok", "error"], deliveryStatuses: ["delivered", "not-requested"], query: "fail", sortDir: "desc", }), ).toBe(true); expect( validateCronRunsParams({ scope: "job", statuses: [], }), ).toBe(false); }); });