import { describe, expect, it } from "vitest"; import { validateCronAddParams, 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("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 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", sortBy: "nextRunAtMs", sortDir: "asc", }), ).toBe(true); expect(validateCronListParams({ offset: -1 })).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", limit: 50, offset: 0, status: "error", query: "timeout", sortDir: "desc", }), ).toBe(true); expect(validateCronRunsParams({ id: "job-1", offset: -1 })).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); }); });