fix: clean up QA temp roots on node lookup failure

This commit is contained in:
Gustavo Madeira Santana
2026-04-15 20:21:19 -04:00
parent e8a5b24f13
commit 1f91f218fc
2 changed files with 43 additions and 2 deletions

View File

@@ -4,18 +4,37 @@ import os from "node:os";
import path from "node:path";
import { pathToFileURL } from "node:url";
import { afterEach, describe, expect, it, vi } from "vitest";
import { __testing, buildQaRuntimeEnv, resolveQaControlUiRoot } from "./gateway-child.js";
import {
__testing,
buildQaRuntimeEnv,
resolveQaControlUiRoot,
startQaGatewayChild,
} from "./gateway-child.js";
const fetchWithSsrFGuardMock = vi.hoisted(() => vi.fn());
const resolveQaNodeExecPathMock = vi.hoisted(() => vi.fn(async () => process.execPath));
const qaTempPathState = vi.hoisted(() => ({
preferredTmpDir: process.env.TMPDIR || "/tmp",
}));
vi.mock("openclaw/plugin-sdk/ssrf-runtime", () => ({
fetchWithSsrFGuard: fetchWithSsrFGuardMock,
}));
vi.mock("openclaw/plugin-sdk/temp-path", () => ({
resolvePreferredOpenClawTmpDir: () => qaTempPathState.preferredTmpDir,
}));
vi.mock("./node-exec.js", () => ({
resolveQaNodeExecPath: resolveQaNodeExecPathMock,
}));
const cleanups: Array<() => Promise<void>> = [];
afterEach(async () => {
fetchWithSsrFGuardMock.mockReset();
resolveQaNodeExecPathMock.mockReset();
qaTempPathState.preferredTmpDir = process.env.TMPDIR || "/tmp";
while (cleanups.length > 0) {
await cleanups.pop()?.();
}
@@ -37,6 +56,28 @@ function createParams(baseEnv?: NodeJS.ProcessEnv) {
}
describe("buildQaRuntimeEnv", () => {
it("cleans up temp QA gateway roots when node path resolution fails before startup", async () => {
const tempParent = await mkdtemp(path.join(os.tmpdir(), "qa-gateway-node-exec-fail-"));
cleanups.push(async () => {
await rm(tempParent, { recursive: true, force: true });
});
qaTempPathState.preferredTmpDir = tempParent;
resolveQaNodeExecPathMock.mockRejectedValueOnce(new Error("node missing"));
await expect(
startQaGatewayChild({
repoRoot: process.cwd(),
transport: {
requiredPluginIds: [],
createGatewayConfig: () => ({}),
},
transportBaseUrl: "http://127.0.0.1:43123",
}),
).rejects.toThrow("node missing");
await expect(readdir(tempParent)).resolves.toEqual([]);
});
it("keeps the slow-reply QA opt-out enabled under fast mode", () => {
const env = buildQaRuntimeEnv({
...createParams(),

View File

@@ -820,9 +820,9 @@ export async function startQaGatewayChild(params: {
let rpcClient: Awaited<ReturnType<typeof startQaGatewayRpcClient>> | null = null;
let stagedBundledPluginsRoot: string | null = null;
let env: NodeJS.ProcessEnv | null = null;
const nodeExecPath = await resolveQaNodeExecPath();
try {
const nodeExecPath = await resolveQaNodeExecPath();
for (let attempt = 1; attempt <= QA_GATEWAY_CHILD_STARTUP_MAX_ATTEMPTS; attempt += 1) {
gatewayPort = await getFreePort();
baseUrl = `http://127.0.0.1:${gatewayPort}`;