mirror of https://github.com/openclaw/openclaw.git
263 lines
8.5 KiB
TypeScript
263 lines
8.5 KiB
TypeScript
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
|
import type { OpenClawConfig } from "../config/config.js";
|
|
import type { PluginWebFetchProviderEntry } from "../plugins/types.js";
|
|
import type { RuntimeWebFetchMetadata } from "../secrets/runtime-web-tools.types.js";
|
|
|
|
type TestPluginWebFetchConfig = {
|
|
webFetch?: {
|
|
apiKey?: unknown;
|
|
};
|
|
};
|
|
|
|
const { resolveBundledPluginWebFetchProvidersMock, resolveRuntimeWebFetchProvidersMock } =
|
|
vi.hoisted(() => ({
|
|
resolveBundledPluginWebFetchProvidersMock: vi.fn<() => PluginWebFetchProviderEntry[]>(() => []),
|
|
resolveRuntimeWebFetchProvidersMock: vi.fn<() => PluginWebFetchProviderEntry[]>(() => []),
|
|
}));
|
|
|
|
vi.mock("../plugins/web-fetch-providers.js", () => ({
|
|
resolveBundledPluginWebFetchProviders: resolveBundledPluginWebFetchProvidersMock,
|
|
}));
|
|
|
|
vi.mock("../plugins/web-fetch-providers.runtime.js", () => ({
|
|
resolvePluginWebFetchProviders: resolveRuntimeWebFetchProvidersMock,
|
|
resolveRuntimeWebFetchProviders: resolveRuntimeWebFetchProvidersMock,
|
|
}));
|
|
|
|
function createProvider(params: {
|
|
pluginId: string;
|
|
id: string;
|
|
credentialPath: string;
|
|
autoDetectOrder?: number;
|
|
requiresCredential?: boolean;
|
|
getCredentialValue?: PluginWebFetchProviderEntry["getCredentialValue"];
|
|
getConfiguredCredentialValue?: PluginWebFetchProviderEntry["getConfiguredCredentialValue"];
|
|
createTool?: PluginWebFetchProviderEntry["createTool"];
|
|
}): PluginWebFetchProviderEntry {
|
|
return {
|
|
pluginId: params.pluginId,
|
|
id: params.id,
|
|
label: params.id,
|
|
hint: `${params.id} runtime provider`,
|
|
envVars: [`${params.id.toUpperCase()}_API_KEY`],
|
|
placeholder: `${params.id}-...`,
|
|
signupUrl: `https://example.com/${params.id}`,
|
|
credentialPath: params.credentialPath,
|
|
autoDetectOrder: params.autoDetectOrder,
|
|
requiresCredential: params.requiresCredential,
|
|
getCredentialValue: params.getCredentialValue ?? (() => undefined),
|
|
setCredentialValue: () => {},
|
|
getConfiguredCredentialValue: params.getConfiguredCredentialValue,
|
|
createTool:
|
|
params.createTool ??
|
|
(() => ({
|
|
description: params.id,
|
|
parameters: {},
|
|
execute: async (args) => ({ ...args, provider: params.id }),
|
|
})),
|
|
};
|
|
}
|
|
|
|
describe("web fetch runtime", () => {
|
|
let resolveWebFetchDefinition: typeof import("./runtime.js").resolveWebFetchDefinition;
|
|
let clearSecretsRuntimeSnapshot: typeof import("../secrets/runtime.js").clearSecretsRuntimeSnapshot;
|
|
|
|
beforeAll(async () => {
|
|
({ resolveWebFetchDefinition } = await import("./runtime.js"));
|
|
({ clearSecretsRuntimeSnapshot } = await import("../secrets/runtime.js"));
|
|
});
|
|
|
|
beforeEach(() => {
|
|
vi.unstubAllEnvs();
|
|
resolveBundledPluginWebFetchProvidersMock.mockReset();
|
|
resolveRuntimeWebFetchProvidersMock.mockReset();
|
|
resolveBundledPluginWebFetchProvidersMock.mockReturnValue([]);
|
|
resolveRuntimeWebFetchProvidersMock.mockReturnValue([]);
|
|
});
|
|
|
|
afterEach(() => {
|
|
clearSecretsRuntimeSnapshot();
|
|
});
|
|
|
|
it("does not auto-detect providers from plugin-owned env SecretRefs without runtime metadata", () => {
|
|
const provider = createProvider({
|
|
pluginId: "firecrawl",
|
|
id: "firecrawl",
|
|
credentialPath: "plugins.entries.firecrawl.config.webFetch.apiKey",
|
|
autoDetectOrder: 1,
|
|
getConfiguredCredentialValue: (config) => {
|
|
const pluginConfig = config?.plugins?.entries?.firecrawl?.config as
|
|
| TestPluginWebFetchConfig
|
|
| undefined;
|
|
return pluginConfig?.webFetch?.apiKey;
|
|
},
|
|
});
|
|
resolveBundledPluginWebFetchProvidersMock.mockReturnValue([provider]);
|
|
|
|
const config: OpenClawConfig = {
|
|
plugins: {
|
|
entries: {
|
|
firecrawl: {
|
|
enabled: true,
|
|
config: {
|
|
webFetch: {
|
|
apiKey: {
|
|
source: "env",
|
|
provider: "default",
|
|
id: "AWS_SECRET_ACCESS_KEY",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
};
|
|
|
|
vi.stubEnv("FIRECRAWL_API_KEY", "");
|
|
|
|
expect(resolveWebFetchDefinition({ config })).toBeNull();
|
|
});
|
|
|
|
it("prefers the runtime-selected provider when metadata is available", async () => {
|
|
const provider = createProvider({
|
|
pluginId: "firecrawl",
|
|
id: "firecrawl",
|
|
credentialPath: "plugins.entries.firecrawl.config.webFetch.apiKey",
|
|
autoDetectOrder: 1,
|
|
createTool: ({ runtimeMetadata }) => ({
|
|
description: "firecrawl",
|
|
parameters: {},
|
|
execute: async (args) => ({
|
|
...args,
|
|
provider: runtimeMetadata?.selectedProvider ?? "firecrawl",
|
|
}),
|
|
}),
|
|
});
|
|
resolveBundledPluginWebFetchProvidersMock.mockReturnValue([provider]);
|
|
resolveRuntimeWebFetchProvidersMock.mockReturnValue([provider]);
|
|
|
|
const runtimeWebFetch: RuntimeWebFetchMetadata = {
|
|
providerSource: "auto-detect",
|
|
selectedProvider: "firecrawl",
|
|
selectedProviderKeySource: "env",
|
|
diagnostics: [],
|
|
};
|
|
|
|
const resolved = resolveWebFetchDefinition({
|
|
config: {},
|
|
runtimeWebFetch,
|
|
preferRuntimeProviders: true,
|
|
});
|
|
|
|
expect(resolved?.provider.id).toBe("firecrawl");
|
|
await expect(
|
|
resolved?.definition.execute({
|
|
url: "https://example.com",
|
|
extractMode: "markdown",
|
|
maxChars: 1000,
|
|
}),
|
|
).resolves.toEqual({
|
|
url: "https://example.com",
|
|
extractMode: "markdown",
|
|
maxChars: 1000,
|
|
provider: "firecrawl",
|
|
});
|
|
});
|
|
|
|
it("auto-detects providers from provider-declared env vars", () => {
|
|
const provider = createProvider({
|
|
pluginId: "firecrawl",
|
|
id: "firecrawl",
|
|
credentialPath: "plugins.entries.firecrawl.config.webFetch.apiKey",
|
|
autoDetectOrder: 1,
|
|
});
|
|
resolveBundledPluginWebFetchProvidersMock.mockReturnValue([provider]);
|
|
vi.stubEnv("FIRECRAWL_API_KEY", "firecrawl-env-key");
|
|
|
|
const resolved = resolveWebFetchDefinition({
|
|
config: {},
|
|
});
|
|
|
|
expect(resolved?.provider.id).toBe("firecrawl");
|
|
});
|
|
|
|
it("falls back to auto-detect when the configured provider is invalid", () => {
|
|
const provider = createProvider({
|
|
pluginId: "firecrawl",
|
|
id: "firecrawl",
|
|
credentialPath: "plugins.entries.firecrawl.config.webFetch.apiKey",
|
|
autoDetectOrder: 1,
|
|
getConfiguredCredentialValue: () => "firecrawl-key",
|
|
});
|
|
resolveBundledPluginWebFetchProvidersMock.mockReturnValue([provider]);
|
|
|
|
const resolved = resolveWebFetchDefinition({
|
|
config: {
|
|
tools: {
|
|
web: {
|
|
fetch: {
|
|
provider: "does-not-exist",
|
|
},
|
|
},
|
|
},
|
|
} as OpenClawConfig,
|
|
});
|
|
|
|
expect(resolved?.provider.id).toBe("firecrawl");
|
|
});
|
|
|
|
it("keeps sandboxed web fetch on bundled providers even when runtime providers are preferred", () => {
|
|
const bundled = createProvider({
|
|
pluginId: "firecrawl",
|
|
id: "firecrawl",
|
|
credentialPath: "plugins.entries.firecrawl.config.webFetch.apiKey",
|
|
autoDetectOrder: 1,
|
|
getConfiguredCredentialValue: () => "bundled-key",
|
|
});
|
|
const runtimeOnly = createProvider({
|
|
pluginId: "third-party-fetch",
|
|
id: "thirdparty",
|
|
credentialPath: "plugins.entries.third-party-fetch.config.webFetch.apiKey",
|
|
autoDetectOrder: 0,
|
|
getConfiguredCredentialValue: () => "runtime-key",
|
|
});
|
|
resolveBundledPluginWebFetchProvidersMock.mockReturnValue([bundled]);
|
|
resolveRuntimeWebFetchProvidersMock.mockReturnValue([runtimeOnly]);
|
|
|
|
const resolved = resolveWebFetchDefinition({
|
|
config: {},
|
|
sandboxed: true,
|
|
preferRuntimeProviders: true,
|
|
});
|
|
|
|
expect(resolved?.provider.id).toBe("firecrawl");
|
|
});
|
|
|
|
it("keeps non-sandboxed web fetch on bundled providers even when runtime providers are preferred", () => {
|
|
const bundled = createProvider({
|
|
pluginId: "firecrawl",
|
|
id: "firecrawl",
|
|
credentialPath: "plugins.entries.firecrawl.config.webFetch.apiKey",
|
|
autoDetectOrder: 1,
|
|
getConfiguredCredentialValue: () => "bundled-key",
|
|
});
|
|
const runtimeOnly = createProvider({
|
|
pluginId: "third-party-fetch",
|
|
id: "thirdparty",
|
|
credentialPath: "plugins.entries.third-party-fetch.config.webFetch.apiKey",
|
|
autoDetectOrder: 0,
|
|
getConfiguredCredentialValue: () => "runtime-key",
|
|
});
|
|
resolveBundledPluginWebFetchProvidersMock.mockReturnValue([bundled]);
|
|
resolveRuntimeWebFetchProvidersMock.mockReturnValue([runtimeOnly]);
|
|
|
|
const resolved = resolveWebFetchDefinition({
|
|
config: {},
|
|
sandboxed: false,
|
|
preferRuntimeProviders: true,
|
|
});
|
|
|
|
expect(resolved?.provider.id).toBe("firecrawl");
|
|
});
|
|
});
|