test: add fetch mock helper and reaction coverage

This commit is contained in:
Sebastian
2026-02-17 09:01:30 -05:00
parent 0e023e300e
commit cc359d338e
28 changed files with 193 additions and 106 deletions

View File

@@ -1,4 +1,5 @@
import { describe, expect, it } from "vitest";
import { withFetchPreconnect } from "../test-utils/fetch-mock.js";
import {
CHUTES_TOKEN_ENDPOINT,
CHUTES_USERINFO_ENDPOINT,
@@ -15,7 +16,7 @@ const urlToString = (url: Request | URL | string): string => {
describe("chutes-oauth", () => {
it("exchanges code for tokens and stores username as email", async () => {
const fetchFn: typeof fetch = async (input, init) => {
const fetchFn = withFetchPreconnect(async (input, init) => {
const url = urlToString(input);
if (url === CHUTES_TOKEN_ENDPOINT) {
expect(init?.method).toBe("POST");
@@ -41,7 +42,7 @@ describe("chutes-oauth", () => {
});
}
return new Response("not found", { status: 404 });
};
});
const now = 1_000_000;
const creds = await exchangeChutesCodeForTokens({
@@ -65,7 +66,7 @@ describe("chutes-oauth", () => {
});
it("refreshes tokens using stored client id and falls back to old refresh token", async () => {
const fetchFn: typeof fetch = async (input, init) => {
const fetchFn = withFetchPreconnect(async (input, init) => {
const url = urlToString(input);
if (url !== CHUTES_TOKEN_ENDPOINT) {
return new Response("not found", { status: 404 });
@@ -82,7 +83,7 @@ describe("chutes-oauth", () => {
}),
{ status: 200, headers: { "Content-Type": "application/json" } },
);
};
});
const now = 2_000_000;
const refreshed = await refreshChutesTokens({

View File

@@ -1,4 +1,5 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import { withFetchPreconnect } from "../test-utils/fetch-mock.js";
describe("minimaxUnderstandImage apiKey normalization", () => {
const priorFetch = global.fetch;
@@ -21,7 +22,7 @@ describe("minimaxUnderstandImage apiKey normalization", () => {
{ status: 200, headers: { "Content-Type": "application/json" } },
);
});
global.fetch = fetchSpy;
global.fetch = withFetchPreconnect(fetchSpy);
const { minimaxUnderstandImage } = await import("./minimax-vlm.js");
const text = await minimaxUnderstandImage({

View File

@@ -1,13 +1,16 @@
import { describe, expect, it } from "vitest";
import { captureEnv } from "../test-utils/env.js";
import { withFetchPreconnect } from "../test-utils/fetch-mock.js";
import { scanOpenRouterModels } from "./model-scan.js";
function createFetchFixture(payload: unknown): typeof fetch {
return async () =>
new Response(JSON.stringify(payload), {
status: 200,
headers: { "content-type": "application/json" },
});
return withFetchPreconnect(
async () =>
new Response(JSON.stringify(payload), {
status: 200,
headers: { "content-type": "application/json" },
}),
);
}
describe("scanOpenRouterModels", () => {

View File

@@ -4,6 +4,7 @@ import path from "node:path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import type { OpenClawConfig } from "../../config/config.js";
import type { ModelDefinitionConfig } from "../../config/types.models.js";
import { withFetchPreconnect } from "../../test-utils/fetch-mock.js";
import { createOpenClawCodingTools } from "../pi-tools.js";
import { createHostSandboxFsBridge } from "../test-helpers/host-sandbox-fs-bridge.js";
import { __testing, createImageTool, resolveImageModelConfigForTool } from "./image-tool.js";
@@ -47,7 +48,7 @@ function stubMinimaxOkFetch() {
base_resp: { status_code: 0, status_msg: "" },
}),
});
global.fetch = fetch;
global.fetch = withFetchPreconnect(fetch);
vi.stubEnv("MINIMAX_API_KEY", "minimax-test");
return fetch;
}
@@ -414,7 +415,7 @@ describe("image tool implicit imageModel config", () => {
base_resp: { status_code: 0, status_msg: "" },
}),
});
global.fetch = fetch;
global.fetch = withFetchPreconnect(fetch);
vi.stubEnv("MINIMAX_API_KEY", "minimax-test");
const cfg: OpenClawConfig = {
@@ -487,7 +488,7 @@ describe("image tool MiniMax VLM routing", () => {
base_resp: baseResp,
}),
});
global.fetch = fetch;
global.fetch = withFetchPreconnect(fetch);
const agentDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-minimax-vlm-"));
vi.stubEnv("MINIMAX_API_KEY", "minimax-test");

View File

@@ -1,5 +1,6 @@
import { describe, expect, it, vi } from "vitest";
import * as logger from "../../logger.js";
import { withFetchPreconnect } from "../../test-utils/fetch-mock.js";
import {
createBaseWebFetchToolConfig,
installWebFetchSsrfHarness,
@@ -37,7 +38,7 @@ function htmlResponse(body: string): Response {
describe("web_fetch Cloudflare Markdown for Agents", () => {
it("sends Accept header preferring text/markdown", async () => {
const fetchSpy = vi.fn().mockResolvedValue(markdownResponse("# Test Page\n\nHello world."));
global.fetch = fetchSpy;
global.fetch = withFetchPreconnect(fetchSpy);
const tool = createWebFetchTool(baseToolConfig);
@@ -51,7 +52,7 @@ describe("web_fetch Cloudflare Markdown for Agents", () => {
it("uses cf-markdown extractor for text/markdown responses", async () => {
const md = "# CF Markdown\n\nThis is server-rendered markdown.";
const fetchSpy = vi.fn().mockResolvedValue(markdownResponse(md));
global.fetch = fetchSpy;
global.fetch = withFetchPreconnect(fetchSpy);
const tool = createWebFetchTool(baseToolConfig);
@@ -73,7 +74,7 @@ describe("web_fetch Cloudflare Markdown for Agents", () => {
const html =
"<html><body><article><h1>HTML Page</h1><p>Content here.</p></article></body></html>";
const fetchSpy = vi.fn().mockResolvedValue(htmlResponse(html));
global.fetch = fetchSpy;
global.fetch = withFetchPreconnect(fetchSpy);
const tool = createWebFetchTool(baseToolConfig);
@@ -88,7 +89,7 @@ describe("web_fetch Cloudflare Markdown for Agents", () => {
const fetchSpy = vi
.fn()
.mockResolvedValue(markdownResponse("# Tokens Test", { "x-markdown-tokens": "1500" }));
global.fetch = fetchSpy;
global.fetch = withFetchPreconnect(fetchSpy);
const tool = createWebFetchTool(baseToolConfig);
@@ -108,7 +109,7 @@ describe("web_fetch Cloudflare Markdown for Agents", () => {
it("converts markdown to text when extractMode is text", async () => {
const md = "# Heading\n\n**Bold text** and [a link](https://example.com).";
const fetchSpy = vi.fn().mockResolvedValue(markdownResponse(md));
global.fetch = fetchSpy;
global.fetch = withFetchPreconnect(fetchSpy);
const tool = createWebFetchTool(baseToolConfig);
@@ -132,7 +133,7 @@ describe("web_fetch Cloudflare Markdown for Agents", () => {
it("does not log x-markdown-tokens when header is absent", async () => {
const logSpy = vi.spyOn(logger, "logDebug").mockImplementation(() => {});
const fetchSpy = vi.fn().mockResolvedValue(markdownResponse("# No tokens"));
global.fetch = fetchSpy;
global.fetch = withFetchPreconnect(fetchSpy);
const tool = createWebFetchTool(baseToolConfig);

View File

@@ -1,4 +1,5 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import { withFetchPreconnect } from "../../test-utils/fetch-mock.js";
vi.mock("../../infra/net/fetch-guard.js", () => {
return {
@@ -33,7 +34,7 @@ describe("web_fetch firecrawl apiKey normalization", () => {
);
});
global.fetch = fetchSpy;
global.fetch = withFetchPreconnect(fetchSpy);
const { createWebFetchTool } = await import("./web-tools.js");
const tool = createWebFetchTool({

View File

@@ -1,4 +1,5 @@
import { describe, expect, it, vi } from "vitest";
import { withFetchPreconnect } from "../../test-utils/fetch-mock.js";
import {
createBaseWebFetchToolConfig,
installWebFetchSsrfHarness,
@@ -23,7 +24,7 @@ describe("web_fetch response size limits", () => {
});
const fetchSpy = vi.fn().mockResolvedValue(response);
global.fetch = fetchSpy;
global.fetch = withFetchPreconnect(fetchSpy);
const tool = createWebFetchTool(baseToolConfig);
const result = await tool?.execute?.("call", { url: "https://example.com/stream" });

View File

@@ -1,5 +1,6 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import * as ssrf from "../../infra/net/ssrf.js";
import { withFetchPreconnect } from "../../test-utils/fetch-mock.js";
const lookupMock = vi.fn();
const resolvePinnedHostname = ssrf.resolvePinnedHostname;
@@ -30,7 +31,7 @@ function textResponse(body: string): Response {
function setMockFetch(impl?: (...args: unknown[]) => unknown) {
const fetchSpy = vi.fn(impl);
global.fetch = fetchSpy as typeof fetch;
global.fetch = withFetchPreconnect(fetchSpy);
return fetchSpy;
}

View File

@@ -1,4 +1,5 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { withFetchPreconnect } from "../../test-utils/fetch-mock.js";
import { createWebFetchTool, createWebSearchTool } from "./web-tools.js";
function installMockFetch(payload: unknown) {
@@ -8,7 +9,7 @@ function installMockFetch(payload: unknown) {
json: () => Promise.resolve(payload),
} as Response),
);
global.fetch = mockFetch;
global.fetch = withFetchPreconnect(mockFetch);
return mockFetch;
}

View File

@@ -1,5 +1,6 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import * as ssrf from "../../infra/net/ssrf.js";
import { withFetchPreconnect } from "../../test-utils/fetch-mock.js";
import { createWebFetchTool } from "./web-tools.js";
type MockResponse = {
@@ -92,7 +93,7 @@ function requestUrl(input: RequestInfo | URL): string {
function installMockFetch(impl: (input: RequestInfo | URL) => Promise<Response>) {
const mockFetch = vi.fn(async (input: RequestInfo | URL) => await impl(input));
global.fetch = mockFetch as typeof global.fetch;
global.fetch = withFetchPreconnect(mockFetch);
return mockFetch;
}