mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-30 19:32:27 +00:00
200 lines
6.8 KiB
TypeScript
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(),
|
|
});
|
|
});
|
|
});
|