From fe395cf045fa881ccf351efb0bc66ba732a9681e Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 11 Apr 2026 00:36:56 +0100 Subject: [PATCH] test: isolate remaining extension network tests --- .../firecrawl/src/firecrawl-tools.test.ts | 5 +++++ .../msteams/src/attachments.graph.test.ts | 5 +++++ .../src/attachments/bot-framework.test.ts | 17 +++++++++++++++++ extensions/msteams/src/sdk.test.ts | 18 ++++++++++++++++++ 4 files changed, 45 insertions(+) diff --git a/extensions/firecrawl/src/firecrawl-tools.test.ts b/extensions/firecrawl/src/firecrawl-tools.test.ts index fcce5f5b1ed..009e327c64e 100644 --- a/extensions/firecrawl/src/firecrawl-tools.test.ts +++ b/extensions/firecrawl/src/firecrawl-tools.test.ts @@ -1,5 +1,6 @@ import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import { mockPinnedHostnameResolution } from "../../../src/test-helpers/ssrf.js"; import { DEFAULT_FIRECRAWL_BASE_URL, DEFAULT_FIRECRAWL_MAX_AGE_MS, @@ -35,6 +36,7 @@ describe("firecrawl tools", () => { let createFirecrawlSearchTool: typeof import("./firecrawl-search-tool.js").createFirecrawlSearchTool; let createFirecrawlScrapeTool: typeof import("./firecrawl-scrape-tool.js").createFirecrawlScrapeTool; let firecrawlClientTesting: typeof import("./firecrawl-client.js").__testing; + let ssrfMock: { mockRestore: () => void } | undefined; beforeAll(async () => { ({ fetchFirecrawlContent } = await import("../api.js")); @@ -47,6 +49,7 @@ describe("firecrawl tools", () => { }); beforeEach(() => { + ssrfMock = mockPinnedHostnameResolution(); runFirecrawlSearch.mockReset(); runFirecrawlSearch.mockImplementation(async (params: Record) => params); runFirecrawlScrape.mockReset(); @@ -58,6 +61,8 @@ describe("firecrawl tools", () => { }); afterEach(() => { + ssrfMock?.mockRestore(); + ssrfMock = undefined; global.fetch = priorFetch; }); diff --git a/extensions/msteams/src/attachments.graph.test.ts b/extensions/msteams/src/attachments.graph.test.ts index a88afe58a93..1792de9d60a 100644 --- a/extensions/msteams/src/attachments.graph.test.ts +++ b/extensions/msteams/src/attachments.graph.test.ts @@ -1,4 +1,5 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; +import { mockPinnedHostnameResolution } from "../../../src/test-helpers/ssrf.js"; import type { PluginRuntime } from "../runtime-api.js"; import { downloadMSTeamsGraphMedia } from "./attachments/graph.js"; import { resolveRequestUrl } from "./attachments/shared.js"; @@ -249,7 +250,11 @@ const GRAPH_MEDIA_SUCCESS_CASES: GraphMediaSuccessCase[] = [ ]; describe("msteams graph attachments", () => { + let ssrfMock: { mockRestore: () => void } | undefined; + beforeEach(() => { + ssrfMock?.mockRestore(); + ssrfMock = mockPinnedHostnameResolution(); detectMimeMock.mockClear(); fetchRemoteMediaMock.mockClear(); saveMediaBufferMock.mockClear(); diff --git a/extensions/msteams/src/attachments/bot-framework.test.ts b/extensions/msteams/src/attachments/bot-framework.test.ts index 8bfd67c063d..3cccb6243d5 100644 --- a/extensions/msteams/src/attachments/bot-framework.test.ts +++ b/extensions/msteams/src/attachments/bot-framework.test.ts @@ -7,6 +7,23 @@ import { } from "./bot-framework.js"; import type { MSTeamsAccessTokenProvider } from "./types.js"; +vi.mock("../../runtime-api.js", async () => { + const actual = + await vi.importActual("../../runtime-api.js"); + return { + ...actual, + fetchWithSsrFGuard: async (params: { + url: string; + init?: RequestInit; + fetchImpl?: typeof fetch; + }) => ({ + response: await (params.fetchImpl ?? fetch)(params.url, params.init), + finalUrl: params.url, + release: async () => {}, + }), + }; +}); + type SavedCall = { buffer: Buffer; contentType?: string; diff --git a/extensions/msteams/src/sdk.test.ts b/extensions/msteams/src/sdk.test.ts index 08ff8ff9784..4d4191615d4 100644 --- a/extensions/msteams/src/sdk.test.ts +++ b/extensions/msteams/src/sdk.test.ts @@ -7,6 +7,24 @@ import { } from "./sdk.js"; import type { MSTeamsCredentials } from "./token.js"; +vi.mock("openclaw/plugin-sdk/ssrf-runtime", async () => { + const actual = await vi.importActual( + "openclaw/plugin-sdk/ssrf-runtime", + ); + return { + ...actual, + fetchWithSsrFGuard: async (params: { + url: string; + init?: RequestInit; + fetchImpl?: typeof fetch; + }) => ({ + response: await (params.fetchImpl ?? fetch)(params.url, params.init), + finalUrl: params.url, + release: async () => {}, + }), + }; +}); + const clientConstructorState = vi.hoisted(() => ({ calls: [] as Array<{ serviceUrl: string; options: unknown }>, }));