From 74c49c943d6f999f712e9dab998ccaf63060f4e3 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Mon, 16 Feb 2026 18:09:45 +0000 Subject: [PATCH] refactor(test): share web fetch e2e setup helpers --- src/agents/tools/web-tools.fetch.e2e.test.ts | 236 +++++++------------ 1 file changed, 80 insertions(+), 156 deletions(-) diff --git a/src/agents/tools/web-tools.fetch.e2e.test.ts b/src/agents/tools/web-tools.fetch.e2e.test.ts index a238d7f6a90..48c00cbddf9 100644 --- a/src/agents/tools/web-tools.fetch.e2e.test.ts +++ b/src/agents/tools/web-tools.fetch.e2e.test.ts @@ -90,6 +90,41 @@ function requestUrl(input: RequestInfo): string { return ""; } +function installMockFetch(impl: (input: RequestInfo) => Promise) { + const mockFetch = vi.fn(impl); + // @ts-expect-error mock fetch + global.fetch = mockFetch; + return mockFetch; +} + +function createFetchTool(fetchOverrides: Record = {}) { + return createWebFetchTool({ + config: { + tools: { + web: { + fetch: { + cacheTtlMinutes: 0, + ...fetchOverrides, + }, + }, + }, + }, + sandboxed: false, + }); +} + +async function captureToolErrorMessage(params: { + tool: ReturnType; + url: string; +}) { + try { + await params.tool?.execute?.("call", { url: params.url }); + return ""; + } catch (error) { + return (error as Error).message; + } +} + describe("web_fetch extraction fallbacks", () => { const priorFetch = global.fetch; @@ -112,7 +147,7 @@ describe("web_fetch extraction fallbacks", () => { }); it("wraps fetched text with external content markers", async () => { - const mockFetch = vi.fn((input: RequestInfo) => + installMockFetch((input: RequestInfo) => Promise.resolve({ ok: true, status: 200, @@ -121,19 +156,8 @@ describe("web_fetch extraction fallbacks", () => { url: requestUrl(input), } as Response), ); - // @ts-expect-error mock fetch - global.fetch = mockFetch; - const tool = createWebFetchTool({ - config: { - tools: { - web: { - fetch: { cacheTtlMinutes: 0, firecrawl: { enabled: false } }, - }, - }, - }, - sandboxed: false, - }); + const tool = createFetchTool({ firecrawl: { enabled: false } }); const result = await tool?.execute?.("call", { url: "https://example.com/plain" }); const details = result?.details as { @@ -161,7 +185,7 @@ describe("web_fetch extraction fallbacks", () => { it("enforces maxChars after wrapping", async () => { const longText = "x".repeat(5_000); - const mockFetch = vi.fn((input: RequestInfo) => + installMockFetch((input: RequestInfo) => Promise.resolve({ ok: true, status: 200, @@ -170,18 +194,10 @@ describe("web_fetch extraction fallbacks", () => { url: requestUrl(input), } as Response), ); - // @ts-expect-error mock fetch - global.fetch = mockFetch; - const tool = createWebFetchTool({ - config: { - tools: { - web: { - fetch: { cacheTtlMinutes: 0, firecrawl: { enabled: false }, maxChars: 2000 }, - }, - }, - }, - sandboxed: false, + const tool = createFetchTool({ + firecrawl: { enabled: false }, + maxChars: 2000, }); const result = await tool?.execute?.("call", { url: "https://example.com/long" }); @@ -192,7 +208,7 @@ describe("web_fetch extraction fallbacks", () => { }); it("honors maxChars even when wrapper overhead exceeds limit", async () => { - const mockFetch = vi.fn((input: RequestInfo) => + installMockFetch((input: RequestInfo) => Promise.resolve({ ok: true, status: 200, @@ -201,18 +217,10 @@ describe("web_fetch extraction fallbacks", () => { url: requestUrl(input), } as Response), ); - // @ts-expect-error mock fetch - global.fetch = mockFetch; - const tool = createWebFetchTool({ - config: { - tools: { - web: { - fetch: { cacheTtlMinutes: 0, firecrawl: { enabled: false }, maxChars: 100 }, - }, - }, - }, - sandboxed: false, + const tool = createFetchTool({ + firecrawl: { enabled: false }, + maxChars: 100, }); const result = await tool?.execute?.("call", { url: "https://example.com/short" }); @@ -226,7 +234,7 @@ describe("web_fetch extraction fallbacks", () => { // The sanitization of these fields is verified by external-content.test.ts tests. it("falls back to firecrawl when readability returns no content", async () => { - const mockFetch = vi.fn((input: RequestInfo) => { + installMockFetch((input: RequestInfo) => { const url = requestUrl(input); if (url.includes("api.firecrawl.dev")) { return Promise.resolve(firecrawlResponse("firecrawl content")) as Promise; @@ -235,21 +243,9 @@ describe("web_fetch extraction fallbacks", () => { htmlResponse("", url), ) as Promise; }); - // @ts-expect-error mock fetch - global.fetch = mockFetch; - const tool = createWebFetchTool({ - config: { - tools: { - web: { - fetch: { - cacheTtlMinutes: 0, - firecrawl: { apiKey: "firecrawl-test" }, - }, - }, - }, - }, - sandboxed: false, + const tool = createFetchTool({ + firecrawl: { apiKey: "firecrawl-test" }, }); const result = await tool?.execute?.("call", { url: "https://example.com/empty" }); @@ -259,21 +255,13 @@ describe("web_fetch extraction fallbacks", () => { }); it("throws when readability is disabled and firecrawl is unavailable", async () => { - const mockFetch = vi.fn((input: RequestInfo) => + installMockFetch((input: RequestInfo) => Promise.resolve(htmlResponse("hi", requestUrl(input))), ); - // @ts-expect-error mock fetch - global.fetch = mockFetch; - const tool = createWebFetchTool({ - config: { - tools: { - web: { - fetch: { readability: false, cacheTtlMinutes: 0, firecrawl: { enabled: false } }, - }, - }, - }, - sandboxed: false, + const tool = createFetchTool({ + readability: false, + firecrawl: { enabled: false }, }); await expect( @@ -282,7 +270,7 @@ describe("web_fetch extraction fallbacks", () => { }); it("throws when readability is empty and firecrawl fails", async () => { - const mockFetch = vi.fn((input: RequestInfo) => { + installMockFetch((input: RequestInfo) => { const url = requestUrl(input); if (url.includes("api.firecrawl.dev")) { return Promise.resolve(firecrawlError()) as Promise; @@ -291,18 +279,9 @@ describe("web_fetch extraction fallbacks", () => { htmlResponse("", url), ) as Promise; }); - // @ts-expect-error mock fetch - global.fetch = mockFetch; - const tool = createWebFetchTool({ - config: { - tools: { - web: { - fetch: { cacheTtlMinutes: 0, firecrawl: { apiKey: "firecrawl-test" } }, - }, - }, - }, - sandboxed: false, + const tool = createFetchTool({ + firecrawl: { apiKey: "firecrawl-test" }, }); await expect( @@ -311,7 +290,7 @@ describe("web_fetch extraction fallbacks", () => { }); it("uses firecrawl when direct fetch fails", async () => { - const mockFetch = vi.fn((input: RequestInfo) => { + installMockFetch((input: RequestInfo) => { const url = requestUrl(input); if (url.includes("api.firecrawl.dev")) { return Promise.resolve(firecrawlResponse("firecrawl fallback", url)) as Promise; @@ -323,18 +302,9 @@ describe("web_fetch extraction fallbacks", () => { text: async () => "blocked", } as Response); }); - // @ts-expect-error mock fetch - global.fetch = mockFetch; - const tool = createWebFetchTool({ - config: { - tools: { - web: { - fetch: { cacheTtlMinutes: 0, firecrawl: { apiKey: "firecrawl-test" } }, - }, - }, - }, - sandboxed: false, + const tool = createFetchTool({ + firecrawl: { apiKey: "firecrawl-test" }, }); const result = await tool?.execute?.("call", { url: "https://example.com/blocked" }); @@ -345,22 +315,14 @@ describe("web_fetch extraction fallbacks", () => { it("wraps external content and clamps oversized maxChars", async () => { const large = "a".repeat(80_000); - const mockFetch = vi.fn( + installMockFetch( (input: RequestInfo) => Promise.resolve(textResponse(large, requestUrl(input))) as Promise, ); - // @ts-expect-error mock fetch - global.fetch = mockFetch; - const tool = createWebFetchTool({ - config: { - tools: { - web: { - fetch: { cacheTtlMinutes: 0, firecrawl: { enabled: false }, maxCharsCap: 10_000 }, - }, - }, - }, - sandboxed: false, + const tool = createFetchTool({ + firecrawl: { enabled: false }, + maxCharsCap: 10_000, }); const result = await tool?.execute?.("call", { @@ -373,36 +335,23 @@ describe("web_fetch extraction fallbacks", () => { expect(details.length).toBeLessThanOrEqual(10_000); expect(details.truncated).toBe(true); }); + it("strips and truncates HTML from error responses", async () => { const long = "x".repeat(12_000); const html = "Not Found

Not Found

" + long + "

"; - const mockFetch = vi.fn((input: RequestInfo) => + installMockFetch((input: RequestInfo) => Promise.resolve(errorHtmlResponse(html, 404, requestUrl(input), "Text/HTML; charset=utf-8")), ); - // @ts-expect-error mock fetch - global.fetch = mockFetch; - const tool = createWebFetchTool({ - config: { - tools: { - web: { - fetch: { cacheTtlMinutes: 0, firecrawl: { enabled: false } }, - }, - }, - }, - sandboxed: false, + const tool = createFetchTool({ firecrawl: { enabled: false } }); + const message = await captureToolErrorMessage({ + tool, + url: "https://example.com/missing", }); - let message = ""; - try { - await tool?.execute?.("call", { url: "https://example.com/missing" }); - } catch (error) { - message = (error as Error).message; - } - expect(message).toContain("Web fetch failed (404):"); expect(message).toContain("<<>>"); expect(message).toContain("SECURITY NOTICE"); @@ -414,37 +363,23 @@ describe("web_fetch extraction fallbacks", () => { it("strips HTML errors when content-type is missing", async () => { const html = "Oops

Oops

"; - const mockFetch = vi.fn((input: RequestInfo) => + installMockFetch((input: RequestInfo) => Promise.resolve(errorHtmlResponse(html, 500, requestUrl(input), null)), ); - // @ts-expect-error mock fetch - global.fetch = mockFetch; - const tool = createWebFetchTool({ - config: { - tools: { - web: { - fetch: { cacheTtlMinutes: 0, firecrawl: { enabled: false } }, - }, - }, - }, - sandboxed: false, + const tool = createFetchTool({ firecrawl: { enabled: false } }); + const message = await captureToolErrorMessage({ + tool, + url: "https://example.com/oops", }); - let message = ""; - try { - await tool?.execute?.("call", { url: "https://example.com/oops" }); - } catch (error) { - message = (error as Error).message; - } - expect(message).toContain("Web fetch failed (500):"); expect(message).toContain("<<>>"); expect(message).toContain("Oops"); }); it("wraps firecrawl error details", async () => { - const mockFetch = vi.fn((input: RequestInfo) => { + installMockFetch((input: RequestInfo) => { const url = requestUrl(input); if (url.includes("api.firecrawl.dev")) { return Promise.resolve({ @@ -455,26 +390,15 @@ describe("web_fetch extraction fallbacks", () => { } return Promise.reject(new Error("network down")); }); - // @ts-expect-error mock fetch - global.fetch = mockFetch; - const tool = createWebFetchTool({ - config: { - tools: { - web: { - fetch: { cacheTtlMinutes: 0, firecrawl: { apiKey: "firecrawl-test" } }, - }, - }, - }, - sandboxed: false, + const tool = createFetchTool({ + firecrawl: { apiKey: "firecrawl-test" }, }); - let message = ""; - try { - await tool?.execute?.("call", { url: "https://example.com/firecrawl-error" }); - } catch (error) { - message = (error as Error).message; - } + const message = await captureToolErrorMessage({ + tool, + url: "https://example.com/firecrawl-error", + }); expect(message).toContain("Firecrawl fetch failed (403):"); expect(message).toContain("<<>>");