158 lines
4.0 KiB
TypeScript
158 lines
4.0 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
import { resolvePluginTools } from "./tools.js";
|
|
|
|
type MockRegistryToolEntry = {
|
|
pluginId: string;
|
|
optional: boolean;
|
|
source: string;
|
|
factory: (ctx: unknown) => unknown;
|
|
};
|
|
|
|
const loadOpenClawPluginsMock = vi.fn();
|
|
|
|
vi.mock("./loader.js", () => ({
|
|
loadOpenClawPlugins: (params: unknown) => loadOpenClawPluginsMock(params),
|
|
}));
|
|
|
|
function makeTool(name: string) {
|
|
return {
|
|
name,
|
|
description: `${name} tool`,
|
|
parameters: { type: "object", properties: {} },
|
|
async execute() {
|
|
return { content: [{ type: "text", text: "ok" }] };
|
|
},
|
|
};
|
|
}
|
|
|
|
function createContext() {
|
|
return {
|
|
config: {
|
|
plugins: {
|
|
enabled: true,
|
|
allow: ["optional-demo", "message", "multi"],
|
|
load: { paths: ["/tmp/plugin.js"] },
|
|
},
|
|
},
|
|
workspaceDir: "/tmp",
|
|
};
|
|
}
|
|
|
|
function setRegistry(entries: MockRegistryToolEntry[]) {
|
|
const registry = {
|
|
tools: entries,
|
|
diagnostics: [] as Array<{
|
|
level: string;
|
|
pluginId: string;
|
|
source: string;
|
|
message: string;
|
|
}>,
|
|
};
|
|
loadOpenClawPluginsMock.mockReturnValue(registry);
|
|
return registry;
|
|
}
|
|
|
|
describe("resolvePluginTools optional tools", () => {
|
|
beforeEach(() => {
|
|
loadOpenClawPluginsMock.mockReset();
|
|
});
|
|
|
|
it("skips optional tools without explicit allowlist", () => {
|
|
setRegistry([
|
|
{
|
|
pluginId: "optional-demo",
|
|
optional: true,
|
|
source: "/tmp/optional-demo.js",
|
|
factory: () => makeTool("optional_tool"),
|
|
},
|
|
]);
|
|
|
|
const tools = resolvePluginTools({
|
|
context: createContext() as never,
|
|
});
|
|
|
|
expect(tools).toHaveLength(0);
|
|
});
|
|
|
|
it("allows optional tools by tool name", () => {
|
|
setRegistry([
|
|
{
|
|
pluginId: "optional-demo",
|
|
optional: true,
|
|
source: "/tmp/optional-demo.js",
|
|
factory: () => makeTool("optional_tool"),
|
|
},
|
|
]);
|
|
|
|
const tools = resolvePluginTools({
|
|
context: createContext() as never,
|
|
toolAllowlist: ["optional_tool"],
|
|
});
|
|
|
|
expect(tools.map((tool) => tool.name)).toEqual(["optional_tool"]);
|
|
});
|
|
|
|
it("allows optional tools via plugin-scoped allowlist entries", () => {
|
|
setRegistry([
|
|
{
|
|
pluginId: "optional-demo",
|
|
optional: true,
|
|
source: "/tmp/optional-demo.js",
|
|
factory: () => makeTool("optional_tool"),
|
|
},
|
|
]);
|
|
|
|
const toolsByPlugin = resolvePluginTools({
|
|
context: createContext() as never,
|
|
toolAllowlist: ["optional-demo"],
|
|
});
|
|
const toolsByGroup = resolvePluginTools({
|
|
context: createContext() as never,
|
|
toolAllowlist: ["group:plugins"],
|
|
});
|
|
|
|
expect(toolsByPlugin.map((tool) => tool.name)).toEqual(["optional_tool"]);
|
|
expect(toolsByGroup.map((tool) => tool.name)).toEqual(["optional_tool"]);
|
|
});
|
|
|
|
it("rejects plugin id collisions with core tool names", () => {
|
|
const registry = setRegistry([
|
|
{
|
|
pluginId: "message",
|
|
optional: false,
|
|
source: "/tmp/message.js",
|
|
factory: () => makeTool("optional_tool"),
|
|
},
|
|
]);
|
|
|
|
const tools = resolvePluginTools({
|
|
context: createContext() as never,
|
|
existingToolNames: new Set(["message"]),
|
|
});
|
|
|
|
expect(tools).toHaveLength(0);
|
|
expect(registry.diagnostics).toHaveLength(1);
|
|
expect(registry.diagnostics[0]?.message).toContain("plugin id conflicts with core tool name");
|
|
});
|
|
|
|
it("skips conflicting tool names but keeps other tools", () => {
|
|
const registry = setRegistry([
|
|
{
|
|
pluginId: "multi",
|
|
optional: false,
|
|
source: "/tmp/multi.js",
|
|
factory: () => [makeTool("message"), makeTool("other_tool")],
|
|
},
|
|
]);
|
|
|
|
const tools = resolvePluginTools({
|
|
context: createContext() as never,
|
|
existingToolNames: new Set(["message"]),
|
|
});
|
|
|
|
expect(tools.map((tool) => tool.name)).toEqual(["other_tool"]);
|
|
expect(registry.diagnostics).toHaveLength(1);
|
|
expect(registry.diagnostics[0]?.message).toContain("plugin tool name conflict");
|
|
});
|
|
});
|