























@@ -0,0 +1,188 @@
1+import { spawn } from "node:child_process";
2+import fs from "node:fs";
3+import { createServer as createHttpServer } from "node:http";
4+import { createServer as createTcpServer, type Server, type Socket } from "node:net";
5+import os from "node:os";
6+import path from "node:path";
7+import { afterEach, describe, expect, it } from "vitest";
8+9+const probePath = path.resolve("scripts/e2e/lib/upgrade-survivor/probe-gateway.mjs");
10+const tempDirs: string[] = [];
11+12+function makeTempDir(): string {
13+const dir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-upgrade-probe-"));
14+tempDirs.push(dir);
15+return dir;
16+}
17+18+interface ProbeResult {
19+error?: Error;
20+signal: NodeJS.Signals | null;
21+status: number | null;
22+stderr: string;
23+stdout: string;
24+}
25+26+function runProbe(args: string[], timeout = 5_000): Promise<ProbeResult> {
27+return new Promise((resolve) => {
28+const child = spawn(process.execPath, [probePath, ...args], {
29+stdio: ["ignore", "pipe", "pipe"],
30+});
31+let stdout = "";
32+let stderr = "";
33+let timedOut = false;
34+child.stdout.setEncoding("utf8");
35+child.stderr.setEncoding("utf8");
36+child.stdout.on("data", (chunk) => {
37+stdout += chunk;
38+});
39+child.stderr.on("data", (chunk) => {
40+stderr += chunk;
41+});
42+const timer = setTimeout(() => {
43+timedOut = true;
44+child.kill("SIGKILL");
45+}, timeout);
46+child.on("error", (error) => {
47+clearTimeout(timer);
48+resolve({ error, signal: null, status: null, stderr, stdout });
49+});
50+child.on("exit", (status, signal) => {
51+clearTimeout(timer);
52+resolve({
53+error: timedOut ? new Error(`probe timed out after ${timeout}ms`) : undefined,
54+ signal,
55+ status,
56+ stderr,
57+ stdout,
58+});
59+});
60+});
61+}
62+63+async function listen(server: Server): Promise<string> {
64+await new Promise<void>((resolve, reject) => {
65+server.once("error", reject);
66+server.listen(0, "127.0.0.1", () => {
67+server.off("error", reject);
68+resolve();
69+});
70+});
71+const address = server.address();
72+if (!address || typeof address === "string") {
73+throw new Error("test server did not expose a TCP port");
74+}
75+return `http://127.0.0.1:${address.port}`;
76+}
77+78+afterEach(() => {
79+for (const dir of tempDirs.splice(0)) {
80+fs.rmSync(dir, { force: true, recursive: true });
81+}
82+});
83+84+describe("scripts/e2e/lib/upgrade-survivor/probe-gateway.mjs", () => {
85+it("writes a result when the ready probe matches", async () => {
86+const server = createHttpServer((_request, response) => {
87+response.writeHead(200, { "content-type": "application/json" });
88+response.end(JSON.stringify({ ready: true }));
89+});
90+const baseUrl = await listen(server);
91+const out = path.join(makeTempDir(), "ready.json");
92+try {
93+const result = await runProbe([
94+"--base-url",
95+baseUrl,
96+"--path",
97+"/readyz",
98+"--expect",
99+"ready",
100+"--out",
101+out,
102+"--timeout-ms",
103+"1000",
104+]);
105+106+expect(result.status).toBe(0);
107+expect(JSON.parse(fs.readFileSync(out, "utf8"))).toMatchObject({
108+body: { ready: true },
109+path: "/readyz",
110+status: 200,
111+url: `${baseUrl}/readyz`,
112+});
113+} finally {
114+server.close();
115+}
116+});
117+118+it("bounds probes when a server accepts the connection but never responds", async () => {
119+const sockets = new Set<Socket>();
120+const server = createTcpServer((socket) => {
121+sockets.add(socket);
122+socket.on("close", () => sockets.delete(socket));
123+socket.on("data", () => {});
124+});
125+const baseUrl = await listen(server);
126+const out = path.join(makeTempDir(), "stall.json");
127+const startedAt = Date.now();
128+try {
129+const result = await runProbe([
130+"--base-url",
131+baseUrl,
132+"--path",
133+"/healthz",
134+"--expect",
135+"live",
136+"--out",
137+out,
138+"--timeout-ms",
139+"300",
140+"--attempt-timeout-ms",
141+"100",
142+]);
143+const elapsedMs = Date.now() - startedAt;
144+145+expect(result.error).toBeUndefined();
146+expect(result.status).not.toBe(0);
147+expect(result.stderr).toContain("probe did not satisfy live within 300ms");
148+expect(elapsedMs).toBeLessThan(2_500);
149+expect(fs.existsSync(out)).toBe(false);
150+} finally {
151+for (const socket of sockets) {
152+socket.destroy();
153+}
154+server.close();
155+}
156+});
157+158+it("caps response bodies before parsing probe JSON", async () => {
159+const server = createHttpServer((_request, response) => {
160+response.writeHead(200, { "content-type": "application/json" });
161+response.end("x".repeat(256));
162+});
163+const baseUrl = await listen(server);
164+const out = path.join(makeTempDir(), "oversized.json");
165+try {
166+const result = await runProbe([
167+"--base-url",
168+baseUrl,
169+"--path",
170+"/readyz",
171+"--expect",
172+"ready",
173+"--out",
174+out,
175+"--timeout-ms",
176+"300",
177+"--max-body-bytes",
178+"64",
179+]);
180+181+expect(result.status).not.toBe(0);
182+expect(result.stderr).toContain("probe body exceeded 64 bytes");
183+expect(fs.existsSync(out)).toBe(false);
184+} finally {
185+server.close();
186+}
187+});
188+});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。