mirror of https://github.com/openclaw/openclaw.git
test: dedupe plugin provider runtime suites
This commit is contained in:
parent
de173f0e3e
commit
b4c38c78f3
|
|
@ -70,6 +70,18 @@ function expectBundledCompatLoadPath(params: {
|
|||
});
|
||||
}
|
||||
|
||||
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: [
|
||||
|
|
@ -137,14 +149,7 @@ describe("resolvePluginCapabilityProviders", () => {
|
|||
["mediaUnderstandingProviders", "mediaUnderstandingProviders"],
|
||||
["imageGenerationProviders", "imageGenerationProviders"],
|
||||
] as const)("applies bundled compat before fallback loading for %s", (key, contractKey) => {
|
||||
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 } },
|
||||
},
|
||||
};
|
||||
const { cfg, allowlistCompat, enablementCompat } = createCompatChainConfig();
|
||||
setBundledCapabilityFixture(contractKey);
|
||||
mocks.withBundledPluginAllowlistCompat.mockReturnValue(allowlistCompat);
|
||||
mocks.withBundledPluginEnablementCompat.mockReturnValue(enablementCompat);
|
||||
|
|
|
|||
|
|
@ -30,6 +30,10 @@ function expectDiagnosticMessages(
|
|||
);
|
||||
}
|
||||
|
||||
function expectDiagnosticText(diagnostics: PluginDiagnostic[], messages: readonly string[]) {
|
||||
expect(diagnostics.map((diag) => diag.message)).toEqual([...messages]);
|
||||
}
|
||||
|
||||
function normalizeProviderFixture(provider: ProviderPlugin) {
|
||||
const { diagnostics, pushDiagnostic } = collectDiagnostics();
|
||||
const normalizedProvider = normalizeRegisteredProvider({
|
||||
|
|
@ -155,7 +159,7 @@ describe("normalizeRegisteredProvider", () => {
|
|||
diagnostics: PluginDiagnostic[],
|
||||
) => {
|
||||
expect(provider?.wizard).toBeUndefined();
|
||||
expect(diagnostics.map((diag) => diag.message)).toEqual([
|
||||
expectDiagnosticText(diagnostics, [
|
||||
'provider "demo" setup metadata ignored because it has no auth methods',
|
||||
'provider "demo" model-picker metadata ignored because it has no auth methods',
|
||||
]);
|
||||
|
|
@ -195,7 +199,7 @@ describe("normalizeRegisteredProvider", () => {
|
|||
|
||||
expect(provider?.catalog).toBeDefined();
|
||||
expect(provider?.discovery).toBeUndefined();
|
||||
expect(diagnostics.map((diag) => diag.message)).toEqual([
|
||||
expectDiagnosticText(diagnostics, [
|
||||
'provider "demo" registered both catalog and discovery; using catalog',
|
||||
]);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -95,6 +95,13 @@ function expectNoCompatibilityWarnings() {
|
|||
expect(buildPluginCompatibilityWarnings()).toEqual([]);
|
||||
}
|
||||
|
||||
function expectCapabilityKinds(
|
||||
inspect: NonNullable<ReturnType<typeof buildPluginInspectReport>>,
|
||||
kinds: readonly string[],
|
||||
) {
|
||||
expect(inspect.capabilities.map((entry) => entry.kind)).toEqual(kinds);
|
||||
}
|
||||
|
||||
describe("buildPluginStatusReport", () => {
|
||||
beforeEach(async () => {
|
||||
vi.resetModules();
|
||||
|
|
@ -230,7 +237,7 @@ describe("buildPluginStatusReport", () => {
|
|||
expect(inspect).not.toBeNull();
|
||||
expect(inspect?.shape).toBe("hybrid-capability");
|
||||
expect(inspect?.capabilityMode).toBe("hybrid");
|
||||
expect(inspect?.capabilities.map((entry) => entry.kind)).toEqual([
|
||||
expectCapabilityKinds(inspect!, [
|
||||
"cli-backend",
|
||||
"text-inference",
|
||||
"media-understanding",
|
||||
|
|
@ -279,10 +286,7 @@ describe("buildPluginStatusReport", () => {
|
|||
expect(inspect.map((entry) => entry.plugin.id)).toEqual(["lca", "microsoft"]);
|
||||
expect(inspect.map((entry) => entry.shape)).toEqual(["hook-only", "hybrid-capability"]);
|
||||
expect(inspect[0]?.usesLegacyBeforeAgentStart).toBe(true);
|
||||
expect(inspect[1]?.capabilities.map((entry) => entry.kind)).toEqual([
|
||||
"text-inference",
|
||||
"web-search",
|
||||
]);
|
||||
expectCapabilityKinds(inspect[1], ["text-inference", "web-search"]);
|
||||
});
|
||||
|
||||
it("treats a CLI-backend-only plugin as a plain capability", () => {
|
||||
|
|
@ -298,6 +302,7 @@ describe("buildPluginStatusReport", () => {
|
|||
|
||||
expect(inspect?.shape).toBe("plain-capability");
|
||||
expect(inspect?.capabilityMode).toBe("plain");
|
||||
expectCapabilityKinds(inspect!, ["cli-backend"]);
|
||||
expect(inspect?.capabilities).toEqual([{ kind: "cli-backend", ids: ["claude-cli"] }]);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -117,6 +117,14 @@ function expectLoaderCall(overrides: Record<string, unknown>) {
|
|||
expect(loadOpenClawPluginsMock).toHaveBeenCalledWith(expect.objectContaining(overrides));
|
||||
}
|
||||
|
||||
function expectSingleDiagnosticMessage(
|
||||
diagnostics: Array<{ message: string }>,
|
||||
messageFragment: string,
|
||||
) {
|
||||
expect(diagnostics).toHaveLength(1);
|
||||
expect(diagnostics[0]?.message).toContain(messageFragment);
|
||||
}
|
||||
|
||||
describe("resolvePluginTools optional tools", () => {
|
||||
beforeEach(async () => {
|
||||
vi.resetModules();
|
||||
|
|
@ -170,8 +178,7 @@ describe("resolvePluginTools optional tools", () => {
|
|||
);
|
||||
|
||||
expect(tools).toHaveLength(0);
|
||||
expect(registry.diagnostics).toHaveLength(1);
|
||||
expect(registry.diagnostics[0]?.message).toContain("plugin id conflicts with core tool name");
|
||||
expectSingleDiagnosticMessage(registry.diagnostics, "plugin id conflicts with core tool name");
|
||||
});
|
||||
|
||||
it("skips conflicting tool names but keeps other tools", () => {
|
||||
|
|
@ -179,8 +186,7 @@ describe("resolvePluginTools optional tools", () => {
|
|||
const tools = resolveWithConflictingCoreName();
|
||||
|
||||
expectResolvedToolNames(tools, ["other_tool"]);
|
||||
expect(registry.diagnostics).toHaveLength(1);
|
||||
expect(registry.diagnostics[0]?.message).toContain("plugin tool name conflict");
|
||||
expectSingleDiagnosticMessage(registry.diagnostics, "plugin tool name conflict");
|
||||
});
|
||||
|
||||
it("suppresses conflict diagnostics when requested", () => {
|
||||
|
|
|
|||
|
|
@ -131,6 +131,51 @@ function expectBundledRuntimeProviderKeys(
|
|||
);
|
||||
}
|
||||
|
||||
function createManifestRegistryFixture() {
|
||||
return {
|
||||
plugins: [
|
||||
{
|
||||
id: "brave",
|
||||
origin: "bundled",
|
||||
rootDir: "/tmp/brave",
|
||||
source: "/tmp/brave/index.js",
|
||||
manifestPath: "/tmp/brave/openclaw.plugin.json",
|
||||
channels: [],
|
||||
providers: [],
|
||||
skills: [],
|
||||
hooks: [],
|
||||
configUiHints: { "webSearch.apiKey": { label: "key" } },
|
||||
},
|
||||
{
|
||||
id: "noise",
|
||||
origin: "bundled",
|
||||
rootDir: "/tmp/noise",
|
||||
source: "/tmp/noise/index.js",
|
||||
manifestPath: "/tmp/noise/openclaw.plugin.json",
|
||||
channels: [],
|
||||
providers: [],
|
||||
skills: [],
|
||||
hooks: [],
|
||||
configUiHints: { unrelated: { label: "nope" } },
|
||||
},
|
||||
],
|
||||
diagnostics: [],
|
||||
};
|
||||
}
|
||||
|
||||
function expectLoaderCallCount(count: number) {
|
||||
expect(loadOpenClawPluginsMock).toHaveBeenCalledTimes(count);
|
||||
}
|
||||
|
||||
function expectScopedWebSearchCandidates(pluginIds: readonly string[]) {
|
||||
expect(loadPluginManifestRegistryMock).toHaveBeenCalled();
|
||||
expect(loadOpenClawPluginsMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
onlyPluginIds: [...pluginIds],
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
describe("resolvePluginWebSearchProviders", () => {
|
||||
beforeAll(async () => {
|
||||
({ createEmptyPluginRegistry } = await import("./registry.js"));
|
||||
|
|
@ -148,39 +193,13 @@ describe("resolvePluginWebSearchProviders", () => {
|
|||
resetWebSearchProviderSnapshotCacheForTests();
|
||||
loadPluginManifestRegistryMock = vi
|
||||
.spyOn(manifestRegistryModule, "loadPluginManifestRegistry")
|
||||
.mockReturnValue({
|
||||
plugins: [
|
||||
{
|
||||
id: "brave",
|
||||
origin: "bundled",
|
||||
rootDir: "/tmp/brave",
|
||||
source: "/tmp/brave/index.js",
|
||||
manifestPath: "/tmp/brave/openclaw.plugin.json",
|
||||
channels: [],
|
||||
providers: [],
|
||||
skills: [],
|
||||
hooks: [],
|
||||
configUiHints: { "webSearch.apiKey": { label: "key" } },
|
||||
},
|
||||
{
|
||||
id: "noise",
|
||||
origin: "bundled",
|
||||
rootDir: "/tmp/noise",
|
||||
source: "/tmp/noise/index.js",
|
||||
manifestPath: "/tmp/noise/openclaw.plugin.json",
|
||||
channels: [],
|
||||
providers: [],
|
||||
skills: [],
|
||||
hooks: [],
|
||||
configUiHints: { unrelated: { label: "nope" } },
|
||||
},
|
||||
],
|
||||
diagnostics: [],
|
||||
} as ManifestRegistryModule["loadPluginManifestRegistry"] extends (
|
||||
...args: unknown[]
|
||||
) => infer R
|
||||
? R
|
||||
: never);
|
||||
.mockReturnValue(
|
||||
createManifestRegistryFixture() as ManifestRegistryModule["loadPluginManifestRegistry"] extends (
|
||||
...args: unknown[]
|
||||
) => infer R
|
||||
? R
|
||||
: never,
|
||||
);
|
||||
loadOpenClawPluginsMock = vi
|
||||
.spyOn(loaderModule, "loadOpenClawPlugins")
|
||||
.mockImplementation((params) => {
|
||||
|
|
@ -201,18 +220,13 @@ describe("resolvePluginWebSearchProviders", () => {
|
|||
const providers = resolvePluginWebSearchProviders({});
|
||||
|
||||
expectBundledRuntimeProviderKeys(providers);
|
||||
expect(loadOpenClawPluginsMock).toHaveBeenCalledTimes(1);
|
||||
expectLoaderCallCount(1);
|
||||
});
|
||||
|
||||
it("scopes plugin loading to manifest-declared web-search candidates", () => {
|
||||
resolvePluginWebSearchProviders({});
|
||||
|
||||
expect(loadPluginManifestRegistryMock).toHaveBeenCalled();
|
||||
expect(loadOpenClawPluginsMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
onlyPluginIds: ["brave"],
|
||||
}),
|
||||
);
|
||||
expectScopedWebSearchCandidates(["brave"]);
|
||||
});
|
||||
|
||||
it("memoizes snapshot provider resolution for the same config and env", () => {
|
||||
|
|
@ -224,7 +238,7 @@ describe("resolvePluginWebSearchProviders", () => {
|
|||
const second = resolvePluginWebSearchProviders(runtimeParams);
|
||||
|
||||
expect(second).toBe(first);
|
||||
expect(loadOpenClawPluginsMock).toHaveBeenCalledTimes(1);
|
||||
expectLoaderCallCount(1);
|
||||
});
|
||||
|
||||
it("invalidates the snapshot cache when config or env contents change in place", () => {
|
||||
|
|
@ -236,7 +250,7 @@ describe("resolvePluginWebSearchProviders", () => {
|
|||
env.OPENCLAW_HOME = "/tmp/openclaw-home-b";
|
||||
resolvePluginWebSearchProviders(createSnapshotParams({ config, env }));
|
||||
|
||||
expect(loadOpenClawPluginsMock).toHaveBeenCalledTimes(2);
|
||||
expectLoaderCallCount(2);
|
||||
});
|
||||
|
||||
it.each([
|
||||
|
|
@ -258,7 +272,7 @@ describe("resolvePluginWebSearchProviders", () => {
|
|||
resolvePluginWebSearchProviders(createSnapshotParams({ config, env: createWebSearchEnv(env) }));
|
||||
resolvePluginWebSearchProviders(createSnapshotParams({ config, env: createWebSearchEnv(env) }));
|
||||
|
||||
expect(loadOpenClawPluginsMock).toHaveBeenCalledTimes(2);
|
||||
expectLoaderCallCount(2);
|
||||
});
|
||||
|
||||
it("does not leak host Vitest env into an explicit non-Vitest cache key", () => {
|
||||
|
|
@ -313,7 +327,7 @@ describe("resolvePluginWebSearchProviders", () => {
|
|||
|
||||
resolvePluginWebSearchProviders(createSnapshotParams({ config, env }));
|
||||
|
||||
expect(loadOpenClawPluginsMock).toHaveBeenCalledTimes(2);
|
||||
expectLoaderCallCount(2);
|
||||
});
|
||||
|
||||
it("prefers the active plugin registry for runtime resolution", () => {
|
||||
|
|
|
|||
|
|
@ -51,6 +51,13 @@ function expectBundledWebSearchProviders(
|
|||
);
|
||||
}
|
||||
|
||||
function expectResolvedPluginIds(
|
||||
providers: ReturnType<typeof resolveBundledPluginWebSearchProviders>,
|
||||
expectedPluginIds: readonly string[],
|
||||
) {
|
||||
expect(providers.map((provider) => provider.pluginId)).toEqual(expectedPluginIds);
|
||||
}
|
||||
|
||||
describe("resolveBundledPluginWebSearchProviders", () => {
|
||||
it(
|
||||
"returns bundled providers in alphabetical order",
|
||||
|
|
@ -123,7 +130,7 @@ describe("resolveBundledPluginWebSearchProviders", () => {
|
|||
])("$title", ({ params, expectedPluginIds }) => {
|
||||
const providers = resolveBundledPluginWebSearchProviders(params);
|
||||
|
||||
expect(providers.map((provider) => provider.pluginId)).toEqual(expectedPluginIds);
|
||||
expectResolvedPluginIds(providers, expectedPluginIds);
|
||||
});
|
||||
|
||||
it("preserves explicit bundled provider entry state", () => {
|
||||
|
|
|
|||
Loading…
Reference in New Issue