fix(e2e): bound ClickClack fixture plugin fetch requests (#108826)

* fix(e2e): bound ClickClack fixture plugin fetch requests

* test(e2e): exercise ClickClack response deadline

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
zengLingbiao
2026-07-16 23:35:51 +08:00
committed by GitHub
parent ac3635b50c
commit b273fd41c4
2 changed files with 69 additions and 1 deletions
@@ -103,6 +103,7 @@ async function requestJson(account, method, pathname, body) {
...(body == null ? {} : { "content-type": "application/json" }),
},
...(body == null ? {} : { body: JSON.stringify(body) }),
signal: AbortSignal.timeout(30_000),
});
if (!response.ok) {
throw new Error(\`ClickClack fixture \${response.status}: \${await response.text()}\`);
+68 -1
View File
@@ -4,14 +4,27 @@ import fs from "node:fs";
import { createServer, type Server } from "node:http";
import os from "node:os";
import path from "node:path";
import { pathToFileURL } from "node:url";
import { MAX_TIMER_TIMEOUT_MS } from "@openclaw/normalization-core/number-coercion";
import { describe, expect, it } from "vitest";
import { describe, expect, it, vi } from "vitest";
import { createBoundedChildOutput } from "../helpers/bounded-child-output.js";
const browserFixturePath = "scripts/e2e/lib/browser-cdp-snapshot/fixture-server.mjs";
const clickclackFixturePath = "scripts/e2e/lib/release-user-journey/clickclack-fixture.mjs";
const clickclackPluginWritePath =
"scripts/e2e/lib/release-user-journey/write-clickclack-plugin.mjs";
const httpProbePath = "scripts/e2e/lib/openwebui/http-probe.mjs";
type ClickClackFixturePlugin = {
outbound: {
sendText(ctx: {
cfg: { channels: { clickclack: { baseUrl: string; token: string } } };
text: string;
to: string;
}): Promise<unknown>;
};
};
function runScript(scriptPath: string, args: string[] = [], env: Record<string, string> = {}) {
return spawnSync(process.execPath, [scriptPath, ...args], {
encoding: "utf8",
@@ -265,4 +278,58 @@ describe("e2e helper numeric env limits", () => {
}),
).resolves.toBe(true);
});
it("bounds generated ClickClack plugin response bodies", async () => {
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-clickclack-plugin-"));
let headersSentResolve: (() => void) | undefined;
const headersSent = new Promise<void>((resolve) => {
headersSentResolve = resolve;
});
const server = createServer((_request, response) => {
response.writeHead(200, { "content-type": "application/json" });
response.flushHeaders();
headersSentResolve?.();
});
const baseUrl = await listen(server);
const realTimeout = AbortSignal.timeout;
const timeoutSpy = vi.spyOn(AbortSignal, "timeout").mockImplementation(() => realTimeout(200));
try {
const result = runScript(clickclackPluginWritePath, [tempDir]);
expect(result.status).toBe(0);
const generated = (await import(pathToFileURL(path.join(tempDir, "index.mjs")).href)) as {
default: {
register(api: {
registerChannel(entry: { plugin: ClickClackFixturePlugin }): void;
}): void;
};
};
let plugin: ClickClackFixturePlugin | undefined;
generated.default.register({
registerChannel: ({ plugin: registeredPlugin }) => {
plugin = registeredPlugin;
},
});
if (!plugin) {
throw new Error("generated ClickClack plugin did not register a channel");
}
const startedAt = Date.now();
const request = plugin.outbound.sendText({
cfg: { channels: { clickclack: { baseUrl, token: "x" } } },
text: "hello",
to: "channel:general",
});
const rejection = expect(request).rejects.toMatchObject({ name: "TimeoutError" });
await headersSent;
await rejection;
expect(timeoutSpy).toHaveBeenCalledWith(30_000);
expect(Date.now() - startedAt).toBeLessThan(2_000);
} finally {
timeoutSpy.mockRestore();
server.closeAllConnections();
await new Promise<void>((resolve) => server.close(() => resolve()));
fs.rmSync(tempDir, { force: true, recursive: true });
}
});
});