fix(e2e): reject loose upgrade probe limits

This commit is contained in:
Vincent Koc
2026-05-30 14:40:11 +02:00
parent 0840fea50d
commit ec58491f75
2 changed files with 88 additions and 26 deletions

View File

@@ -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}`);
}

View File

@@ -23,9 +23,14 @@ interface ProbeResult {
stdout: string;
}
function runProbe(args: string[], timeout = 5_000): Promise<ProbeResult> {
function runProbe(
args: string[],
timeout = 5_000,
env: NodeJS.ProcessEnv = {},
): Promise<ProbeResult> {
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" });