diff --git a/scripts/e2e/lib/openwebui/http-probe.mjs b/scripts/e2e/lib/openwebui/http-probe.mjs index 8ea5e087b7cb..6a6af327f603 100644 --- a/scripts/e2e/lib/openwebui/http-probe.mjs +++ b/scripts/e2e/lib/openwebui/http-probe.mjs @@ -1,11 +1,7 @@ // HTTP probe for OpenWebUI E2E scenarios. +import { pathToFileURL } from "node:url"; import { readPositiveIntEnv } from "../env-limits.mjs"; -const [url, expectedRaw = "200"] = process.argv.slice(2); -if (!url) { - throw new Error("usage: http-probe.mjs [status|lt500]"); -} - function parseExpectedStatus(raw) { if (!/^[1-5]\d\d$/u.test(raw)) { throw new Error(`expected status must be lt500 or a decimal HTTP status. Got: ${raw}`); @@ -13,20 +9,47 @@ function parseExpectedStatus(raw) { return Number(raw); } -const timeoutMs = readPositiveIntEnv("OPENCLAW_HTTP_PROBE_TIMEOUT_MS", 30_000); -const expectedStatus = expectedRaw === "lt500" ? undefined : parseExpectedStatus(expectedRaw); -const controller = new AbortController(); -const timer = setTimeout(() => controller.abort(), timeoutMs); - -try { - const headers = {}; - if (process.env.OPENCLAW_HTTP_PROBE_BEARER) { - headers.authorization = `Bearer ${process.env.OPENCLAW_HTTP_PROBE_BEARER}`; +export async function probeHttpStatus({ + url, + expectedRaw = "200", + timeoutMs = 30_000, + bearer = "", + fetchImpl = fetch, +}) { + if (!url) { + throw new Error("usage: http-probe.mjs [status|lt500]"); + } + const expectedStatus = expectedRaw === "lt500" ? undefined : parseExpectedStatus(expectedRaw); + const controller = new AbortController(); + const timer = setTimeout(() => controller.abort(), timeoutMs); + let res; + const headers = {}; + if (bearer) { + headers.authorization = `Bearer ${bearer}`; + } + + try { + res = await fetchImpl(url, { headers, signal: controller.signal }).catch(() => null); + return expectedRaw === "lt500" + ? Boolean(res && res.status < 500) + : res?.status === expectedStatus; + } finally { + clearTimeout(timer); + await res?.body?.cancel?.().catch(() => undefined); } - const res = await fetch(url, { headers, signal: controller.signal }).catch(() => null); - const ok = - expectedRaw === "lt500" ? Boolean(res && res.status < 500) : res?.status === expectedStatus; - process.exit(ok ? 0 : 1); -} finally { - clearTimeout(timer); +} + +async function main() { + const [url, expectedRaw = "200"] = process.argv.slice(2); + const ok = await probeHttpStatus({ + url, + expectedRaw, + timeoutMs: readPositiveIntEnv("OPENCLAW_HTTP_PROBE_TIMEOUT_MS", 30_000), + bearer: process.env.OPENCLAW_HTTP_PROBE_BEARER, + }); + process.exit(ok ? 0 : 1); +} + +if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { + await main(); } diff --git a/test/scripts/e2e-helper-env-limits.test.ts b/test/scripts/e2e-helper-env-limits.test.ts index 7093331c38b5..a1ee39b19890 100644 --- a/test/scripts/e2e-helper-env-limits.test.ts +++ b/test/scripts/e2e-helper-env-limits.test.ts @@ -210,4 +210,30 @@ describe("e2e helper numeric env limits", () => { server.close(); } }); + + it("cancels Open WebUI HTTP probe response bodies", async () => { + const { probeHttpStatus } = await import("../../scripts/e2e/lib/openwebui/http-probe.mjs"); + let canceled = false; + const fetchImpl = (async (_url: string, init: RequestInit) => { + expect(init.headers).toEqual({ authorization: "Bearer token-123" }); + return new Response( + new ReadableStream({ + cancel() { + canceled = true; + }, + }), + { status: 200 }, + ); + }) as typeof fetch; + + await expect( + probeHttpStatus({ + bearer: "token-123", + fetchImpl, + timeoutMs: 500, + url: "http://127.0.0.1/probe", + }), + ).resolves.toBe(true); + expect(canceled).toBe(true); + }); });