mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-06 05:51:15 +08:00
refactor: share cron validation test helpers
This commit is contained in:
@@ -121,6 +121,19 @@ async function invokeCronUpdate(params: Record<string, unknown>, currentJob?: Cr
|
||||
return await invokeCron("cron.update", params, { currentJob });
|
||||
}
|
||||
|
||||
async function invokeCronUpdateDelivery(
|
||||
delivery: Record<string, unknown>,
|
||||
currentJob = createCronJob(),
|
||||
) {
|
||||
return await invokeCronUpdate(
|
||||
{
|
||||
id: "cron-1",
|
||||
patch: { delivery },
|
||||
},
|
||||
currentJob,
|
||||
);
|
||||
}
|
||||
|
||||
async function invokeCronRemove(
|
||||
params: Record<string, unknown>,
|
||||
options?: { removeResult?: { ok: boolean; removed: boolean } },
|
||||
@@ -231,6 +244,19 @@ function slackSynologyConfig(): OpenClawConfig {
|
||||
} as OpenClawConfig;
|
||||
}
|
||||
|
||||
function slackConfig(params: { includeMainSession?: boolean } = {}): OpenClawConfig {
|
||||
return {
|
||||
...(params.includeMainSession ? { session: { mainKey: "main" } } : {}),
|
||||
channels: {
|
||||
slack: {
|
||||
botToken: "xoxb-slack-token",
|
||||
appToken: "xapp-slack-token",
|
||||
},
|
||||
},
|
||||
plugins: pluginEntries("slack"),
|
||||
} as OpenClawConfig;
|
||||
}
|
||||
|
||||
function agentTurnCronParams(overrides: Record<string, unknown> = {}) {
|
||||
return {
|
||||
name: "cron job",
|
||||
@@ -280,6 +306,14 @@ function expectDeliveryFields(payload: Record<string, unknown>, expected: Record
|
||||
}
|
||||
}
|
||||
|
||||
function expectCronUpdateDeliveryPatch(
|
||||
context: ReturnType<typeof createCronContext>,
|
||||
expected: unknown,
|
||||
) {
|
||||
expect(context.cron.update).toHaveBeenCalled();
|
||||
expect(requireCronUpdatePatch(context).delivery).toEqual(expected);
|
||||
}
|
||||
|
||||
function expectResponseError(
|
||||
respond: ReturnType<typeof vi.fn>,
|
||||
expected: { code?: string; messageIncludes?: string },
|
||||
@@ -299,6 +333,10 @@ function expectResponseError(
|
||||
}
|
||||
}
|
||||
|
||||
function expectInvalidCronPatternError(respond: ReturnType<typeof vi.fn>): void {
|
||||
expectResponseError(respond, { code: "INVALID_REQUEST", messageIncludes: "CronPattern" });
|
||||
}
|
||||
|
||||
describe("cron method validation", () => {
|
||||
beforeEach(() => {
|
||||
getRuntimeConfig.mockReset().mockReturnValue({} as OpenClawConfig);
|
||||
@@ -506,13 +544,8 @@ describe("cron method validation", () => {
|
||||
it("validates announce delivery patches that omit mode", async () => {
|
||||
setRuntimeConfig(telegramSlackConfig());
|
||||
|
||||
const { context, respond } = await invokeCronUpdate(
|
||||
{
|
||||
id: "cron-1",
|
||||
patch: {
|
||||
delivery: { channel: "slack", to: "telegram:123" },
|
||||
},
|
||||
},
|
||||
const { context, respond } = await invokeCronUpdateDelivery(
|
||||
{ channel: "slack", to: "telegram:123" },
|
||||
createCronJob({
|
||||
delivery: { mode: "announce", channel: "telegram", to: "123" },
|
||||
}),
|
||||
@@ -591,8 +624,7 @@ describe("cron method validation", () => {
|
||||
}),
|
||||
);
|
||||
|
||||
expect(context.cron.update).toHaveBeenCalled();
|
||||
expect(requireCronUpdatePatch(context).delivery).toEqual({
|
||||
expectCronUpdateDeliveryPatch(context, {
|
||||
channel: null,
|
||||
to: null,
|
||||
threadId: null,
|
||||
@@ -605,18 +637,13 @@ describe("cron method validation", () => {
|
||||
it("accepts nullable failure destination field clears on update", async () => {
|
||||
setRuntimeConfig(telegramSlackConfig());
|
||||
|
||||
const { context, respond } = await invokeCronUpdate(
|
||||
const { context, respond } = await invokeCronUpdateDelivery(
|
||||
{
|
||||
id: "cron-1",
|
||||
patch: {
|
||||
delivery: {
|
||||
failureDestination: {
|
||||
channel: null,
|
||||
to: null,
|
||||
accountId: null,
|
||||
mode: null,
|
||||
},
|
||||
},
|
||||
failureDestination: {
|
||||
channel: null,
|
||||
to: null,
|
||||
accountId: null,
|
||||
mode: null,
|
||||
},
|
||||
},
|
||||
createCronJob({
|
||||
@@ -624,8 +651,7 @@ describe("cron method validation", () => {
|
||||
}),
|
||||
);
|
||||
|
||||
expect(context.cron.update).toHaveBeenCalled();
|
||||
expect(requireCronUpdatePatch(context).delivery).toEqual({
|
||||
expectCronUpdateDeliveryPatch(context, {
|
||||
failureDestination: {
|
||||
channel: null,
|
||||
to: null,
|
||||
@@ -653,15 +679,7 @@ describe("cron method validation", () => {
|
||||
it("rejects ambiguous announce delivery on update when multiple channels are configured", async () => {
|
||||
setRuntimeConfig(telegramSlackConfig({ includeMainSession: true }));
|
||||
|
||||
const { context, respond } = await invokeCronUpdate(
|
||||
{
|
||||
id: "cron-1",
|
||||
patch: {
|
||||
delivery: { mode: "announce" },
|
||||
},
|
||||
},
|
||||
createCronJob(),
|
||||
);
|
||||
const { context, respond } = await invokeCronUpdateDelivery({ mode: "announce" });
|
||||
|
||||
expect(context.cron.update).not.toHaveBeenCalled();
|
||||
expectResponseError(respond, { messageIncludes: "delivery.channel is required" });
|
||||
@@ -713,22 +731,7 @@ describe("cron method validation", () => {
|
||||
});
|
||||
|
||||
it("does not revalidate stale delivery config for unrelated updates", async () => {
|
||||
setRuntimeConfig({
|
||||
session: {
|
||||
mainKey: "main",
|
||||
},
|
||||
channels: {
|
||||
slack: {
|
||||
botToken: "xoxb-slack-token",
|
||||
appToken: "xapp-slack-token",
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
entries: {
|
||||
slack: { enabled: true },
|
||||
},
|
||||
},
|
||||
});
|
||||
setRuntimeConfig(slackConfig({ includeMainSession: true }));
|
||||
|
||||
const { context, respond } = await invokeCronUpdate(
|
||||
{
|
||||
@@ -747,18 +750,7 @@ describe("cron method validation", () => {
|
||||
});
|
||||
|
||||
it("rejects target ids mistakenly supplied as delivery.channel providers", async () => {
|
||||
setRuntimeConfig({
|
||||
session: {
|
||||
mainKey: "main",
|
||||
},
|
||||
channels: {
|
||||
slack: {
|
||||
botToken: "xoxb-slack-token",
|
||||
appToken: "xapp-slack-token",
|
||||
},
|
||||
},
|
||||
plugins: pluginEntries("slack"),
|
||||
} as OpenClawConfig);
|
||||
setRuntimeConfig(slackConfig({ includeMainSession: true }));
|
||||
|
||||
const { context, respond } = await invokeCronAdd(
|
||||
agentTurnCronParams({
|
||||
@@ -791,7 +783,7 @@ describe("cron method validation", () => {
|
||||
{ context },
|
||||
);
|
||||
|
||||
expectResponseError(respond, { code: "INVALID_REQUEST", messageIncludes: "CronPattern" });
|
||||
expectInvalidCronPatternError(respond);
|
||||
});
|
||||
|
||||
it("returns INVALID_REQUEST when cron.add rejects an incompatible main agent", async () => {
|
||||
@@ -801,10 +793,9 @@ describe("cron method validation", () => {
|
||||
'cron: sessionTarget "main" is only valid for the default agent. Use sessionTarget "isolated" with payload.kind "agentTurn" for non-default agents (agentId: worker)',
|
||||
),
|
||||
);
|
||||
const respond = vi.fn();
|
||||
await cronHandlers["cron.add"]({
|
||||
req: {} as never,
|
||||
params: {
|
||||
const { respond } = await invokeCron(
|
||||
"cron.add",
|
||||
{
|
||||
name: "bad-main-agent",
|
||||
enabled: true,
|
||||
schedule: { kind: "every", everyMs: 60_000 },
|
||||
@@ -812,12 +803,9 @@ describe("cron method validation", () => {
|
||||
wakeMode: "next-heartbeat",
|
||||
payload: { kind: "systemEvent", text: "ping" },
|
||||
agentId: "worker",
|
||||
} as never,
|
||||
respond: respond as never,
|
||||
context: context as never,
|
||||
client: null,
|
||||
isWebchatConnect: () => false,
|
||||
});
|
||||
},
|
||||
{ context },
|
||||
);
|
||||
|
||||
expectResponseError(respond, {
|
||||
code: "INVALID_REQUEST",
|
||||
@@ -842,7 +830,7 @@ describe("cron method validation", () => {
|
||||
{ context },
|
||||
);
|
||||
|
||||
expectResponseError(respond, { code: "INVALID_REQUEST", messageIncludes: "CronPattern" });
|
||||
expectInvalidCronPatternError(respond);
|
||||
});
|
||||
|
||||
it("returns INVALID_REQUEST when cron.update cannot find the job", async () => {
|
||||
@@ -882,15 +870,7 @@ describe("cron method validation", () => {
|
||||
it("returns INVALID_REQUEST when cron.run cannot find the job", async () => {
|
||||
const context = createCronContext();
|
||||
context.cron.enqueueRun.mockRejectedValueOnce(new Error("unknown cron job id: missing"));
|
||||
const respond = vi.fn();
|
||||
await cronHandlers["cron.run"]({
|
||||
req: {} as never,
|
||||
params: { id: "missing" } as never,
|
||||
respond: respond as never,
|
||||
context: context as never,
|
||||
client: null,
|
||||
isWebchatConnect: () => false,
|
||||
});
|
||||
const { respond } = await invokeCron("cron.run", { id: "missing" }, { context });
|
||||
|
||||
expectResponseError(respond, {
|
||||
code: "INVALID_REQUEST",
|
||||
@@ -945,11 +925,18 @@ describe("cron method validation", () => {
|
||||
expect(respond).toHaveBeenCalledWith(true, { ok: true }, undefined);
|
||||
});
|
||||
|
||||
it("rejects empty-string sessionKey at schema", async () => {
|
||||
it.each([
|
||||
{ name: "empty-string sessionKey at schema", sessionKey: "" },
|
||||
{ name: "non-string sessionKey at schema", sessionKey: 42 },
|
||||
{
|
||||
name: "subagent sessionKey targets before enqueueing",
|
||||
sessionKey: "agent:main:subagent:worker",
|
||||
},
|
||||
])("rejects $name", async ({ sessionKey }) => {
|
||||
const { context, respond } = await invokeWake({
|
||||
mode: "now",
|
||||
text: "ping",
|
||||
sessionKey: "",
|
||||
sessionKey,
|
||||
});
|
||||
expect(context.cron.wake).not.toHaveBeenCalled();
|
||||
expectResponseError(respond, { code: "INVALID_REQUEST", messageIncludes: "sessionKey" });
|
||||
@@ -967,25 +954,5 @@ describe("cron method validation", () => {
|
||||
});
|
||||
expect(respond).toHaveBeenCalledWith(true, { ok: true }, undefined);
|
||||
});
|
||||
|
||||
it("rejects non-string sessionKey at schema", async () => {
|
||||
const { context, respond } = await invokeWake({
|
||||
mode: "now",
|
||||
text: "ping",
|
||||
sessionKey: 42,
|
||||
});
|
||||
expect(context.cron.wake).not.toHaveBeenCalled();
|
||||
expectResponseError(respond, { code: "INVALID_REQUEST", messageIncludes: "sessionKey" });
|
||||
});
|
||||
|
||||
it("rejects subagent sessionKey targets before enqueueing", async () => {
|
||||
const { context, respond } = await invokeWake({
|
||||
mode: "now",
|
||||
text: "ping",
|
||||
sessionKey: "agent:main:subagent:worker",
|
||||
});
|
||||
expect(context.cron.wake).not.toHaveBeenCalled();
|
||||
expectResponseError(respond, { code: "INVALID_REQUEST", messageIncludes: "sessionKey" });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user