mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-24 19:35:28 -06:00
fix(qa): cancel Docker health probe bodies
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
import { createServer } from "node:net";
|
||||
/**
|
||||
* Tests QA runtime command loading and private CLI gating.
|
||||
*/
|
||||
import { Command } from "commander";
|
||||
import { createServer } from "node:net";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
cleanupTempDirs,
|
||||
@@ -60,6 +60,21 @@ describe("plugin-sdk qa-runtime", () => {
|
||||
};
|
||||
}
|
||||
|
||||
function cancelTrackedFetchResponse(ok = true) {
|
||||
let canceled = false;
|
||||
return {
|
||||
response: {
|
||||
ok,
|
||||
body: {
|
||||
cancel: vi.fn(async () => {
|
||||
canceled = true;
|
||||
}),
|
||||
},
|
||||
},
|
||||
wasCanceled: () => canceled,
|
||||
};
|
||||
}
|
||||
|
||||
it("stays cold until the runtime seam is used", async () => {
|
||||
const module = await import("./qa-runtime.js");
|
||||
|
||||
@@ -341,6 +356,52 @@ describe("plugin-sdk qa-runtime", () => {
|
||||
expect(fetchImpl).toHaveBeenCalledWith("http://172.18.0.4:18789/healthz");
|
||||
});
|
||||
|
||||
it("cancels compose service health probe response bodies", async () => {
|
||||
const module = await import("./qa-runtime.js");
|
||||
const runtime = module.createQaDockerRuntime({ auditContext: "qa-test" });
|
||||
const runCommand = vi.fn(async (_command: string, args: string[]) => {
|
||||
if (args.includes("ps")) {
|
||||
return { stdout: "qa-gateway-one\n", stderr: "" };
|
||||
}
|
||||
return { stdout: "172.18.0.4\n", stderr: "" };
|
||||
});
|
||||
const probe = cancelTrackedFetchResponse(true);
|
||||
const fetchImpl = vi.fn(async () => probe.response);
|
||||
|
||||
await expect(
|
||||
runtime.resolveComposeServiceUrl(
|
||||
"gateway",
|
||||
18789,
|
||||
"/tmp/docker-compose.yml",
|
||||
"/repo",
|
||||
runCommand,
|
||||
fetchImpl,
|
||||
),
|
||||
).resolves.toBe("http://172.18.0.4:18789/");
|
||||
expect(probe.wasCanceled()).toBe(true);
|
||||
});
|
||||
|
||||
it("cancels waitForHealth response bodies after each probe", async () => {
|
||||
const module = await import("./qa-runtime.js");
|
||||
const runtime = module.createQaDockerRuntime({ auditContext: "qa-test" });
|
||||
const first = cancelTrackedFetchResponse(false);
|
||||
const second = cancelTrackedFetchResponse(true);
|
||||
const responses = [first.response, second.response];
|
||||
const fetchImpl = vi.fn(async () => responses.shift() ?? second.response);
|
||||
const sleepImpl = vi.fn(async () => {});
|
||||
|
||||
await runtime.waitForHealth("http://127.0.0.1:18789/healthz", {
|
||||
fetchImpl,
|
||||
sleepImpl,
|
||||
timeoutMs: 1000,
|
||||
pollMs: 1,
|
||||
});
|
||||
|
||||
expect(fetchImpl).toHaveBeenCalledTimes(2);
|
||||
expect(first.wasCanceled()).toBe(true);
|
||||
expect(second.wasCanceled()).toBe(true);
|
||||
});
|
||||
|
||||
it("resolves an unpinned QA Docker host port away from an occupied loopback default", async () => {
|
||||
const module = await import("./qa-runtime.js");
|
||||
const reservation = await occupyLoopbackPort();
|
||||
|
||||
@@ -249,7 +249,11 @@ export type QaDockerRunCommand = (
|
||||
) => Promise<{ stdout: string; stderr: string }>;
|
||||
|
||||
/** Minimal fetch-like health probe used by QA Docker runtime helpers. */
|
||||
export type QaDockerFetchLike = (input: string) => Promise<{ ok: boolean }>;
|
||||
export type QaDockerFetchResponse = {
|
||||
ok: boolean;
|
||||
body?: { cancel?: () => unknown } | null;
|
||||
};
|
||||
export type QaDockerFetchLike = (input: string) => Promise<QaDockerFetchResponse>;
|
||||
|
||||
const DEFAULT_QA_DOCKER_COMMAND_TIMEOUT_MS = 120_000;
|
||||
|
||||
@@ -484,14 +488,23 @@ function parseDockerComposePsRows(stdout: string) {
|
||||
}
|
||||
|
||||
async function isQaDockerHealthy(url: string, fetchImpl: QaDockerFetchLike) {
|
||||
let response: QaDockerFetchResponse | undefined;
|
||||
try {
|
||||
const response = await fetchImpl(url);
|
||||
response = await fetchImpl(url);
|
||||
return response.ok;
|
||||
} catch {
|
||||
return false;
|
||||
} finally {
|
||||
await releaseQaDockerFetchResponse(response);
|
||||
}
|
||||
}
|
||||
|
||||
async function releaseQaDockerFetchResponse(response: QaDockerFetchResponse | undefined) {
|
||||
try {
|
||||
await response?.body?.cancel?.();
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** Create Docker command, health-check, and compose helpers for QA harnesses. */
|
||||
export function createQaDockerRuntime(params: {
|
||||
auditContext: string;
|
||||
@@ -548,14 +561,17 @@ export function createQaDockerRuntime(params: {
|
||||
let lastError: unknown = null;
|
||||
|
||||
while (Date.now() < deadline) {
|
||||
let response: QaDockerFetchResponse | undefined;
|
||||
try {
|
||||
const response = await deps.fetchImpl(url);
|
||||
response = await deps.fetchImpl(url);
|
||||
if (response.ok) {
|
||||
return;
|
||||
}
|
||||
lastError = new Error(`Health check returned non-OK for ${url}`);
|
||||
} catch (error) {
|
||||
lastError = error;
|
||||
} finally {
|
||||
await releaseQaDockerFetchResponse(response);
|
||||
}
|
||||
await deps.sleepImpl(pollMs);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user