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