diff --git a/scripts/e2e/lib/upgrade-survivor/probe-gateway.mjs b/scripts/e2e/lib/upgrade-survivor/probe-gateway.mjs index cabdb324361..a3202262003 100644 --- a/scripts/e2e/lib/upgrade-survivor/probe-gateway.mjs +++ b/scripts/e2e/lib/upgrade-survivor/probe-gateway.mjs @@ -17,11 +17,37 @@ function option(name, fallback) { return value; } +function optionValue(name, envName, fallback) { + const index = args.indexOf(name); + if (index !== -1) { + return { + label: name, + value: option(name), + }; + } + return { + label: envName, + value: process.env[envName] ?? fallback, + }; +} + function writeJson(file, value) { fs.mkdirSync(path.dirname(file), { recursive: true }); fs.writeFileSync(file, `${JSON.stringify(value, null, 2)}\n`); } +function readStrictInteger({ allowZero = false, label, value }) { + const text = String(value ?? "").trim(); + if (!/^\d+$/u.test(text)) { + throw new Error(`invalid ${label}: ${text}`); + } + const parsed = Number(text); + if (!Number.isSafeInteger(parsed) || (allowZero ? parsed < 0 : parsed <= 0)) { + throw new Error(`invalid ${label}: ${text}`); + } + return parsed; +} + const baseUrl = option("--base-url"); const probePath = option("--path"); const expectKind = option("--expect"); @@ -32,35 +58,25 @@ const allowFailing = new Set( .map((entry) => entry.trim()) .filter(Boolean), ); -const timeoutMs = Number.parseInt( - option("--timeout-ms", process.env.OPENCLAW_UPGRADE_SURVIVOR_PROBE_TIMEOUT_MS || "60000"), - 10, +const timeoutOption = optionValue( + "--timeout-ms", + "OPENCLAW_UPGRADE_SURVIVOR_PROBE_TIMEOUT_MS", + "60000", ); -const attemptTimeoutMs = Number.parseInt( - option( - "--attempt-timeout-ms", - process.env.OPENCLAW_UPGRADE_SURVIVOR_PROBE_ATTEMPT_TIMEOUT_MS || "5000", - ), - 10, +const attemptTimeoutOption = optionValue( + "--attempt-timeout-ms", + "OPENCLAW_UPGRADE_SURVIVOR_PROBE_ATTEMPT_TIMEOUT_MS", + "5000", ); -const maxBodyBytes = Number.parseInt( - option( - "--max-body-bytes", - process.env.OPENCLAW_UPGRADE_SURVIVOR_PROBE_MAX_BODY_BYTES || "1048576", - ), - 10, +const maxBodyOption = optionValue( + "--max-body-bytes", + "OPENCLAW_UPGRADE_SURVIVOR_PROBE_MAX_BODY_BYTES", + "1048576", ); +const timeoutMs = readStrictInteger({ ...timeoutOption, allowZero: true }); +const attemptTimeoutMs = readStrictInteger(attemptTimeoutOption); +const maxBodyBytes = readStrictInteger(maxBodyOption); const url = new URL(probePath, baseUrl).toString(); - -if (!Number.isFinite(timeoutMs) || timeoutMs < 0) { - throw new Error(`invalid --timeout-ms: ${String(timeoutMs)}`); -} -if (!Number.isFinite(attemptTimeoutMs) || attemptTimeoutMs <= 0) { - throw new Error(`invalid --attempt-timeout-ms: ${String(attemptTimeoutMs)}`); -} -if (!Number.isFinite(maxBodyBytes) || maxBodyBytes <= 0) { - throw new Error(`invalid --max-body-bytes: ${String(maxBodyBytes)}`); -} if (expectKind !== "live" && expectKind !== "ready") { throw new Error(`unknown probe expectation: ${expectKind}`); } diff --git a/test/scripts/upgrade-survivor-probe-gateway.test.ts b/test/scripts/upgrade-survivor-probe-gateway.test.ts index 82a7dcfb018..3564ff970ed 100644 --- a/test/scripts/upgrade-survivor-probe-gateway.test.ts +++ b/test/scripts/upgrade-survivor-probe-gateway.test.ts @@ -23,9 +23,14 @@ interface ProbeResult { stdout: string; } -function runProbe(args: string[], timeout = 5_000): Promise { +function runProbe( + args: string[], + timeout = 5_000, + env: NodeJS.ProcessEnv = {}, +): Promise { return new Promise((resolve) => { const child = spawn(process.execPath, [probePath, ...args], { + env: { ...process.env, ...env }, stdio: ["ignore", "pipe", "pipe"], }); let stdout = ""; @@ -82,6 +87,47 @@ afterEach(() => { }); describe("scripts/e2e/lib/upgrade-survivor/probe-gateway.mjs", () => { + it("rejects loose numeric probe limits instead of parsing prefixes", async () => { + const out = path.join(makeTempDir(), "invalid.json"); + const timeoutResult = await runProbe([ + "--base-url", + "http://127.0.0.1:9", + "--path", + "/readyz", + "--expect", + "ready", + "--out", + out, + "--timeout-ms", + "1e3", + ]); + + expect(timeoutResult.status).not.toBe(0); + expect(timeoutResult.stderr).toContain("invalid --timeout-ms: 1e3"); + + const bodyLimitResult = await runProbe( + [ + "--base-url", + "http://127.0.0.1:9", + "--path", + "/readyz", + "--expect", + "ready", + "--out", + out, + ], + 5_000, + { + OPENCLAW_UPGRADE_SURVIVOR_PROBE_MAX_BODY_BYTES: "64bytes", + }, + ); + + expect(bodyLimitResult.status).not.toBe(0); + expect(bodyLimitResult.stderr).toContain( + "invalid OPENCLAW_UPGRADE_SURVIVOR_PROBE_MAX_BODY_BYTES: 64bytes", + ); + }); + it("writes a result when the ready probe matches", async () => { const server = createHttpServer((_request, response) => { response.writeHead(200, { "content-type": "application/json" });