mirror of
https://github.com/moltbot/moltbot.git
synced 2026-03-09 15:35:17 +00:00
test: consolidate redundant suites and speed up timers
This commit is contained in:
@@ -1,14 +0,0 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { isModernModelRef } from "./live-model-filter.js";
|
||||
|
||||
describe("isModernModelRef", () => {
|
||||
it("excludes opencode minimax variants from modern selection", () => {
|
||||
expect(isModernModelRef({ provider: "opencode", id: "minimax-m2.1" })).toBe(false);
|
||||
expect(isModernModelRef({ provider: "opencode", id: "minimax-m2.5" })).toBe(false);
|
||||
});
|
||||
|
||||
it("keeps non-minimax opencode modern models", () => {
|
||||
expect(isModernModelRef({ provider: "opencode", id: "claude-opus-4-6" })).toBe(true);
|
||||
expect(isModernModelRef({ provider: "opencode", id: "gemini-3-pro" })).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -1,21 +0,0 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { loadModelCatalog } from "./model-catalog.js";
|
||||
import {
|
||||
installModelCatalogTestHooks,
|
||||
mockCatalogImportFailThenRecover,
|
||||
} from "./model-catalog.test-harness.js";
|
||||
|
||||
describe("loadModelCatalog e2e smoke", () => {
|
||||
installModelCatalogTestHooks();
|
||||
|
||||
it("recovers after an import failure on the next load", async () => {
|
||||
mockCatalogImportFailThenRecover();
|
||||
|
||||
const cfg = {} as OpenClawConfig;
|
||||
expect(await loadModelCatalog({ config: cfg })).toEqual([]);
|
||||
expect(await loadModelCatalog({ config: cfg })).toEqual([
|
||||
{ id: "gpt-4.1", name: "GPT-4.1", provider: "openai" },
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { Api, Model } from "@mariozechner/pi-ai";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { isModernModelRef } from "./live-model-filter.js";
|
||||
import { normalizeModelCompat } from "./model-compat.js";
|
||||
|
||||
const baseModel = (): Model<Api> =>
|
||||
@@ -46,3 +47,15 @@ describe("normalizeModelCompat", () => {
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("isModernModelRef", () => {
|
||||
it("excludes opencode minimax variants from modern selection", () => {
|
||||
expect(isModernModelRef({ provider: "opencode", id: "minimax-m2.1" })).toBe(false);
|
||||
expect(isModernModelRef({ provider: "opencode", id: "minimax-m2.5" })).toBe(false);
|
||||
});
|
||||
|
||||
it("keeps non-minimax opencode modern models", () => {
|
||||
expect(isModernModelRef({ provider: "opencode", id: "claude-opus-4-6" })).toBe(true);
|
||||
expect(isModernModelRef({ provider: "opencode", id: "gemini-3-pro" })).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -616,7 +616,6 @@ describe("Agent-specific tool filtering", () => {
|
||||
|
||||
const result = await execTool!.execute("call-implicit-sandbox-default", {
|
||||
command: "echo done",
|
||||
yieldMs: 10,
|
||||
});
|
||||
const details = result?.details as { status?: string } | undefined;
|
||||
expect(details?.status).toBe("completed");
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { withFetchPreconnect } from "../../test-utils/fetch-mock.js";
|
||||
|
||||
vi.mock("../../infra/net/fetch-guard.js", () => {
|
||||
return {
|
||||
fetchWithSsrFGuard: vi.fn(async () => {
|
||||
throw new Error("network down");
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
describe("web_fetch firecrawl apiKey normalization", () => {
|
||||
const priorFetch = global.fetch;
|
||||
|
||||
afterEach(() => {
|
||||
global.fetch = priorFetch;
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("strips embedded CR/LF before sending Authorization header", async () => {
|
||||
const fetchSpy = vi.fn(async (input: RequestInfo | URL, init?: RequestInit) => {
|
||||
const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : "";
|
||||
expect(url).toContain("/v2/scrape");
|
||||
|
||||
const auth = (init?.headers as Record<string, string> | undefined)?.Authorization;
|
||||
expect(auth).toBe("Bearer firecrawl-test-key");
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
success: true,
|
||||
data: { markdown: "ok", metadata: { title: "t" } },
|
||||
}),
|
||||
{ status: 200, headers: { "Content-Type": "application/json" } },
|
||||
);
|
||||
});
|
||||
|
||||
global.fetch = withFetchPreconnect(fetchSpy);
|
||||
|
||||
const { createWebFetchTool } = await import("./web-tools.js");
|
||||
const tool = createWebFetchTool({
|
||||
config: {
|
||||
tools: {
|
||||
web: {
|
||||
fetch: {
|
||||
cacheTtlMinutes: 0,
|
||||
firecrawl: { apiKey: "firecrawl-test-\r\nkey" },
|
||||
readability: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const result = await tool?.execute?.("call", {
|
||||
url: "https://example.com",
|
||||
extractMode: "text",
|
||||
});
|
||||
expect(result?.details).toMatchObject({ extractor: "firecrawl" });
|
||||
expect(fetchSpy).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -91,8 +91,12 @@ function requestUrl(input: RequestInfo | URL): string {
|
||||
return "";
|
||||
}
|
||||
|
||||
function installMockFetch(impl: (input: RequestInfo | URL) => Promise<Response>) {
|
||||
const mockFetch = vi.fn(async (input: RequestInfo | URL) => await impl(input));
|
||||
function installMockFetch(
|
||||
impl: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>,
|
||||
) {
|
||||
const mockFetch = vi.fn(
|
||||
async (input: RequestInfo | URL, init?: RequestInit) => await impl(input, init),
|
||||
);
|
||||
global.fetch = withFetchPreconnect(mockFetch);
|
||||
return mockFetch;
|
||||
}
|
||||
@@ -253,6 +257,36 @@ describe("web_fetch extraction fallbacks", () => {
|
||||
expect(details.text).toContain("firecrawl content");
|
||||
});
|
||||
|
||||
it("normalizes firecrawl Authorization header values", async () => {
|
||||
const fetchSpy = installMockFetch((input: RequestInfo | URL) => {
|
||||
const url = requestUrl(input);
|
||||
if (url.includes("api.firecrawl.dev/v2/scrape")) {
|
||||
return Promise.resolve(firecrawlResponse("firecrawl normalized")) as Promise<Response>;
|
||||
}
|
||||
return Promise.resolve(
|
||||
htmlResponse("<!doctype html><html><head></head><body></body></html>", url),
|
||||
) as Promise<Response>;
|
||||
});
|
||||
|
||||
const tool = createFetchTool({
|
||||
firecrawl: { apiKey: "firecrawl-test-\r\nkey" },
|
||||
});
|
||||
|
||||
const result = await tool?.execute?.("call", {
|
||||
url: "https://example.com/firecrawl",
|
||||
extractMode: "text",
|
||||
});
|
||||
|
||||
expect(result?.details).toMatchObject({ extractor: "firecrawl" });
|
||||
const firecrawlCall = fetchSpy.mock.calls.find((call) =>
|
||||
requestUrl(call[0]).includes("/v2/scrape"),
|
||||
);
|
||||
expect(firecrawlCall).toBeTruthy();
|
||||
const init = firecrawlCall?.[1];
|
||||
const authHeader = new Headers(init?.headers).get("Authorization");
|
||||
expect(authHeader).toBe("Bearer firecrawl-test-key");
|
||||
});
|
||||
|
||||
it("throws when readability is disabled and firecrawl is unavailable", async () => {
|
||||
installMockFetch(
|
||||
(input: RequestInfo | URL) =>
|
||||
|
||||
@@ -1,108 +0,0 @@
|
||||
import { spawn } from "node:child_process";
|
||||
import path from "node:path";
|
||||
import process from "node:process";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { attachChildProcessBridge } from "./child-process-bridge.js";
|
||||
|
||||
const CHILD_READY_TIMEOUT_MS = 10_000;
|
||||
const CHILD_EXIT_TIMEOUT_MS = 10_000;
|
||||
|
||||
function waitForLine(
|
||||
stream: NodeJS.ReadableStream,
|
||||
timeoutMs = CHILD_READY_TIMEOUT_MS,
|
||||
): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
let buffer = "";
|
||||
|
||||
const timeout = setTimeout(() => {
|
||||
cleanup();
|
||||
reject(new Error("timeout waiting for line"));
|
||||
}, timeoutMs);
|
||||
|
||||
const onData = (chunk: Buffer | string): void => {
|
||||
buffer += chunk.toString();
|
||||
const idx = buffer.indexOf("\n");
|
||||
if (idx >= 0) {
|
||||
const line = buffer.slice(0, idx).trim();
|
||||
cleanup();
|
||||
resolve(line);
|
||||
}
|
||||
};
|
||||
|
||||
const onError = (err: unknown): void => {
|
||||
cleanup();
|
||||
reject(err);
|
||||
};
|
||||
|
||||
const cleanup = (): void => {
|
||||
clearTimeout(timeout);
|
||||
stream.off("data", onData);
|
||||
stream.off("error", onError);
|
||||
};
|
||||
|
||||
stream.on("data", onData);
|
||||
stream.on("error", onError);
|
||||
});
|
||||
}
|
||||
|
||||
describe("attachChildProcessBridge", () => {
|
||||
const children: Array<{ kill: (signal?: NodeJS.Signals) => boolean }> = [];
|
||||
const detachments: Array<() => void> = [];
|
||||
|
||||
afterEach(() => {
|
||||
for (const detach of detachments) {
|
||||
try {
|
||||
detach();
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
detachments.length = 0;
|
||||
for (const child of children) {
|
||||
try {
|
||||
child.kill("SIGKILL");
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
children.length = 0;
|
||||
});
|
||||
|
||||
it("forwards SIGTERM to the wrapped child", async () => {
|
||||
const childPath = path.resolve(process.cwd(), "test/fixtures/child-process-bridge/child.js");
|
||||
|
||||
const beforeSigterm = new Set(process.listeners("SIGTERM"));
|
||||
const child = spawn(process.execPath, [childPath], {
|
||||
stdio: ["ignore", "pipe", "inherit"],
|
||||
env: process.env,
|
||||
});
|
||||
const { detach } = attachChildProcessBridge(child);
|
||||
detachments.push(detach);
|
||||
children.push(child);
|
||||
const afterSigterm = process.listeners("SIGTERM");
|
||||
const addedSigterm = afterSigterm.find((listener) => !beforeSigterm.has(listener));
|
||||
|
||||
if (!child.stdout) {
|
||||
throw new Error("expected stdout");
|
||||
}
|
||||
const ready = await waitForLine(child.stdout);
|
||||
expect(ready).toBe("ready");
|
||||
|
||||
// Simulate systemd sending SIGTERM to the parent process.
|
||||
if (!addedSigterm) {
|
||||
throw new Error("expected SIGTERM listener");
|
||||
}
|
||||
addedSigterm("SIGTERM");
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const timeout = setTimeout(
|
||||
() => reject(new Error("timeout waiting for child exit")),
|
||||
CHILD_EXIT_TIMEOUT_MS,
|
||||
);
|
||||
child.once("exit", () => {
|
||||
clearTimeout(timeout);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}, 8_000);
|
||||
});
|
||||
@@ -1,7 +1,52 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { spawn } from "node:child_process";
|
||||
import path from "node:path";
|
||||
import process from "node:process";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { withEnvAsync } from "../test-utils/env.js";
|
||||
import { attachChildProcessBridge } from "./child-process-bridge.js";
|
||||
import { runCommandWithTimeout, shouldSpawnWithShell } from "./exec.js";
|
||||
|
||||
const CHILD_READY_TIMEOUT_MS = 4_000;
|
||||
const CHILD_EXIT_TIMEOUT_MS = 4_000;
|
||||
|
||||
function waitForLine(
|
||||
stream: NodeJS.ReadableStream,
|
||||
timeoutMs = CHILD_READY_TIMEOUT_MS,
|
||||
): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
let buffer = "";
|
||||
|
||||
const timeout = setTimeout(() => {
|
||||
cleanup();
|
||||
reject(new Error("timeout waiting for line"));
|
||||
}, timeoutMs);
|
||||
|
||||
const onData = (chunk: Buffer | string): void => {
|
||||
buffer += chunk.toString();
|
||||
const idx = buffer.indexOf("\n");
|
||||
if (idx >= 0) {
|
||||
const line = buffer.slice(0, idx).trim();
|
||||
cleanup();
|
||||
resolve(line);
|
||||
}
|
||||
};
|
||||
|
||||
const onError = (err: unknown): void => {
|
||||
cleanup();
|
||||
reject(err);
|
||||
};
|
||||
|
||||
const cleanup = (): void => {
|
||||
clearTimeout(timeout);
|
||||
stream.off("data", onData);
|
||||
stream.off("error", onError);
|
||||
};
|
||||
|
||||
stream.on("data", onData);
|
||||
stream.on("error", onError);
|
||||
});
|
||||
}
|
||||
|
||||
describe("runCommandWithTimeout", () => {
|
||||
it("never enables shell execution (Windows cmd.exe injection hardening)", () => {
|
||||
expect(
|
||||
@@ -56,16 +101,16 @@ describe("runCommandWithTimeout", () => {
|
||||
"let count = 0;",
|
||||
'const ticker = setInterval(() => { process.stdout.write(".");',
|
||||
"count += 1;",
|
||||
"if (count === 4) {",
|
||||
"if (count === 2) {",
|
||||
"clearInterval(ticker);",
|
||||
"process.exit(0);",
|
||||
"}",
|
||||
"}, 60);",
|
||||
"}, 40);",
|
||||
].join(" "),
|
||||
],
|
||||
{
|
||||
timeoutMs: 5_000,
|
||||
noOutputTimeoutMs: 250,
|
||||
noOutputTimeoutMs: 500,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -73,7 +118,7 @@ describe("runCommandWithTimeout", () => {
|
||||
expect(result.code ?? 0).toBe(0);
|
||||
expect(result.termination).toBe("exit");
|
||||
expect(result.noOutputTimedOut).toBe(false);
|
||||
expect(result.stdout.length).toBeGreaterThanOrEqual(5);
|
||||
expect(result.stdout.length).toBeGreaterThanOrEqual(3);
|
||||
});
|
||||
|
||||
it("reports global timeout termination when overall timeout elapses", async () => {
|
||||
@@ -89,3 +134,64 @@ describe("runCommandWithTimeout", () => {
|
||||
expect(result.code).not.toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("attachChildProcessBridge", () => {
|
||||
const children: Array<{ kill: (signal?: NodeJS.Signals) => boolean }> = [];
|
||||
const detachments: Array<() => void> = [];
|
||||
|
||||
afterEach(() => {
|
||||
for (const detach of detachments) {
|
||||
try {
|
||||
detach();
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
detachments.length = 0;
|
||||
for (const child of children) {
|
||||
try {
|
||||
child.kill("SIGKILL");
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
children.length = 0;
|
||||
});
|
||||
|
||||
it("forwards SIGTERM to the wrapped child", async () => {
|
||||
const childPath = path.resolve(process.cwd(), "test/fixtures/child-process-bridge/child.js");
|
||||
|
||||
const beforeSigterm = new Set(process.listeners("SIGTERM"));
|
||||
const child = spawn(process.execPath, [childPath], {
|
||||
stdio: ["ignore", "pipe", "inherit"],
|
||||
env: process.env,
|
||||
});
|
||||
const { detach } = attachChildProcessBridge(child);
|
||||
detachments.push(detach);
|
||||
children.push(child);
|
||||
const afterSigterm = process.listeners("SIGTERM");
|
||||
const addedSigterm = afterSigterm.find((listener) => !beforeSigterm.has(listener));
|
||||
|
||||
if (!child.stdout) {
|
||||
throw new Error("expected stdout");
|
||||
}
|
||||
const ready = await waitForLine(child.stdout);
|
||||
expect(ready).toBe("ready");
|
||||
|
||||
if (!addedSigterm) {
|
||||
throw new Error("expected SIGTERM listener");
|
||||
}
|
||||
addedSigterm("SIGTERM");
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const timeout = setTimeout(
|
||||
() => reject(new Error("timeout waiting for child exit")),
|
||||
CHILD_EXIT_TIMEOUT_MS,
|
||||
);
|
||||
child.once("exit", () => {
|
||||
clearTimeout(timeout);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { markdownToSlackMrkdwn } from "./format.js";
|
||||
import { escapeSlackMrkdwn } from "./monitor/mrkdwn.js";
|
||||
|
||||
describe("markdownToSlackMrkdwn", () => {
|
||||
it("handles core markdown formatting conversions", () => {
|
||||
@@ -57,3 +58,13 @@ describe("markdownToSlackMrkdwn", () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("escapeSlackMrkdwn", () => {
|
||||
it("returns plain text unchanged", () => {
|
||||
expect(escapeSlackMrkdwn("heartbeat status ok")).toBe("heartbeat status ok");
|
||||
});
|
||||
|
||||
it("escapes slack and mrkdwn control characters", () => {
|
||||
expect(escapeSlackMrkdwn("mode_*`~<&>\\")).toBe("mode\\_\\*\\`\\~<&>\\\\");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { escapeSlackMrkdwn } from "./mrkdwn.js";
|
||||
|
||||
describe("escapeSlackMrkdwn", () => {
|
||||
it("returns plain text unchanged", () => {
|
||||
expect(escapeSlackMrkdwn("heartbeat status ok")).toBe("heartbeat status ok");
|
||||
});
|
||||
|
||||
it("escapes slack and mrkdwn control characters", () => {
|
||||
expect(escapeSlackMrkdwn("mode_*`~<&>\\")).toBe("mode\\_\\*\\`\\~<&>\\\\");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user