refactor(plugins): remove media test globals (#122636)

This commit is contained in:
Peter Steinberger
2026-08-12 07:07:19 -07:00
committed by GitHub
parent 85230cf76c
commit c5ba4efbd7
11 changed files with 57 additions and 134 deletions
-6
View File
@@ -5,7 +5,6 @@ import { createTestPluginApi } from "openclaw/plugin-sdk/plugin-test-api";
import { isLiveTestEnabled, readLiveTestConfig } from "openclaw/plugin-sdk/test-live";
import { beforeAll, describe, expect, it } from "vitest";
import plugin from "./index.js";
import { getComfyConfigForTesting } from "./test-support.js";
import { isComfyCapabilityConfigured } from "./workflow-runtime.js";
const LIVE =
@@ -123,9 +122,4 @@ describeLive("comfy live", () => {
expect(result.tracks[0]?.mimeType.startsWith("audio/")).toBe(true);
expect(result.tracks[0]?.buffer.byteLength).toBeGreaterThan(512);
}, 180_000);
it("documents the effective comfy config shape for live debugging", () => {
const comfyConfig = getComfyConfigForTesting(cfg as never);
expect(typeof comfyConfig).toBe("object");
});
});
@@ -1,7 +1,6 @@
// Comfy tests cover image generation provider plugin behavior.
import type { LookupAddress } from "node:dns";
import { MAX_TIMER_TIMEOUT_MS } from "openclaw/plugin-sdk/number-runtime";
import { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { buildComfyImageGenerationProvider } from "./image-generation-provider.js";
import {
@@ -11,12 +10,23 @@ import {
mockComfyProviderApiKey,
parseComfyJsonBody,
} from "./test-helpers.js";
import { setComfyFetchGuardForTesting } from "./test-support.js";
const { fetchWithSsrFGuardMock } = vi.hoisted(() => ({
type FetchWithSsrFGuard = (typeof import("openclaw/plugin-sdk/ssrf-runtime"))["fetchWithSsrFGuard"];
const { fetchWithSsrFGuardMock, ssrfGuardState } = vi.hoisted(() => ({
fetchWithSsrFGuardMock: vi.fn(),
ssrfGuardState: {} as { actual?: FetchWithSsrFGuard },
}));
vi.mock("openclaw/plugin-sdk/ssrf-runtime", async (importOriginal) => {
const actual = await importOriginal<typeof import("openclaw/plugin-sdk/ssrf-runtime")>();
ssrfGuardState.actual = actual.fetchWithSsrFGuard;
return {
...actual,
fetchWithSsrFGuard: fetchWithSsrFGuardMock,
};
});
type FetchGuardRequest = {
url?: unknown;
auditContext?: unknown;
@@ -28,7 +38,7 @@ type FetchGuardRequest = {
body?: BodyInit | null;
};
};
type RealGuardParams = Parameters<typeof fetchWithSsrFGuard>[0];
type RealGuardParams = Parameters<FetchWithSsrFGuard>[0];
type RealGuardFetchImpl = NonNullable<RealGuardParams["fetchImpl"]>;
type RealGuardLookupFn = NonNullable<RealGuardParams["lookupFn"]>;
type RealGuardHarness = {
@@ -206,9 +216,13 @@ function installRealComfyFetchGuard(options: RealComfyFetchOptions): RealGuardHa
});
};
setComfyFetchGuardForTesting(async (params) => {
const actualFetchWithSsrFGuard = ssrfGuardState.actual;
if (!actualFetchWithSsrFGuard) {
throw new Error("expected actual SSRF guard");
}
fetchWithSsrFGuardMock.mockImplementation(async (params) => {
guardCalls.push(params);
return await fetchWithSsrFGuard({
return await actualFetchWithSsrFGuard({
...params,
fetchImpl,
lookupFn,
@@ -219,11 +233,12 @@ function installRealComfyFetchGuard(options: RealComfyFetchOptions): RealGuardHa
describe("comfy image-generation provider", () => {
beforeEach(() => {
fetchWithSsrFGuardMock.mockReset();
vi.clearAllMocks();
});
afterEach(() => {
setComfyFetchGuardForTesting(null);
fetchWithSsrFGuardMock.mockReset();
vi.unstubAllEnvs();
vi.restoreAllMocks();
});
@@ -353,7 +368,6 @@ describe("comfy image-generation provider", () => {
});
it("submits a local workflow, waits for history, and downloads images", async () => {
setComfyFetchGuardForTesting(fetchWithSsrFGuardMock);
fetchWithSsrFGuardMock
.mockResolvedValueOnce({
response: new Response(JSON.stringify({ prompt_id: "local-prompt-1" }), {
@@ -441,7 +455,6 @@ describe("comfy image-generation provider", () => {
});
it("honors local private-network access for service-discovery hostnames", async () => {
setComfyFetchGuardForTesting(fetchWithSsrFGuardMock);
mockLocalImageResponses("compose-prompt-1");
const provider = buildComfyImageGenerationProvider();
@@ -468,7 +481,6 @@ describe("comfy image-generation provider", () => {
});
it("keeps local public-looking hostnames strict without explicit private-network access", async () => {
setComfyFetchGuardForTesting(fetchWithSsrFGuardMock);
mockLocalImageResponses("public-host-prompt-1");
const provider = buildComfyImageGenerationProvider();
@@ -492,7 +504,6 @@ describe("comfy image-generation provider", () => {
});
it("keeps cloud service-discovery hostnames strict without explicit private-network access", async () => {
setComfyFetchGuardForTesting(fetchWithSsrFGuardMock);
mockComfyCloudJobResponses(fetchWithSsrFGuardMock, {
body: Buffer.from("cloud-data"),
contentType: "image/png",
@@ -525,7 +536,6 @@ describe("comfy image-generation provider", () => {
});
it("honors explicit cloud private-network access for service-discovery hostnames", async () => {
setComfyFetchGuardForTesting(fetchWithSsrFGuardMock);
mockComfyCloudJobResponses(fetchWithSsrFGuardMock, {
body: Buffer.from("cloud-data"),
contentType: "image/png",
@@ -787,7 +797,6 @@ describe("comfy image-generation provider", () => {
});
it("caps oversized local workflow timeouts", async () => {
setComfyFetchGuardForTesting(fetchWithSsrFGuardMock);
const nowSpy = vi.spyOn(Date, "now");
nowSpy
.mockReturnValueOnce(0)
@@ -836,7 +845,6 @@ describe("comfy image-generation provider", () => {
});
it("rejects generated image downloads that exceed the configured media cap", async () => {
setComfyFetchGuardForTesting(fetchWithSsrFGuardMock);
fetchWithSsrFGuardMock
.mockResolvedValueOnce({
response: new Response(JSON.stringify({ prompt_id: "local-prompt-1" }), {
@@ -893,7 +901,6 @@ describe("comfy image-generation provider", () => {
});
it("reports malformed local workflow submit JSON as a provider error", async () => {
setComfyFetchGuardForTesting(fetchWithSsrFGuardMock);
const release = vi.fn(async () => {});
fetchWithSsrFGuardMock.mockResolvedValueOnce({
response: new Response("{ nope", {
@@ -923,7 +930,6 @@ describe("comfy image-generation provider", () => {
});
it("bounds oversized local workflow submit responses and releases the request", async () => {
setComfyFetchGuardForTesting(fetchWithSsrFGuardMock);
const chunk = new Uint8Array(1024 * 1024);
const totalBytes = 32 * chunk.length;
let bytesPulled = 0;
@@ -971,7 +977,6 @@ describe("comfy image-generation provider", () => {
});
it("uploads reference images for local edit workflows", async () => {
setComfyFetchGuardForTesting(fetchWithSsrFGuardMock);
fetchWithSsrFGuardMock
.mockResolvedValueOnce({
response: new Response(JSON.stringify({ name: "upload.png" }), {
@@ -1059,7 +1064,6 @@ describe("comfy image-generation provider", () => {
it("uses cloud endpoints, auth headers, and partner-node extra_data", async () => {
mockComfyProviderApiKey();
setComfyFetchGuardForTesting(fetchWithSsrFGuardMock);
mockComfyCloudJobResponses(fetchWithSsrFGuardMock, {
body: Buffer.from("cloud-data"),
contentType: "image/png",
@@ -1120,7 +1124,6 @@ describe("comfy image-generation provider", () => {
it("uses plugin config env SecretRef auth for cloud workflows", async () => {
vi.stubEnv("COMFY_TEST_API_KEY", "comfy-secret-ref-key");
setComfyFetchGuardForTesting(fetchWithSsrFGuardMock);
mockComfyCloudJobResponses(fetchWithSsrFGuardMock, {
body: Buffer.from("cloud-data"),
contentType: "image/png",
@@ -1158,7 +1161,6 @@ describe("comfy image-generation provider", () => {
it("uses provider auth fallback for cloud workflows without plugin config API keys", async () => {
vi.stubEnv("COMFY_API_KEY", "stale-env-key");
mockComfyProviderApiKey("profile-key");
setComfyFetchGuardForTesting(fetchWithSsrFGuardMock);
mockComfyCloudJobResponses(fetchWithSsrFGuardMock, {
body: Buffer.from("cloud-data"),
contentType: "image/png",
@@ -2,15 +2,19 @@
import { expectExplicitMusicGenerationCapabilities } from "openclaw/plugin-sdk/provider-test-contracts";
import { afterEach, describe, expect, it, vi } from "vitest";
import { buildComfyMusicGenerationProvider } from "./music-generation-provider.js";
import { setComfyFetchGuardForTesting } from "./test-support.js";
const { fetchWithSsrFGuardMock } = vi.hoisted(() => ({
fetchWithSsrFGuardMock: vi.fn(),
}));
vi.mock("openclaw/plugin-sdk/ssrf-runtime", async (importOriginal) => ({
...(await importOriginal<typeof import("openclaw/plugin-sdk/ssrf-runtime")>()),
fetchWithSsrFGuard: fetchWithSsrFGuardMock,
}));
describe("comfy music-generation provider", () => {
afterEach(() => {
setComfyFetchGuardForTesting(null);
fetchWithSsrFGuardMock.mockReset();
vi.clearAllMocks();
});
@@ -23,7 +27,6 @@ describe("comfy music-generation provider", () => {
});
it("runs a music workflow and returns audio outputs", async () => {
setComfyFetchGuardForTesting(fetchWithSsrFGuardMock);
fetchWithSsrFGuardMock
.mockResolvedValueOnce({
response: new Response(JSON.stringify({ prompt_id: "music-job-1" }), {
@@ -101,7 +104,6 @@ describe("comfy music-generation provider", () => {
});
it("rejects generated music downloads that exceed the configured media cap", async () => {
setComfyFetchGuardForTesting(fetchWithSsrFGuardMock);
fetchWithSsrFGuardMock
.mockResolvedValueOnce({
response: new Response(JSON.stringify({ prompt_id: "music-job-1" }), {
-22
View File
@@ -1,22 +0,0 @@
import type { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime";
type ComfyTestApi = {
getConfig: (cfg?: unknown) => Record<string, unknown>;
setFetchGuard: (impl: typeof fetchWithSsrFGuard | null) => void;
};
function getComfyTestApi(): ComfyTestApi {
const api = Reflect.get(globalThis, Symbol.for("openclaw.comfyTestApi"));
if (!api) {
throw new Error("Comfy test API is unavailable");
}
return api as ComfyTestApi;
}
export function setComfyFetchGuardForTesting(impl: typeof fetchWithSsrFGuard | null): void {
getComfyTestApi().setFetchGuard(impl);
}
export function getComfyConfigForTesting(cfg?: unknown): Record<string, unknown> {
return getComfyTestApi().getConfig(cfg);
}
@@ -7,13 +7,17 @@ import {
mockComfyProviderApiKey,
parseComfyJsonBody,
} from "./test-helpers.js";
import { setComfyFetchGuardForTesting } from "./test-support.js";
import { buildComfyVideoGenerationProvider } from "./video-generation-provider.js";
const { fetchWithSsrFGuardMock } = vi.hoisted(() => ({
fetchWithSsrFGuardMock: vi.fn(),
}));
vi.mock("openclaw/plugin-sdk/ssrf-runtime", async (importOriginal) => ({
...(await importOriginal<typeof import("openclaw/plugin-sdk/ssrf-runtime")>()),
fetchWithSsrFGuard: fetchWithSsrFGuardMock,
}));
function parseJsonBody(call: number): Record<string, unknown> {
return parseComfyJsonBody(fetchWithSsrFGuardMock, call);
}
@@ -89,11 +93,12 @@ function generateLocalVideo(outputNodeId?: string) {
describe("comfy video-generation provider", () => {
beforeEach(() => {
fetchWithSsrFGuardMock.mockReset();
vi.clearAllMocks();
});
afterEach(() => {
setComfyFetchGuardForTesting(null);
fetchWithSsrFGuardMock.mockReset();
vi.restoreAllMocks();
});
@@ -118,7 +123,6 @@ describe("comfy video-generation provider", () => {
});
it("submits a local workflow, waits for history, and downloads videos", async () => {
setComfyFetchGuardForTesting(fetchWithSsrFGuardMock);
fetchWithSsrFGuardMock
.mockResolvedValueOnce({
response: new Response(JSON.stringify({ prompt_id: "local-video-1" }), {
@@ -205,7 +209,6 @@ describe("comfy video-generation provider", () => {
});
it("returns only MP4 video entries from mixed images buckets", async () => {
setComfyFetchGuardForTesting(fetchWithSsrFGuardMock);
mockLocalVideoResponses({
promptId: "local-video-mixed",
outputs: {
@@ -243,7 +246,6 @@ describe("comfy video-generation provider", () => {
});
it("accepts uppercase WEBM names from the images bucket", async () => {
setComfyFetchGuardForTesting(fetchWithSsrFGuardMock);
mockLocalVideoResponses({
promptId: "local-video-webm",
outputs: {
@@ -272,7 +274,6 @@ describe("comfy video-generation provider", () => {
});
it("rejects images-only workflow output for video generation", async () => {
setComfyFetchGuardForTesting(fetchWithSsrFGuardMock);
mockLocalVideoResponses({
promptId: "local-video-images-only",
outputs: {
@@ -292,7 +293,6 @@ describe("comfy video-generation provider", () => {
});
it("preserves legacy videos bucket output without filename filtering", async () => {
setComfyFetchGuardForTesting(fetchWithSsrFGuardMock);
mockLocalVideoResponses({
promptId: "local-video-legacy",
outputs: {
@@ -318,7 +318,6 @@ describe("comfy video-generation provider", () => {
});
it("rejects generated video downloads that exceed the configured media cap", async () => {
setComfyFetchGuardForTesting(fetchWithSsrFGuardMock);
fetchWithSsrFGuardMock
.mockResolvedValueOnce({
response: new Response(JSON.stringify({ prompt_id: "local-video-1" }), {
@@ -378,7 +377,6 @@ describe("comfy video-generation provider", () => {
it("uses cloud endpoints for video workflows", async () => {
mockComfyProviderApiKey();
setComfyFetchGuardForTesting(fetchWithSsrFGuardMock);
mockComfyCloudJobResponses(fetchWithSsrFGuardMock, {
body: Buffer.from("cloud-video-data"),
contentType: "video/mp4",
+2 -15
View File
@@ -111,19 +111,6 @@ type ComfyWorkflowResult = {
outputNodeIds: string[];
};
let comfyFetchGuard = fetchWithSsrFGuard;
function setComfyFetchGuardForTesting(impl: typeof fetchWithSsrFGuard | null): void {
comfyFetchGuard = impl ?? fetchWithSsrFGuard;
}
if (process.env.VITEST === "true") {
Reflect.set(globalThis, Symbol.for("openclaw.comfyTestApi"), {
getConfig: getComfyConfig,
setFetchGuard: setComfyFetchGuardForTesting,
});
}
function readConfigInteger(config: ComfyProviderConfig, key: string): number | undefined {
const value = config[key];
return typeof value === "number" && Number.isInteger(value) && value > 0 ? value : undefined;
@@ -327,7 +314,7 @@ async function readJsonResponse<T>(params: {
auditContext: string;
errorPrefix: string;
}): Promise<T> {
const { response, release } = await comfyFetchGuard({
const { response, release } = await fetchWithSsrFGuard({
url: params.url,
init: params.init,
timeoutMs: params.timeoutMs,
@@ -581,7 +568,7 @@ async function downloadOutputFile(params: {
const viewPath = params.mode === "cloud" ? "/api/view" : "/view";
const auditContext = `comfy-${params.capability}-download`;
const firstResponse = await comfyFetchGuard({
const firstResponse = await fetchWithSsrFGuard({
url: `${params.baseUrl}${viewPath}?${query.toString()}`,
init: {
method: "GET",
@@ -8,8 +8,12 @@ const { fetchWithSsrFGuardMock } = vi.hoisted(() => ({
fetchWithSsrFGuardMock: vi.fn(),
}));
vi.mock("openclaw/plugin-sdk/ssrf-runtime", async (importOriginal) => ({
...(await importOriginal<typeof import("openclaw/plugin-sdk/ssrf-runtime")>()),
fetchWithSsrFGuard: fetchWithSsrFGuardMock,
}));
import { buildFalImageGenerationProvider } from "./image-generation-provider.js";
import { setFalFetchGuardForTesting } from "./test-support.js";
const falApiKey = { apiKey: "fal-test-key", source: "env", mode: "api-key" } as const;
@@ -75,14 +79,14 @@ describe("fal image-generation provider", () => {
}
beforeEach(() => {
fetchWithSsrFGuardMock.mockReset();
vi.clearAllMocks();
vi.spyOn(providerAuth, "resolveApiKeyForProvider").mockResolvedValue(falApiKey);
setFalFetchGuardForTesting(fetchWithSsrFGuardMock);
provider = buildFalImageGenerationProvider();
});
afterEach(() => {
setFalFetchGuardForTesting(null);
fetchWithSsrFGuardMock.mockReset();
vi.useRealTimers();
vi.restoreAllMocks();
});
+2 -14
View File
@@ -154,18 +154,6 @@ type FalNetworkPolicy = {
trustedDownloadPolicy?: SsrFPolicy;
};
let falFetchGuard = fetchWithSsrFGuard;
function setFalFetchGuardForTesting(impl: typeof fetchWithSsrFGuard | null): void {
falFetchGuard = impl ?? fetchWithSsrFGuard;
}
if (process.env.VITEST === "true") {
const key = Symbol.for("openclaw.falTestApi");
const api = (Reflect.get(globalThis, key) as Record<string, unknown> | undefined) ?? {};
Reflect.set(globalThis, key, { ...api, setImageFetchGuard: setFalFetchGuardForTesting });
}
function matchesTrustedHostSuffix(hostname: string, trustedSuffix: string): boolean {
const normalizedHost = normalizeLowercaseStringOrEmpty(hostname);
const normalizedSuffix = normalizeLowercaseStringOrEmpty(trustedSuffix);
@@ -609,7 +597,7 @@ async function fetchImageBuffer(
return undefined;
}
})();
const { response, release } = await falFetchGuard({
const { response, release } = await fetchWithSsrFGuard({
url,
timeoutMs: resolveProviderOperationTimeoutMs({
deadline,
@@ -782,7 +770,7 @@ export function buildFalImageGenerationProvider(): ImageGenerationProvider {
inputImages: req.inputImages ?? [],
});
}
const { response, release } = await falFetchGuard({
const { response, release } = await fetchWithSsrFGuard({
url: `${baseUrl}/${model}`,
init: {
method: "POST",
-22
View File
@@ -1,22 +0,0 @@
import type { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime";
type FalTestApi = {
setImageFetchGuard: (impl: typeof fetchWithSsrFGuard | null) => void;
setVideoFetchGuard: (impl: typeof fetchWithSsrFGuard | null) => void;
};
function getFalTestApi(): FalTestApi {
const api = Reflect.get(globalThis, Symbol.for("openclaw.falTestApi"));
if (!api) {
throw new Error("Fal test API is unavailable");
}
return api as FalTestApi;
}
export function setFalFetchGuardForTesting(impl: typeof fetchWithSsrFGuard | null): void {
getFalTestApi().setImageFetchGuard(impl);
}
export function setFalVideoFetchGuardForTesting(impl: typeof fetchWithSsrFGuard | null): void {
getFalTestApi().setVideoFetchGuard(impl);
}
@@ -4,15 +4,21 @@ import * as providerAuth from "openclaw/plugin-sdk/provider-auth-runtime";
import * as providerHttp from "openclaw/plugin-sdk/provider-http";
import { expectExplicitVideoGenerationCapabilities } from "openclaw/plugin-sdk/provider-test-contracts";
import { afterEach, describe, expect, it, vi } from "vitest";
import { setFalVideoFetchGuardForTesting } from "./test-support.js";
import { buildFalVideoGenerationProvider } from "./video-generation-provider.js";
const { fetchGuardMock } = vi.hoisted(() => ({
fetchGuardMock: vi.fn(),
}));
vi.mock("openclaw/plugin-sdk/ssrf-runtime", async (importOriginal) => ({
...(await importOriginal<typeof import("openclaw/plugin-sdk/ssrf-runtime")>()),
fetchWithSsrFGuard: fetchGuardMock,
}));
function createMockRequestConfig() {
return {} as ReturnType<typeof providerHttp.resolveProviderHttpRequestConfig>["requestConfig"];
}
describe("fal video generation provider", () => {
const fetchGuardMock = vi.fn();
function mockFalProviderRuntime() {
vi.spyOn(providerAuth, "resolveApiKeyForProvider").mockResolvedValue({
apiKey: "fal-key",
@@ -30,7 +36,6 @@ describe("fal video generation provider", () => {
requestConfig: createMockRequestConfig(),
});
vi.spyOn(providerHttp, "assertOkOrThrowHttpError").mockResolvedValue(undefined);
setFalVideoFetchGuardForTesting(fetchGuardMock as never);
}
function releasedJson(value: unknown) {
@@ -109,7 +114,6 @@ describe("fal video generation provider", () => {
afterEach(() => {
vi.restoreAllMocks();
fetchGuardMock.mockReset();
setFalVideoFetchGuardForTesting(null);
});
it("declares explicit mode capabilities", () => {
+2 -14
View File
@@ -97,18 +97,6 @@ type FalQueueResponse = {
};
};
let falFetchGuard = fetchWithSsrFGuard;
function setFalVideoFetchGuardForTesting(impl: typeof fetchWithSsrFGuard | null): void {
falFetchGuard = impl ?? fetchWithSsrFGuard;
}
if (process.env.VITEST === "true") {
const key = Symbol.for("openclaw.falTestApi");
const api = (Reflect.get(globalThis, key) as Record<string, unknown> | undefined) ?? {};
Reflect.set(globalThis, key, { ...api, setVideoFetchGuard: setFalVideoFetchGuardForTesting });
}
function normalizeFalVideoUrl(value: unknown): string | undefined {
const normalized = normalizeOptionalString(value);
if (!normalized && value !== undefined && value !== null) {
@@ -208,7 +196,7 @@ async function downloadFalVideo(
policy: SsrFPolicy | undefined,
maxBytes: number,
): Promise<GeneratedVideoAsset> {
const { response, release } = await falFetchGuard({
const { response, release } = await fetchWithSsrFGuard({
url,
timeoutMs: DEFAULT_HTTP_TIMEOUT_MS,
policy,
@@ -468,7 +456,7 @@ async function fetchFalJson(params: {
auditContext: string;
errorContext: string;
}): Promise<unknown> {
const { response, release } = await falFetchGuard({
const { response, release } = await fetchWithSsrFGuard({
url: params.url,
init: params.init,
timeoutMs: params.timeoutMs,