Files
openclaw/src/plugins/capability-provider-runtime.test.ts
2026-03-28 04:28:54 +00:00

200 lines
6.8 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from "vitest";
import type { OpenClawConfig } from "../config/config.js";
import { createEmptyPluginRegistry } from "./registry.js";
type MockManifestRegistry = {
plugins: Array<Record<string, unknown>>;
diagnostics: unknown[];
};
function createEmptyMockManifestRegistry(): MockManifestRegistry {
return { plugins: [], diagnostics: [] };
}
const mocks = vi.hoisted(() => ({
resolveRuntimePluginRegistry: vi.fn<
(params?: unknown) => ReturnType<typeof createEmptyPluginRegistry> | undefined
>(() => undefined),
loadPluginManifestRegistry: vi.fn<() => MockManifestRegistry>(() =>
createEmptyMockManifestRegistry(),
),
withBundledPluginAllowlistCompat: vi.fn(({ config }) => config),
withBundledPluginEnablementCompat: vi.fn(({ config }) => config),
withBundledPluginVitestCompat: vi.fn(({ config }) => config),
}));
vi.mock("./loader.js", () => ({
resolveRuntimePluginRegistry: mocks.resolveRuntimePluginRegistry,
}));
vi.mock("./manifest-registry.js", () => ({
loadPluginManifestRegistry: mocks.loadPluginManifestRegistry,
}));
vi.mock("./bundled-compat.js", () => ({
withBundledPluginAllowlistCompat: mocks.withBundledPluginAllowlistCompat,
withBundledPluginEnablementCompat: mocks.withBundledPluginEnablementCompat,
withBundledPluginVitestCompat: mocks.withBundledPluginVitestCompat,
}));
let resolvePluginCapabilityProviders: typeof import("./capability-provider-runtime.js").resolvePluginCapabilityProviders;
function expectResolvedCapabilityProviderIds(providers: Array<{ id: string }>, expected: string[]) {
expect(providers.map((provider) => provider.id)).toEqual(expected);
}
function expectBundledCompatLoadPath(params: {
cfg: OpenClawConfig;
allowlistCompat: { plugins: { allow: string[] } };
enablementCompat: {
plugins: {
allow: string[];
entries: { openai: { enabled: boolean } };
};
};
}) {
expect(mocks.loadPluginManifestRegistry).toHaveBeenCalledWith({
config: params.cfg,
env: process.env,
});
expect(mocks.withBundledPluginAllowlistCompat).toHaveBeenCalledWith({
config: params.cfg,
pluginIds: ["openai"],
});
expect(mocks.withBundledPluginEnablementCompat).toHaveBeenCalledWith({
config: params.allowlistCompat,
pluginIds: ["openai"],
});
expect(mocks.withBundledPluginVitestCompat).toHaveBeenCalledWith({
config: params.enablementCompat,
pluginIds: ["openai"],
env: process.env,
});
expect(mocks.resolveRuntimePluginRegistry).toHaveBeenCalledWith({
config: params.enablementCompat,
});
}
function createCompatChainConfig() {
const cfg = { plugins: { allow: ["custom-plugin"] } } as OpenClawConfig;
const allowlistCompat = { plugins: { allow: ["custom-plugin", "openai"] } };
const enablementCompat = {
plugins: {
allow: ["custom-plugin", "openai"],
entries: { openai: { enabled: true } },
},
};
return { cfg, allowlistCompat, enablementCompat };
}
function setBundledCapabilityFixture(contractKey: string) {
mocks.loadPluginManifestRegistry.mockReturnValue({
plugins: [
{
id: "openai",
origin: "bundled",
contracts: { [contractKey]: ["openai"] },
},
{
id: "custom-plugin",
origin: "workspace",
contracts: {},
},
] as never,
diagnostics: [],
});
}
function expectCompatChainApplied(params: {
key: "speechProviders" | "mediaUnderstandingProviders" | "imageGenerationProviders";
contractKey: string;
cfg: OpenClawConfig;
allowlistCompat: { plugins: { allow: string[] } };
enablementCompat: {
plugins: {
allow: string[];
entries: { openai: { enabled: boolean } };
};
};
}) {
setBundledCapabilityFixture(params.contractKey);
mocks.withBundledPluginAllowlistCompat.mockReturnValue(params.allowlistCompat);
mocks.withBundledPluginEnablementCompat.mockReturnValue(params.enablementCompat);
mocks.withBundledPluginVitestCompat.mockReturnValue(params.enablementCompat);
resolvePluginCapabilityProviders({ key: params.key, cfg: params.cfg });
expectBundledCompatLoadPath(params);
}
describe("resolvePluginCapabilityProviders", () => {
beforeEach(async () => {
vi.resetModules();
mocks.resolveRuntimePluginRegistry.mockReset();
mocks.resolveRuntimePluginRegistry.mockReturnValue(undefined);
mocks.loadPluginManifestRegistry.mockReset();
mocks.loadPluginManifestRegistry.mockReturnValue(createEmptyMockManifestRegistry());
mocks.withBundledPluginAllowlistCompat.mockReset();
mocks.withBundledPluginAllowlistCompat.mockImplementation(({ config }) => config);
mocks.withBundledPluginEnablementCompat.mockReset();
mocks.withBundledPluginEnablementCompat.mockImplementation(({ config }) => config);
mocks.withBundledPluginVitestCompat.mockReset();
mocks.withBundledPluginVitestCompat.mockImplementation(({ config }) => config);
({ resolvePluginCapabilityProviders } = await import("./capability-provider-runtime.js"));
});
it("uses the active registry when capability providers are already loaded", () => {
const active = createEmptyPluginRegistry();
active.speechProviders.push({
pluginId: "openai",
pluginName: "OpenAI",
source: "test",
provider: {
id: "openai",
label: "OpenAI",
isConfigured: () => true,
synthesize: async () => ({
audioBuffer: Buffer.from("x"),
outputFormat: "mp3",
voiceCompatible: false,
fileExtension: ".mp3",
}),
},
});
mocks.resolveRuntimePluginRegistry.mockReturnValue(active);
const providers = resolvePluginCapabilityProviders({ key: "speechProviders" });
expectResolvedCapabilityProviderIds(providers, ["openai"]);
expect(mocks.loadPluginManifestRegistry).not.toHaveBeenCalled();
expect(mocks.resolveRuntimePluginRegistry).toHaveBeenCalledWith(undefined);
});
it.each([
["speechProviders", "speechProviders"],
["mediaUnderstandingProviders", "mediaUnderstandingProviders"],
["imageGenerationProviders", "imageGenerationProviders"],
] as const)("applies bundled compat before fallback loading for %s", (key, contractKey) => {
const { cfg, allowlistCompat, enablementCompat } = createCompatChainConfig();
expectCompatChainApplied({
key,
contractKey,
cfg,
allowlistCompat,
enablementCompat,
});
});
it("reuses a compatible active registry even when the capability list is empty", () => {
const active = createEmptyPluginRegistry();
mocks.resolveRuntimePluginRegistry.mockReturnValue(active);
const providers = resolvePluginCapabilityProviders({
key: "mediaUnderstandingProviders",
cfg: {} as OpenClawConfig,
});
expect(providers).toEqual([]);
expect(mocks.resolveRuntimePluginRegistry).toHaveBeenCalledWith({
config: expect.anything(),
});
});
});