fix flaky gateway tests in CI
What: - resolve shell from PATH in bash-tools tests (avoid /bin/bash dependency) - mock DNS for web-fetch SSRF tests (no real network) - stub a2ui bundle in canvas-host server test when missing Why: - keep gateway test suite deterministic on Nix/Garnix Linux Tests: - not run locally (known missing deps in unit test run)
This commit is contained in:
parent
c41ea252b0
commit
5f4715acfc
@ -1,3 +1,4 @@
|
|||||||
|
import fs from "node:fs";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
|
|
||||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||||
@ -8,6 +9,24 @@ import { buildDockerExecArgs } from "./bash-tools.shared.js";
|
|||||||
import { sanitizeBinaryOutput } from "./shell-utils.js";
|
import { sanitizeBinaryOutput } from "./shell-utils.js";
|
||||||
|
|
||||||
const isWin = process.platform === "win32";
|
const isWin = process.platform === "win32";
|
||||||
|
const resolveShellFromPath = (name: string) => {
|
||||||
|
const envPath = process.env.PATH ?? "";
|
||||||
|
if (!envPath) return undefined;
|
||||||
|
const entries = envPath.split(path.delimiter).filter(Boolean);
|
||||||
|
for (const entry of entries) {
|
||||||
|
const candidate = path.join(entry, name);
|
||||||
|
try {
|
||||||
|
fs.accessSync(candidate, fs.constants.X_OK);
|
||||||
|
return candidate;
|
||||||
|
} catch {
|
||||||
|
// ignore missing or non-executable entries
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
const defaultShell = isWin
|
||||||
|
? undefined
|
||||||
|
: process.env.CLAWDBOT_TEST_SHELL || resolveShellFromPath("bash") || process.env.SHELL || "sh";
|
||||||
// PowerShell: Start-Sleep for delays, ; for command separation, $null for null device
|
// PowerShell: Start-Sleep for delays, ; for command separation, $null for null device
|
||||||
const shortDelayCmd = isWin ? "Start-Sleep -Milliseconds 50" : "sleep 0.05";
|
const shortDelayCmd = isWin ? "Start-Sleep -Milliseconds 50" : "sleep 0.05";
|
||||||
const yieldDelayCmd = isWin ? "Start-Sleep -Milliseconds 200" : "sleep 0.2";
|
const yieldDelayCmd = isWin ? "Start-Sleep -Milliseconds 200" : "sleep 0.2";
|
||||||
@ -52,7 +71,7 @@ describe("exec tool backgrounding", () => {
|
|||||||
const originalShell = process.env.SHELL;
|
const originalShell = process.env.SHELL;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
if (!isWin) process.env.SHELL = "/bin/bash";
|
if (!isWin && defaultShell) process.env.SHELL = defaultShell;
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@ -282,7 +301,7 @@ describe("exec PATH handling", () => {
|
|||||||
const originalShell = process.env.SHELL;
|
const originalShell = process.env.SHELL;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
if (!isWin) process.env.SHELL = "/bin/bash";
|
if (!isWin && defaultShell) process.env.SHELL = defaultShell;
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
|||||||
@ -1,10 +1,9 @@
|
|||||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
|
import * as ssrf from "../../infra/net/ssrf.js";
|
||||||
|
|
||||||
const lookupMock = vi.fn();
|
const lookupMock = vi.fn();
|
||||||
|
const resolvePinnedHostname = ssrf.resolvePinnedHostname;
|
||||||
vi.mock("node:dns/promises", () => ({
|
|
||||||
lookup: lookupMock,
|
|
||||||
}));
|
|
||||||
|
|
||||||
function makeHeaders(map: Record<string, string>): { get: (key: string) => string | null } {
|
function makeHeaders(map: Record<string, string>): { get: (key: string) => string | null } {
|
||||||
return {
|
return {
|
||||||
@ -33,6 +32,12 @@ function textResponse(body: string): Response {
|
|||||||
describe("web_fetch SSRF protection", () => {
|
describe("web_fetch SSRF protection", () => {
|
||||||
const priorFetch = global.fetch;
|
const priorFetch = global.fetch;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.spyOn(ssrf, "resolvePinnedHostname").mockImplementation((hostname) =>
|
||||||
|
resolvePinnedHostname(hostname, lookupMock),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
// @ts-expect-error restore
|
// @ts-expect-error restore
|
||||||
global.fetch = priorFetch;
|
global.fetch = priorFetch;
|
||||||
|
|||||||
@ -202,6 +202,16 @@ describe("canvas host", () => {
|
|||||||
|
|
||||||
it("serves the gateway-hosted A2UI scaffold", async () => {
|
it("serves the gateway-hosted A2UI scaffold", async () => {
|
||||||
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "moltbot-canvas-"));
|
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "moltbot-canvas-"));
|
||||||
|
const a2uiRoot = path.resolve(process.cwd(), "src/canvas-host/a2ui");
|
||||||
|
const bundlePath = path.join(a2uiRoot, "a2ui.bundle.js");
|
||||||
|
let createdBundle = false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await fs.stat(bundlePath);
|
||||||
|
} catch {
|
||||||
|
await fs.writeFile(bundlePath, "window.moltbotA2UI = {};", "utf8");
|
||||||
|
createdBundle = true;
|
||||||
|
}
|
||||||
|
|
||||||
const server = await startCanvasHost({
|
const server = await startCanvasHost({
|
||||||
runtime: defaultRuntime,
|
runtime: defaultRuntime,
|
||||||
@ -226,6 +236,9 @@ describe("canvas host", () => {
|
|||||||
expect(js).toContain("moltbotA2UI");
|
expect(js).toContain("moltbotA2UI");
|
||||||
} finally {
|
} finally {
|
||||||
await server.close();
|
await server.close();
|
||||||
|
if (createdBundle) {
|
||||||
|
await fs.rm(bundlePath, { force: true });
|
||||||
|
}
|
||||||
await fs.rm(dir, { recursive: true, force: true });
|
await fs.rm(dir, { recursive: true, force: true });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user