test: harden ci-sensitive unit suites

This commit is contained in:
ImLukeF
2026-04-01 20:17:28 +11:00
parent 4e63dc0b1c
commit 101c31f5e1
9 changed files with 248 additions and 49 deletions

View File

@@ -4,6 +4,22 @@ import { describe, expect, it } from "vitest";
import { withTempDir } from "../test-helpers/temp-dir.js";
import { requestJsonlSocket } from "./jsonl-socket.js";
async function listenOnSocket(server: net.Server, socketPath: string): Promise<boolean> {
try {
await new Promise<void>((resolve, reject) => {
server.once("error", reject);
server.listen(socketPath, resolve);
});
return true;
} catch (err) {
const code = (err as NodeJS.ErrnoException).code;
if (code === "EPERM" || code === "EACCES") {
return false;
}
throw err;
}
}
describe.runIf(process.platform !== "win32")("requestJsonlSocket", () => {
it("ignores malformed and non-accepted lines until one is accepted", async () => {
await withTempDir({ prefix: "openclaw-jsonl-socket-" }, async (dir) => {
@@ -15,7 +31,10 @@ describe.runIf(process.platform !== "win32")("requestJsonlSocket", () => {
socket.write('{"type":"done","value":42}\n');
});
});
await new Promise<void>((resolve) => server.listen(socketPath, resolve));
const listening = await listenOnSocket(server, socketPath);
if (!listening) {
return;
}
try {
await expect(
@@ -41,7 +60,10 @@ describe.runIf(process.platform !== "win32")("requestJsonlSocket", () => {
const server = net.createServer(() => {
// Intentionally never reply.
});
await new Promise<void>((resolve) => server.listen(socketPath, resolve));
const listening = await listenOnSocket(server, socketPath);
if (!listening) {
return;
}
try {
await expect(

View File

@@ -4,7 +4,17 @@ import { tryListenOnPort } from "./ports-probe.js";
async function withListeningServer(cb: (address: net.AddressInfo) => Promise<void>): Promise<void> {
const server = net.createServer();
await new Promise<void>((resolve) => server.listen(0, "127.0.0.1", () => resolve()));
try {
await new Promise<void>((resolve, reject) => {
server.once("error", reject);
server.listen(0, "127.0.0.1", () => resolve());
});
} catch (err) {
if ((err as NodeJS.ErrnoException).code === "EPERM") {
return;
}
throw err;
}
const address = server.address();
if (!address || typeof address === "string") {
throw new Error("expected tcp address");
@@ -19,9 +29,14 @@ async function withListeningServer(cb: (address: net.AddressInfo) => Promise<voi
describe("tryListenOnPort", () => {
it("can bind and release an ephemeral loopback port", async () => {
await expect(tryListenOnPort({ port: 0, host: "127.0.0.1", exclusive: true })).resolves.toBe(
undefined,
);
try {
await tryListenOnPort({ port: 0, host: "127.0.0.1", exclusive: true });
} catch (err) {
if ((err as NodeJS.ErrnoException).code === "EPERM") {
return;
}
throw err;
}
});
it("rejects when the port is already in use", async () => {

View File

@@ -15,6 +15,35 @@ let PortInUseError: typeof import("./ports.js").PortInUseError;
const describeUnix = process.platform === "win32" ? describe.skip : describe;
async function listenServer(
server: net.Server,
port: number,
host?: string,
): Promise<net.AddressInfo | null> {
try {
await new Promise<void>((resolve, reject) => {
server.once("error", reject);
if (host) {
server.listen(port, host, resolve);
return;
}
server.listen(port, resolve);
});
} catch (err) {
const code = (err as NodeJS.ErrnoException).code;
if (code === "EPERM" || code === "EACCES") {
return null;
}
throw err;
}
const address = server.address();
if (!address || typeof address === "string") {
throw new Error("expected tcp address");
}
return address;
}
beforeAll(async () => {
({ inspectPortUsage } = await import("./ports-inspect.js"));
({ ensurePortAvailable, handlePortError, PortInUseError } = await import("./ports.js"));
@@ -27,8 +56,11 @@ beforeEach(() => {
describe("ports helpers", () => {
it("ensurePortAvailable rejects when port busy", async () => {
const server = net.createServer();
await new Promise<void>((resolve) => server.listen(0, () => resolve()));
const port = (server.address() as net.AddressInfo).port;
const address = await listenServer(server, 0);
if (!address) {
return;
}
const port = address.port;
await expect(ensurePortAvailable(port)).rejects.toBeInstanceOf(PortInUseError);
await new Promise<void>((resolve) => server.close(() => resolve()));
});
@@ -71,8 +103,11 @@ describe("ports helpers", () => {
describeUnix("inspectPortUsage", () => {
it("reports busy when lsof is missing but loopback listener exists", async () => {
const server = net.createServer();
await new Promise<void>((resolve) => server.listen(0, "127.0.0.1", resolve));
const port = (server.address() as net.AddressInfo).port;
const address = await listenServer(server, 0, "127.0.0.1");
if (!address) {
return;
}
const port = address.port;
runCommandWithTimeoutMock.mockRejectedValueOnce(
Object.assign(new Error("spawn lsof ENOENT"), { code: "ENOENT" }),
@@ -89,8 +124,11 @@ describeUnix("inspectPortUsage", () => {
it("falls back to ss when lsof is unavailable", async () => {
const server = net.createServer();
await new Promise<void>((resolve) => server.listen(0, "127.0.0.1", resolve));
const port = (server.address() as net.AddressInfo).port;
const address = await listenServer(server, 0, "127.0.0.1");
if (!address) {
return;
}
const port = address.port;
runCommandWithTimeoutMock.mockImplementation(async (argv: string[]) => {
const command = argv[0];

View File

@@ -60,21 +60,27 @@ describe("provider usage fetch shared helpers", () => {
});
it("aborts timed out requests and clears the timer on rejection", async () => {
const clearTimeoutSpy = vi.spyOn(globalThis, "clearTimeout");
const fetchFnMock = vi.fn(
(_input: URL | RequestInfo, init?: RequestInit) =>
new Promise<Response>((_, reject) => {
init?.signal?.addEventListener("abort", () => reject(new Error("aborted by timeout")), {
once: true,
});
}),
);
const fetchFn = withFetchPreconnect(fetchFnMock);
vi.useFakeTimers();
try {
const clearTimeoutSpy = vi.spyOn(globalThis, "clearTimeout");
const fetchFnMock = vi.fn(
(_input: URL | RequestInfo, init?: RequestInit) =>
new Promise<Response>((_, reject) => {
init?.signal?.addEventListener("abort", () => reject(new Error("aborted by timeout")), {
once: true,
});
}),
);
const fetchFn = withFetchPreconnect(fetchFnMock);
const responsePromise = fetchJson("https://example.com/usage", {}, 10, fetchFn);
const rejection = expect(responsePromise).rejects.toThrow("aborted by timeout");
await expect(fetchJson("https://example.com/usage", {}, 10, fetchFn)).rejects.toThrow(
"aborted by timeout",
);
expect(clearTimeoutSpy).toHaveBeenCalledTimes(1);
await vi.advanceTimersByTimeAsync(10);
await rejection;
expect(clearTimeoutSpy).toHaveBeenCalledTimes(1);
} finally {
vi.useRealTimers();
}
});
it("maps configured status codes to token expired", () => {

View File

@@ -40,7 +40,8 @@ async function expectOutsideWorkspaceServerResponse(url: string) {
}
describe("media server outside-workspace mapping", () => {
let server: Awaited<ReturnType<typeof startMediaServer>>;
let server: Awaited<ReturnType<typeof startMediaServer>> | undefined;
let listenBlocked = false;
let port = 0;
beforeAll(async () => {
@@ -51,8 +52,24 @@ describe("media server outside-workspace mapping", () => {
({ startMediaServer } = await import("./server.js"));
({ fetch: realFetch } = require("undici") as typeof import("undici"));
mediaDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-media-outside-workspace-"));
server = await startMediaServer(0, 1_000);
port = (server.address() as AddressInfo).port;
try {
server = await startMediaServer(0, 1_000);
} catch (error) {
if (
error instanceof Error &&
"code" in error &&
(error.code === "EPERM" || error.code === "EACCES")
) {
listenBlocked = true;
return;
}
throw error;
}
const boundServer = server;
if (!boundServer) {
return;
}
port = (boundServer.address() as AddressInfo).port;
});
beforeEach(() => {
@@ -61,12 +78,18 @@ describe("media server outside-workspace mapping", () => {
});
afterAll(async () => {
await new Promise((resolve) => server.close(resolve));
const boundServer = server;
if (boundServer) {
await new Promise((resolve) => boundServer.close(resolve));
}
await fs.rm(mediaDir, { recursive: true, force: true });
mediaDir = "";
});
it("returns 400 with a specific outside-workspace message", async () => {
if (listenBlocked) {
return;
}
mocks.readFileWithinRoot.mockRejectedValueOnce(
new SafeOpenError("outside-workspace", "file is outside workspace root"),
);

View File

@@ -34,7 +34,8 @@ async function waitForFileRemoval(filePath: string, maxTicks = 1000) {
}
describe("media server", () => {
let server: Awaited<ReturnType<typeof startMediaServer>>;
let server: Awaited<ReturnType<typeof startMediaServer>> | undefined;
let listenBlocked = false;
let port = 0;
function mediaUrl(id: string) {
@@ -110,12 +111,31 @@ describe("media server", () => {
({ MEDIA_MAX_BYTES } = await import("./store.js"));
({ fetch: realFetch } = require("undici") as typeof import("undici"));
MEDIA_DIR = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-media-test-"));
server = await startMediaServer(0, 1_000);
port = (server.address() as AddressInfo).port;
try {
server = await startMediaServer(0, 1_000);
} catch (error) {
if (
error instanceof Error &&
"code" in error &&
(error.code === "EPERM" || error.code === "EACCES")
) {
listenBlocked = true;
return;
}
throw error;
}
const boundServer = server;
if (!boundServer) {
return;
}
port = (boundServer.address() as AddressInfo).port;
});
afterAll(async () => {
await new Promise((r) => server.close(r));
const boundServer = server;
if (boundServer) {
await new Promise((r) => boundServer.close(r));
}
await fs.rm(MEDIA_DIR, { recursive: true, force: true });
MEDIA_DIR = "";
});
@@ -140,6 +160,9 @@ describe("media server", () => {
assertAfterFetch: expectMissingMediaFile,
},
] as const)("$name", async (testCase) => {
if (listenBlocked) {
return;
}
await expectMediaFileLifecycleCase(testCase);
});
@@ -199,6 +222,9 @@ describe("media server", () => {
expectedBody: "invalid path",
},
] as const)("%#", async (testCase) => {
if (listenBlocked) {
return;
}
await expectFetchedMediaCase(testCase);
});
});