diff --git a/src/agents/tools/web-fetch.ssrf.e2e.test.ts b/src/agents/tools/web-fetch.ssrf.e2e.test.ts index e61b56952b2..9a02821cb7f 100644 --- a/src/agents/tools/web-fetch.ssrf.e2e.test.ts +++ b/src/agents/tools/web-fetch.ssrf.e2e.test.ts @@ -1,6 +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 { type FetchMock, withFetchPreconnect } from "../../test-utils/fetch-mock.js"; const lookupMock = vi.fn(); const resolvePinnedHostname = ssrf.resolvePinnedHostname; @@ -29,8 +29,10 @@ function textResponse(body: string): Response { } as unknown as Response; } -function setMockFetch(impl?: (...args: unknown[]) => unknown) { - const fetchSpy = vi.fn(impl); +function setMockFetch( + impl: FetchMock = async (_input: RequestInfo | URL, _init?: RequestInit) => textResponse(""), +) { + const fetchSpy = vi.fn(impl); global.fetch = withFetchPreconnect(fetchSpy); return fetchSpy; } diff --git a/src/agents/tools/web-tools.enabled-defaults.e2e.test.ts b/src/agents/tools/web-tools.enabled-defaults.e2e.test.ts index b6f2f2f24ef..e8ca5a00092 100644 --- a/src/agents/tools/web-tools.enabled-defaults.e2e.test.ts +++ b/src/agents/tools/web-tools.enabled-defaults.e2e.test.ts @@ -216,7 +216,7 @@ describe("web_search external content wrapping", () => { it("wraps Brave result descriptions", async () => { vi.stubEnv("BRAVE_API_KEY", "test-key"); - const mockFetch = vi.fn(() => + const mockFetch = vi.fn(async (_input: RequestInfo | URL, _init?: RequestInit) => Promise.resolve({ ok: true, json: () => @@ -233,7 +233,7 @@ describe("web_search external content wrapping", () => { }), } as Response), ); - global.fetch = mockFetch; + global.fetch = withFetchPreconnect(mockFetch); const tool = createWebSearchTool({ config: undefined, sandboxed: true }); const result = await tool?.execute?.("call-1", { query: "test" }); @@ -254,7 +254,7 @@ describe("web_search external content wrapping", () => { it("does not wrap Brave result urls (raw for tool chaining)", async () => { vi.stubEnv("BRAVE_API_KEY", "test-key"); const url = "https://example.com/some-page"; - const mockFetch = vi.fn(() => + const mockFetch = vi.fn(async (_input: RequestInfo | URL, _init?: RequestInit) => Promise.resolve({ ok: true, json: () => @@ -271,7 +271,7 @@ describe("web_search external content wrapping", () => { }), } as Response), ); - global.fetch = mockFetch; + global.fetch = withFetchPreconnect(mockFetch); const tool = createWebSearchTool({ config: undefined, sandboxed: true }); const result = await tool?.execute?.("call-1", { query: "unique-test-url-not-wrapped" }); @@ -284,7 +284,7 @@ describe("web_search external content wrapping", () => { it("does not wrap Brave site names", async () => { vi.stubEnv("BRAVE_API_KEY", "test-key"); - const mockFetch = vi.fn(() => + const mockFetch = vi.fn(async (_input: RequestInfo | URL, _init?: RequestInit) => Promise.resolve({ ok: true, json: () => @@ -301,7 +301,7 @@ describe("web_search external content wrapping", () => { }), } as Response), ); - global.fetch = mockFetch; + global.fetch = withFetchPreconnect(mockFetch); const tool = createWebSearchTool({ config: undefined, sandboxed: true }); const result = await tool?.execute?.("call-1", { query: "unique-test-site-name-wrapping" }); @@ -313,7 +313,7 @@ describe("web_search external content wrapping", () => { it("does not wrap Brave published ages", async () => { vi.stubEnv("BRAVE_API_KEY", "test-key"); - const mockFetch = vi.fn(() => + const mockFetch = vi.fn(async (_input: RequestInfo | URL, _init?: RequestInit) => Promise.resolve({ ok: true, json: () => @@ -331,7 +331,7 @@ describe("web_search external content wrapping", () => { }), } as Response), ); - global.fetch = mockFetch; + global.fetch = withFetchPreconnect(mockFetch); const tool = createWebSearchTool({ config: undefined, sandboxed: true }); const result = await tool?.execute?.("call-1", { @@ -345,7 +345,7 @@ describe("web_search external content wrapping", () => { it("wraps Perplexity content", async () => { vi.stubEnv("PERPLEXITY_API_KEY", "pplx-test"); - const mockFetch = vi.fn(() => + const mockFetch = vi.fn(async (_input: RequestInfo | URL, _init?: RequestInit) => Promise.resolve({ ok: true, json: () => @@ -355,7 +355,7 @@ describe("web_search external content wrapping", () => { }), } as Response), ); - global.fetch = mockFetch; + global.fetch = withFetchPreconnect(mockFetch); const tool = createWebSearchTool({ config: { tools: { web: { search: { provider: "perplexity" } } } }, @@ -371,7 +371,7 @@ describe("web_search external content wrapping", () => { it("does not wrap Perplexity citations (raw for tool chaining)", async () => { vi.stubEnv("PERPLEXITY_API_KEY", "pplx-test"); const citation = "https://example.com/some-article"; - const mockFetch = vi.fn((_input?: unknown, _init?: unknown) => + const mockFetch = vi.fn(async (_input: RequestInfo | URL, _init?: RequestInit) => Promise.resolve({ ok: true, json: () => @@ -381,7 +381,7 @@ describe("web_search external content wrapping", () => { }), } as Response), ); - global.fetch = mockFetch; + global.fetch = withFetchPreconnect(mockFetch); const tool = createWebSearchTool({ config: { tools: { web: { search: { provider: "perplexity" } } } }, diff --git a/src/commands/chutes-oauth.e2e.test.ts b/src/commands/chutes-oauth.e2e.test.ts index 812f0ba0a52..afe533ca226 100644 --- a/src/commands/chutes-oauth.e2e.test.ts +++ b/src/commands/chutes-oauth.e2e.test.ts @@ -32,7 +32,7 @@ function createOAuthFetchFn(params: { refreshToken: string; username: string; passthrough?: boolean; -}): typeof fetch { +}) { return withFetchPreconnect(async (input, init) => { const url = urlToString(input); if (url === CHUTES_TOKEN_ENDPOINT) { diff --git a/src/memory/embeddings-voyage.test.ts b/src/memory/embeddings-voyage.test.ts index 65b6e4306ed..cdde6ace88e 100644 --- a/src/memory/embeddings-voyage.test.ts +++ b/src/memory/embeddings-voyage.test.ts @@ -1,6 +1,6 @@ import { afterEach, describe, expect, it, vi } from "vitest"; import * as authModule from "../agents/model-auth.js"; -import { withFetchPreconnect } from "../test-utils/fetch-mock.js"; +import { type FetchMock, withFetchPreconnect } from "../test-utils/fetch-mock.js"; import { createVoyageEmbeddingProvider, normalizeVoyageModel } from "./embeddings-voyage.js"; vi.mock("../agents/model-auth.js", () => ({ @@ -14,13 +14,14 @@ vi.mock("../agents/model-auth.js", () => ({ })); const createFetchMock = () => { - const fetchMock = vi.fn(async (_input: RequestInfo | URL, _init?: RequestInit) => { - return new Response(JSON.stringify({ data: [{ embedding: [0.1, 0.2, 0.3] }] }), { - status: 200, - headers: { "Content-Type": "application/json" }, - }); - }); - return withFetchPreconnect(fetchMock) as typeof fetch & typeof fetchMock; + const fetchMock = vi.fn( + async (_input: RequestInfo | URL, _init?: RequestInit) => + new Response(JSON.stringify({ data: [{ embedding: [0.1, 0.2, 0.3] }] }), { + status: 200, + headers: { "Content-Type": "application/json" }, + }), + ); + return withFetchPreconnect(fetchMock); }; describe("voyage embedding provider", () => { @@ -95,6 +96,7 @@ describe("voyage embedding provider", () => { it("passes input_type=document for embedBatch", async () => { const fetchMock = withFetchPreconnect( +<<<<<<< HEAD vi.fn(async (_input: RequestInfo | URL, _init?: RequestInit) => { return new Response( JSON.stringify({ @@ -106,6 +108,17 @@ describe("voyage embedding provider", () => { }, ); }), +======= + vi.fn( + async (_input: RequestInfo | URL, _init?: RequestInit) => + new Response( + JSON.stringify({ + data: [{ embedding: [0.1, 0.2] }, { embedding: [0.3, 0.4] }], + }), + { status: 200, headers: { "Content-Type": "application/json" } }, + ), + ), +>>>>>>> 03a725142 (test(fetch): align mocks openclaw#19194 thanks @sebslight) ); vi.stubGlobal("fetch", fetchMock); diff --git a/src/slack/monitor/media.test.ts b/src/slack/monitor/media.test.ts index 05b30615e84..b3d0bcb51b7 100644 --- a/src/slack/monitor/media.test.ts +++ b/src/slack/monitor/media.test.ts @@ -2,7 +2,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import * as ssrf from "../../infra/net/ssrf.js"; import type { SavedMedia } from "../../media/store.js"; import * as mediaStore from "../../media/store.js"; -import { withFetchPreconnect } from "../../test-utils/fetch-mock.js"; +import { type FetchMock, withFetchPreconnect } from "../../test-utils/fetch-mock.js"; import { fetchWithSlackAuth, resolveSlackAttachmentContent, @@ -12,7 +12,7 @@ import { // Store original fetch const originalFetch = globalThis.fetch; -let mockFetch: ReturnType; +let mockFetch: ReturnType>; const createSavedMedia = (filePath: string, contentType: string): SavedMedia => ({ id: "saved-media-id", path: filePath, @@ -23,7 +23,9 @@ const createSavedMedia = (filePath: string, contentType: string): SavedMedia => describe("fetchWithSlackAuth", () => { beforeEach(() => { // Create a new mock for each test - mockFetch = vi.fn(); + mockFetch = vi.fn( + async (_input: RequestInfo | URL, _init?: RequestInit) => new Response(), + ); globalThis.fetch = withFetchPreconnect(mockFetch); }); @@ -366,8 +368,9 @@ describe("resolveSlackMedia", () => { return createSavedMedia("/tmp/unknown", "application/octet-stream"); }); - mockFetch.mockImplementation(async (input) => { - const url = String(input); + mockFetch.mockImplementation(async (input: RequestInfo | URL) => { + const url = + typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url; if (url.includes("/a.jpg")) { return new Response(Buffer.from("image a"), { status: 200, diff --git a/src/test-utils/fetch-mock.ts b/src/test-utils/fetch-mock.ts index 7eec83cd231..430a110431f 100644 --- a/src/test-utils/fetch-mock.ts +++ b/src/test-utils/fetch-mock.ts @@ -1,5 +1,14 @@ +export type FetchMock = (input: RequestInfo | URL, init?: RequestInit) => Promise; + +type FetchPreconnectOptions = { + dns?: boolean; + tcp?: boolean; + http?: boolean; + https?: boolean; +}; + type FetchWithPreconnect = { - preconnect: (url: string, init?: { credentials?: RequestCredentials }) => void; + preconnect: (url: string | URL, options?: FetchPreconnectOptions) => void; }; export function withFetchPreconnect(fn: T): T & FetchWithPreconnect; @@ -8,6 +17,6 @@ export function withFetchPreconnect( ): T & FetchWithPreconnect & typeof fetch; export function withFetchPreconnect(fn: object) { return Object.assign(fn, { - preconnect: (_url: string, _init?: { credentials?: RequestCredentials }) => {}, + preconnect: (_url: string | URL, _options?: FetchPreconnectOptions) => {}, }); }