mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 05:50:43 +00:00
test: move cron validation off gateway server
This commit is contained in:
207
src/gateway/server-methods/cron.validation.test.ts
Normal file
207
src/gateway/server-methods/cron.validation.test.ts
Normal file
@@ -0,0 +1,207 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { OpenClawConfig } from "../../config/types.openclaw.js";
|
||||
import type { CronJob } from "../../cron/types.js";
|
||||
|
||||
const loadConfig = vi.hoisted(() => vi.fn<() => OpenClawConfig>(() => ({}) as OpenClawConfig));
|
||||
|
||||
vi.mock("../../config/config.js", async () => {
|
||||
const actual =
|
||||
await vi.importActual<typeof import("../../config/config.js")>("../../config/config.js");
|
||||
return {
|
||||
...actual,
|
||||
loadConfig,
|
||||
};
|
||||
});
|
||||
|
||||
import { cronHandlers } from "./cron.js";
|
||||
|
||||
function createCronContext(currentJob?: CronJob) {
|
||||
return {
|
||||
cron: {
|
||||
add: vi.fn(async () => ({ id: "cron-1" })),
|
||||
update: vi.fn(async () => ({ id: "cron-1" })),
|
||||
getDefaultAgentId: vi.fn(() => "main"),
|
||||
getJob: vi.fn(() => currentJob),
|
||||
},
|
||||
logGateway: {
|
||||
info: vi.fn(),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async function invokeCronAdd(params: Record<string, unknown>) {
|
||||
const context = createCronContext();
|
||||
const respond = vi.fn();
|
||||
await cronHandlers["cron.add"]({
|
||||
req: {} as never,
|
||||
params: params as never,
|
||||
respond: respond as never,
|
||||
context: context as never,
|
||||
client: null,
|
||||
isWebchatConnect: () => false,
|
||||
});
|
||||
return { context, respond };
|
||||
}
|
||||
|
||||
async function invokeCronUpdate(params: Record<string, unknown>, currentJob: CronJob) {
|
||||
const context = createCronContext(currentJob);
|
||||
const respond = vi.fn();
|
||||
await cronHandlers["cron.update"]({
|
||||
req: {} as never,
|
||||
params: params as never,
|
||||
respond: respond as never,
|
||||
context: context as never,
|
||||
client: null,
|
||||
isWebchatConnect: () => false,
|
||||
});
|
||||
return { context, respond };
|
||||
}
|
||||
|
||||
function createCronJob(overrides: Partial<CronJob> = {}): CronJob {
|
||||
return {
|
||||
id: "cron-1",
|
||||
name: "cron job",
|
||||
enabled: true,
|
||||
createdAtMs: 1,
|
||||
updatedAtMs: 1,
|
||||
schedule: { kind: "every", everyMs: 60_000 },
|
||||
sessionTarget: "isolated",
|
||||
wakeMode: "next-heartbeat",
|
||||
payload: { kind: "agentTurn", message: "hello" },
|
||||
delivery: { mode: "none" },
|
||||
state: {},
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
describe("cron method validation", () => {
|
||||
beforeEach(() => {
|
||||
loadConfig.mockReset().mockReturnValue({} as OpenClawConfig);
|
||||
});
|
||||
|
||||
it("rejects ambiguous announce delivery on add when multiple channels are configured", async () => {
|
||||
loadConfig.mockReturnValue({
|
||||
session: {
|
||||
mainKey: "main",
|
||||
},
|
||||
channels: {
|
||||
telegram: {
|
||||
botToken: "telegram-token",
|
||||
},
|
||||
slack: {
|
||||
botToken: "xoxb-slack-token",
|
||||
appToken: "xapp-slack-token",
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
entries: {
|
||||
telegram: { enabled: true },
|
||||
slack: { enabled: true },
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig);
|
||||
|
||||
const { context, respond } = await invokeCronAdd({
|
||||
name: "ambiguous announce add",
|
||||
enabled: true,
|
||||
schedule: { kind: "every", everyMs: 60_000 },
|
||||
sessionTarget: "isolated",
|
||||
wakeMode: "next-heartbeat",
|
||||
payload: { kind: "agentTurn", message: "hello" },
|
||||
delivery: { mode: "announce" },
|
||||
});
|
||||
|
||||
expect(context.cron.add).not.toHaveBeenCalled();
|
||||
expect(respond).toHaveBeenCalledWith(
|
||||
false,
|
||||
undefined,
|
||||
expect.objectContaining({
|
||||
message: expect.stringContaining("delivery.channel is required"),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("rejects ambiguous announce delivery on update when multiple channels are configured", async () => {
|
||||
loadConfig.mockReturnValue({
|
||||
session: {
|
||||
mainKey: "main",
|
||||
},
|
||||
channels: {
|
||||
telegram: {
|
||||
botToken: "telegram-token",
|
||||
},
|
||||
slack: {
|
||||
botToken: "xoxb-slack-token",
|
||||
appToken: "xapp-slack-token",
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
entries: {
|
||||
telegram: { enabled: true },
|
||||
slack: { enabled: true },
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig);
|
||||
|
||||
const { context, respond } = await invokeCronUpdate(
|
||||
{
|
||||
id: "cron-1",
|
||||
patch: {
|
||||
delivery: { mode: "announce" },
|
||||
},
|
||||
},
|
||||
createCronJob(),
|
||||
);
|
||||
|
||||
expect(context.cron.update).not.toHaveBeenCalled();
|
||||
expect(respond).toHaveBeenCalledWith(
|
||||
false,
|
||||
undefined,
|
||||
expect.objectContaining({
|
||||
message: expect.stringContaining("delivery.channel is required"),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("rejects target ids mistakenly supplied as delivery.channel providers", async () => {
|
||||
loadConfig.mockReturnValue({
|
||||
session: {
|
||||
mainKey: "main",
|
||||
},
|
||||
channels: {
|
||||
slack: {
|
||||
botToken: "xoxb-slack-token",
|
||||
appToken: "xapp-slack-token",
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
entries: {
|
||||
slack: { enabled: true },
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig);
|
||||
|
||||
const { context, respond } = await invokeCronAdd({
|
||||
name: "invalid delivery provider",
|
||||
enabled: true,
|
||||
schedule: { kind: "every", everyMs: 60_000 },
|
||||
sessionTarget: "isolated",
|
||||
wakeMode: "next-heartbeat",
|
||||
payload: { kind: "agentTurn", message: "hello" },
|
||||
delivery: {
|
||||
mode: "announce",
|
||||
channel: "C0AT2Q238MQ",
|
||||
to: "C0AT2Q238MQ",
|
||||
},
|
||||
});
|
||||
|
||||
expect(context.cron.add).not.toHaveBeenCalled();
|
||||
expect(respond).toHaveBeenCalledWith(
|
||||
false,
|
||||
undefined,
|
||||
expect.objectContaining({
|
||||
message: expect.stringContaining("delivery.channel must be one of: slack"),
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -721,54 +721,6 @@ describe("gateway server cron", () => {
|
||||
}
|
||||
});
|
||||
|
||||
test("rejects ambiguous announce delivery on add when multiple channels are configured", async () => {
|
||||
const { prevSkipCron } = await setupCronTestRun({
|
||||
tempPrefix: "openclaw-gw-cron-ambiguous-delivery-add-",
|
||||
cronEnabled: false,
|
||||
});
|
||||
|
||||
await writeCronConfig({
|
||||
session: {
|
||||
mainKey: "main",
|
||||
},
|
||||
channels: {
|
||||
telegram: {
|
||||
botToken: "telegram-token",
|
||||
},
|
||||
slack: {
|
||||
botToken: "xoxb-slack-token",
|
||||
appToken: "xapp-slack-token",
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
entries: {
|
||||
telegram: { enabled: true },
|
||||
slack: { enabled: true },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const { server, ws } = await startServerWithClient();
|
||||
await connectOk(ws);
|
||||
|
||||
try {
|
||||
const addRes = await rpcReq(ws, "cron.add", {
|
||||
name: "ambiguous announce add",
|
||||
enabled: true,
|
||||
schedule: { kind: "every", everyMs: 60_000 },
|
||||
sessionTarget: "isolated",
|
||||
wakeMode: "next-heartbeat",
|
||||
payload: { kind: "agentTurn", message: "hello" },
|
||||
delivery: { mode: "announce" },
|
||||
});
|
||||
|
||||
expect(addRes.ok).toBe(false);
|
||||
expect(addRes.error?.message).toContain("delivery.channel is required");
|
||||
} finally {
|
||||
await cleanupCronTestRun({ ws, server, prevSkipCron });
|
||||
}
|
||||
});
|
||||
|
||||
test("ignores ambient disabled channel env when validating announce delivery", async () => {
|
||||
vi.stubEnv("SLACK_BOT_TOKEN", "xoxb-ambient");
|
||||
vi.stubEnv("TELEGRAM_BOT_TOKEN", "ambient-telegram");
|
||||
@@ -806,114 +758,6 @@ describe("gateway server cron", () => {
|
||||
}
|
||||
});
|
||||
|
||||
test("rejects ambiguous announce delivery on update when multiple channels are configured", async () => {
|
||||
const { prevSkipCron } = await setupCronTestRun({
|
||||
tempPrefix: "openclaw-gw-cron-ambiguous-delivery-update-",
|
||||
cronEnabled: false,
|
||||
});
|
||||
|
||||
await writeCronConfig({
|
||||
session: {
|
||||
mainKey: "main",
|
||||
},
|
||||
channels: {
|
||||
telegram: {
|
||||
botToken: "telegram-token",
|
||||
},
|
||||
slack: {
|
||||
botToken: "xoxb-slack-token",
|
||||
appToken: "xapp-slack-token",
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
entries: {
|
||||
telegram: { enabled: true },
|
||||
slack: { enabled: true },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const { server, ws } = await startServerWithClient();
|
||||
await connectOk(ws);
|
||||
|
||||
try {
|
||||
const addRes = await rpcReq(ws, "cron.add", {
|
||||
name: "ambiguous announce update",
|
||||
enabled: true,
|
||||
schedule: { kind: "every", everyMs: 60_000 },
|
||||
sessionTarget: "isolated",
|
||||
wakeMode: "next-heartbeat",
|
||||
payload: { kind: "agentTurn", message: "hello" },
|
||||
delivery: { mode: "none" },
|
||||
});
|
||||
expect(addRes.ok).toBe(true);
|
||||
const jobIdValue = (addRes.payload as { id?: unknown } | null)?.id;
|
||||
const jobId = typeof jobIdValue === "string" ? jobIdValue : "";
|
||||
expect(jobId.length > 0).toBe(true);
|
||||
|
||||
const updateRes = await rpcReq(ws, "cron.update", {
|
||||
id: jobId,
|
||||
patch: {
|
||||
delivery: { mode: "announce" },
|
||||
},
|
||||
});
|
||||
|
||||
expect(updateRes.ok).toBe(false);
|
||||
expect(updateRes.error?.message).toContain("delivery.channel is required");
|
||||
} finally {
|
||||
await cleanupCronTestRun({ ws, server, prevSkipCron });
|
||||
}
|
||||
});
|
||||
|
||||
test("rejects target ids mistakenly supplied as delivery.channel providers", async () => {
|
||||
const { prevSkipCron } = await setupCronTestRun({
|
||||
tempPrefix: "openclaw-gw-cron-invalid-delivery-provider-",
|
||||
cronEnabled: false,
|
||||
});
|
||||
|
||||
await writeCronConfig({
|
||||
session: {
|
||||
mainKey: "main",
|
||||
},
|
||||
channels: {
|
||||
slack: {
|
||||
botToken: "xoxb-slack-token",
|
||||
appToken: "xapp-slack-token",
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
entries: {
|
||||
slack: { enabled: true },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const { server, ws } = await startServerWithClient();
|
||||
await connectOk(ws);
|
||||
|
||||
try {
|
||||
const addRes = await rpcReq(ws, "cron.add", {
|
||||
name: "invalid delivery provider",
|
||||
enabled: true,
|
||||
schedule: { kind: "every", everyMs: 60_000 },
|
||||
sessionTarget: "isolated",
|
||||
wakeMode: "next-heartbeat",
|
||||
payload: { kind: "agentTurn", message: "hello" },
|
||||
delivery: {
|
||||
mode: "announce",
|
||||
channel: "C0AT2Q238MQ",
|
||||
to: "C0AT2Q238MQ",
|
||||
},
|
||||
});
|
||||
|
||||
expect(addRes.ok).toBe(false);
|
||||
expect(addRes.error?.message).toContain("delivery.channel");
|
||||
expect(addRes.error?.message).toContain("slack");
|
||||
} finally {
|
||||
await cleanupCronTestRun({ ws, server, prevSkipCron });
|
||||
}
|
||||
});
|
||||
|
||||
test("writes cron run history and auto-runs due jobs", async () => {
|
||||
const { prevSkipCron } = await setupCronTestRun({
|
||||
tempPrefix: "openclaw-gw-cron-log-",
|
||||
|
||||
Reference in New Issue
Block a user