mirror of https://github.com/openclaw/openclaw.git
test: share gateway hook and cron helpers
This commit is contained in:
parent
b64466953a
commit
8225b9edbb
|
|
@ -179,6 +179,13 @@ async function addWebhookCronJob(params: {
|
|||
return expectCronJobIdFromResponse(response);
|
||||
}
|
||||
|
||||
async function writeCronConfig(config: unknown) {
|
||||
const configPath = process.env.OPENCLAW_CONFIG_PATH;
|
||||
expect(typeof configPath).toBe("string");
|
||||
await fs.mkdir(path.dirname(configPath as string), { recursive: true });
|
||||
await fs.writeFile(configPath as string, JSON.stringify(config, null, 2), "utf-8");
|
||||
}
|
||||
|
||||
async function runCronJobForce(ws: WebSocket, id: string) {
|
||||
const response = await rpcReq(ws, "cron.run", { id, mode: "force" }, 20_000);
|
||||
expect(response.ok).toBe(true);
|
||||
|
|
@ -186,6 +193,15 @@ async function runCronJobForce(ws: WebSocket, id: string) {
|
|||
return response;
|
||||
}
|
||||
|
||||
async function runCronJobAndWaitForFinished(ws: WebSocket, jobId: string) {
|
||||
const finished = waitForCronEvent(
|
||||
ws,
|
||||
(payload) => payload?.jobId === jobId && payload?.action === "finished",
|
||||
);
|
||||
await runCronJobForce(ws, jobId);
|
||||
await finished;
|
||||
}
|
||||
|
||||
function getWebhookCall(index: number) {
|
||||
const [args] = fetchWithSsrFGuardMock.mock.calls[index] as unknown as [
|
||||
{
|
||||
|
|
@ -720,23 +736,12 @@ describe("gateway server cron", () => {
|
|||
jobs: [legacyNotifyJob],
|
||||
});
|
||||
|
||||
const configPath = process.env.OPENCLAW_CONFIG_PATH;
|
||||
expect(typeof configPath).toBe("string");
|
||||
await fs.mkdir(path.dirname(configPath as string), { recursive: true });
|
||||
await fs.writeFile(
|
||||
configPath as string,
|
||||
JSON.stringify(
|
||||
{
|
||||
cron: {
|
||||
webhook: "https://legacy.example.invalid/cron-finished",
|
||||
webhookToken: "cron-webhook-token",
|
||||
},
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
"utf-8",
|
||||
);
|
||||
await writeCronConfig({
|
||||
cron: {
|
||||
webhook: "https://legacy.example.invalid/cron-finished",
|
||||
webhookToken: "cron-webhook-token",
|
||||
},
|
||||
});
|
||||
|
||||
fetchWithSsrFGuardMock.mockClear();
|
||||
|
||||
|
|
@ -760,12 +765,7 @@ describe("gateway server cron", () => {
|
|||
name: "webhook enabled",
|
||||
delivery: { mode: "webhook", to: "https://example.invalid/cron-finished" },
|
||||
});
|
||||
const notifyFinished = waitForCronEvent(
|
||||
ws,
|
||||
(payload) => payload?.jobId === notifyJobId && payload?.action === "finished",
|
||||
);
|
||||
await runCronJobForce(ws, notifyJobId);
|
||||
await notifyFinished;
|
||||
await runCronJobAndWaitForFinished(ws, notifyJobId);
|
||||
const notifyCall = getWebhookCall(0);
|
||||
expect(notifyCall.url).toBe("https://example.invalid/cron-finished");
|
||||
expect(notifyCall.init.method).toBe("POST");
|
||||
|
|
@ -899,24 +899,13 @@ describe("gateway server cron", () => {
|
|||
cronEnabled: false,
|
||||
});
|
||||
|
||||
const configPath = process.env.OPENCLAW_CONFIG_PATH;
|
||||
expect(typeof configPath).toBe("string");
|
||||
await fs.mkdir(path.dirname(configPath as string), { recursive: true });
|
||||
await fs.writeFile(
|
||||
configPath as string,
|
||||
JSON.stringify(
|
||||
{
|
||||
cron: {
|
||||
webhookToken: {
|
||||
opaque: true,
|
||||
},
|
||||
},
|
||||
await writeCronConfig({
|
||||
cron: {
|
||||
webhookToken: {
|
||||
opaque: true,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
"utf-8",
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
fetchWithSsrFGuardMock.mockClear();
|
||||
|
||||
|
|
@ -929,12 +918,7 @@ describe("gateway server cron", () => {
|
|||
name: "webhook secretinput object",
|
||||
delivery: { mode: "webhook", to: "https://example.invalid/cron-finished" },
|
||||
});
|
||||
const notifyFinished = waitForCronEvent(
|
||||
ws,
|
||||
(payload) => payload?.jobId === notifyJobId && payload?.action === "finished",
|
||||
);
|
||||
await runCronJobForce(ws, notifyJobId);
|
||||
await notifyFinished;
|
||||
await runCronJobAndWaitForFinished(ws, notifyJobId);
|
||||
const [notifyArgs] = fetchWithSsrFGuardMock.mock.calls[0] as unknown as [
|
||||
{
|
||||
url?: string;
|
||||
|
|
|
|||
|
|
@ -62,6 +62,42 @@ function mockIsolatedRunOkOnce(): void {
|
|||
});
|
||||
}
|
||||
|
||||
function mockIsolatedRunOk(): void {
|
||||
cronIsolatedRun.mockClear();
|
||||
cronIsolatedRun.mockResolvedValue({
|
||||
status: "ok",
|
||||
summary: "done",
|
||||
});
|
||||
}
|
||||
|
||||
async function postAgentHookWithIdempotency(
|
||||
port: number,
|
||||
idempotencyKey: string,
|
||||
headers?: Record<string, string>,
|
||||
) {
|
||||
const response = await postHook(
|
||||
port,
|
||||
"/hooks/agent",
|
||||
{ message: "Do it", name: "Email" },
|
||||
{ headers: { "Idempotency-Key": idempotencyKey, ...headers } },
|
||||
);
|
||||
expect(response.status).toBe(200);
|
||||
return response;
|
||||
}
|
||||
|
||||
async function expectFirstHookDelivery(
|
||||
port: number,
|
||||
idempotencyKey: string,
|
||||
headers?: Record<string, string>,
|
||||
) {
|
||||
const first = await postAgentHookWithIdempotency(port, idempotencyKey, headers);
|
||||
const firstBody = (await first.json()) as { runId?: string };
|
||||
expect(firstBody.runId).toBeTruthy();
|
||||
await waitForSystemEvent();
|
||||
drainSystemEvents(resolveMainKey());
|
||||
return firstBody;
|
||||
}
|
||||
|
||||
describe("gateway server hooks", () => {
|
||||
test("handles auth, wake, and agent flows", async () => {
|
||||
testState.hooksConfig = { enabled: true, token: HOOK_TOKEN };
|
||||
|
|
@ -288,29 +324,11 @@ describe("gateway server hooks", () => {
|
|||
test("dedupes repeated /hooks/agent deliveries by idempotency key", async () => {
|
||||
testState.hooksConfig = { enabled: true, token: HOOK_TOKEN };
|
||||
await withGatewayServer(async ({ port }) => {
|
||||
cronIsolatedRun.mockClear();
|
||||
cronIsolatedRun.mockResolvedValue({ status: "ok", summary: "done" });
|
||||
|
||||
const first = await postHook(
|
||||
port,
|
||||
"/hooks/agent",
|
||||
{ message: "Do it", name: "Email" },
|
||||
{ headers: { "Idempotency-Key": "hook-idem-1" } },
|
||||
);
|
||||
expect(first.status).toBe(200);
|
||||
const firstBody = (await first.json()) as { runId?: string };
|
||||
expect(firstBody.runId).toBeTruthy();
|
||||
await waitForSystemEvent();
|
||||
mockIsolatedRunOk();
|
||||
const firstBody = await expectFirstHookDelivery(port, "hook-idem-1");
|
||||
expect(cronIsolatedRun).toHaveBeenCalledTimes(1);
|
||||
drainSystemEvents(resolveMainKey());
|
||||
|
||||
const second = await postHook(
|
||||
port,
|
||||
"/hooks/agent",
|
||||
{ message: "Do it", name: "Email" },
|
||||
{ headers: { "Idempotency-Key": "hook-idem-1" } },
|
||||
);
|
||||
expect(second.status).toBe(200);
|
||||
const second = await postAgentHookWithIdempotency(port, "hook-idem-1");
|
||||
const secondBody = (await second.json()) as { runId?: string };
|
||||
expect(secondBody.runId).toBe(firstBody.runId);
|
||||
expect(cronIsolatedRun).toHaveBeenCalledTimes(1);
|
||||
|
|
@ -329,37 +347,13 @@ describe("gateway server hooks", () => {
|
|||
);
|
||||
|
||||
await withGatewayServer(async ({ port }) => {
|
||||
cronIsolatedRun.mockClear();
|
||||
cronIsolatedRun.mockResolvedValue({ status: "ok", summary: "done" });
|
||||
|
||||
const first = await postHook(
|
||||
port,
|
||||
"/hooks/agent",
|
||||
{ message: "Do it", name: "Email" },
|
||||
{
|
||||
headers: {
|
||||
"Idempotency-Key": "hook-idem-forwarded",
|
||||
"X-Forwarded-For": "198.51.100.10",
|
||||
},
|
||||
},
|
||||
);
|
||||
expect(first.status).toBe(200);
|
||||
const firstBody = (await first.json()) as { runId?: string };
|
||||
await waitForSystemEvent();
|
||||
drainSystemEvents(resolveMainKey());
|
||||
|
||||
const second = await postHook(
|
||||
port,
|
||||
"/hooks/agent",
|
||||
{ message: "Do it", name: "Email" },
|
||||
{
|
||||
headers: {
|
||||
"Idempotency-Key": "hook-idem-forwarded",
|
||||
"X-Forwarded-For": "203.0.113.25",
|
||||
},
|
||||
},
|
||||
);
|
||||
expect(second.status).toBe(200);
|
||||
mockIsolatedRunOk();
|
||||
const firstBody = await expectFirstHookDelivery(port, "hook-idem-forwarded", {
|
||||
"X-Forwarded-For": "198.51.100.10",
|
||||
});
|
||||
const second = await postAgentHookWithIdempotency(port, "hook-idem-forwarded", {
|
||||
"X-Forwarded-For": "203.0.113.25",
|
||||
});
|
||||
const secondBody = (await second.json()) as { runId?: string };
|
||||
expect(secondBody.runId).toBe(firstBody.runId);
|
||||
expect(cronIsolatedRun).toHaveBeenCalledTimes(1);
|
||||
|
|
@ -371,26 +365,9 @@ describe("gateway server hooks", () => {
|
|||
const oversizedKey = "x".repeat(257);
|
||||
|
||||
await withGatewayServer(async ({ port }) => {
|
||||
cronIsolatedRun.mockClear();
|
||||
cronIsolatedRun.mockResolvedValue({ status: "ok", summary: "done" });
|
||||
|
||||
const first = await postHook(
|
||||
port,
|
||||
"/hooks/agent",
|
||||
{ message: "Do it", name: "Email" },
|
||||
{ headers: { "Idempotency-Key": oversizedKey } },
|
||||
);
|
||||
expect(first.status).toBe(200);
|
||||
await waitForSystemEvent();
|
||||
drainSystemEvents(resolveMainKey());
|
||||
|
||||
const second = await postHook(
|
||||
port,
|
||||
"/hooks/agent",
|
||||
{ message: "Do it", name: "Email" },
|
||||
{ headers: { "Idempotency-Key": oversizedKey } },
|
||||
);
|
||||
expect(second.status).toBe(200);
|
||||
mockIsolatedRunOk();
|
||||
await expectFirstHookDelivery(port, oversizedKey);
|
||||
await postAgentHookWithIdempotency(port, oversizedKey);
|
||||
await waitForSystemEvent();
|
||||
|
||||
expect(cronIsolatedRun).toHaveBeenCalledTimes(2);
|
||||
|
|
@ -403,19 +380,8 @@ describe("gateway server hooks", () => {
|
|||
nowSpy.mockReturnValue(1_000_000);
|
||||
|
||||
await withGatewayServer(async ({ port }) => {
|
||||
cronIsolatedRun.mockClear();
|
||||
cronIsolatedRun.mockResolvedValue({ status: "ok", summary: "done" });
|
||||
|
||||
const first = await postHook(
|
||||
port,
|
||||
"/hooks/agent",
|
||||
{ message: "Do it", name: "Email" },
|
||||
{ headers: { "Idempotency-Key": "fixed-window-idem" } },
|
||||
);
|
||||
expect(first.status).toBe(200);
|
||||
const firstBody = (await first.json()) as { runId?: string };
|
||||
await waitForSystemEvent();
|
||||
drainSystemEvents(resolveMainKey());
|
||||
mockIsolatedRunOk();
|
||||
const firstBody = await expectFirstHookDelivery(port, "fixed-window-idem");
|
||||
|
||||
nowSpy.mockReturnValue(1_000_000 + DEDUPE_TTL_MS - 1);
|
||||
const second = await postHook(
|
||||
|
|
|
|||
Loading…
Reference in New Issue