test: speed up plugin optional tools suite

This commit is contained in:
Peter Steinberger
2026-02-16 05:56:26 +00:00
parent 599195fb31
commit bfb5a44089

View File

@@ -1,183 +1,157 @@
import { randomUUID } from "node:crypto"; import { beforeEach, describe, expect, it, vi } from "vitest";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { afterAll, describe, expect, it } from "vitest";
import { resolvePluginTools } from "./tools.js"; import { resolvePluginTools } from "./tools.js";
type TempPlugin = { dir: string; file: string; id: string }; type MockRegistryToolEntry = {
pluginId: string;
optional: boolean;
source: string;
factory: (ctx: unknown) => unknown;
};
const fixtureRoot = path.join(os.tmpdir(), `openclaw-plugin-tools-${randomUUID()}`); const loadOpenClawPluginsMock = vi.fn();
const EMPTY_PLUGIN_SCHEMA = { type: "object", additionalProperties: false, properties: {} };
function makeFixtureDir(id: string) { vi.mock("./loader.js", () => ({
const dir = path.join(fixtureRoot, id); loadOpenClawPlugins: (params: unknown) => loadOpenClawPluginsMock(params),
fs.mkdirSync(dir, { recursive: true }); }));
return dir;
}
function writePlugin(params: { id: string; body: string }): TempPlugin { function makeTool(name: string) {
const dir = makeFixtureDir(params.id); return {
const file = path.join(dir, `${params.id}.js`); name,
fs.writeFileSync(file, params.body, "utf-8"); description: `${name} tool`,
fs.writeFileSync(
path.join(dir, "openclaw.plugin.json"),
JSON.stringify(
{
id: params.id,
configSchema: EMPTY_PLUGIN_SCHEMA,
},
null,
2,
),
"utf-8",
);
return { dir, file, id: params.id };
}
const pluginBody = `
export default { register(api) {
api.registerTool(
{
name: "optional_tool",
description: "optional tool",
parameters: { type: "object", properties: {} },
async execute() {
return { content: [{ type: "text", text: "ok" }] };
},
},
{ optional: true },
);
} }
`;
const optionalDemoPlugin = writePlugin({ id: "optional-demo", body: pluginBody });
const coreNameCollisionPlugin = writePlugin({ id: "message", body: pluginBody });
const multiToolPlugin = writePlugin({
id: "multi",
body: `
export default { register(api) {
api.registerTool({
name: "message",
description: "conflict",
parameters: { type: "object", properties: {} },
async execute() {
return { content: [{ type: "text", text: "nope" }] };
},
});
api.registerTool({
name: "other_tool",
description: "ok",
parameters: { type: "object", properties: {} }, parameters: { type: "object", properties: {} },
async execute() { async execute() {
return { content: [{ type: "text", text: "ok" }] }; return { content: [{ type: "text", text: "ok" }] };
}, },
}); };
} } }
`,
});
afterAll(() => { function createContext() {
try { return {
fs.rmSync(fixtureRoot, { recursive: true, force: true }); config: {
} catch { plugins: {
// ignore cleanup failures 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", () => { describe("resolvePluginTools optional tools", () => {
beforeEach(() => {
loadOpenClawPluginsMock.mockReset();
});
it("skips optional tools without explicit allowlist", () => { it("skips optional tools without explicit allowlist", () => {
const tools = resolvePluginTools({ setRegistry([
context: { {
config: { pluginId: "optional-demo",
plugins: { optional: true,
load: { paths: [optionalDemoPlugin.file] }, source: "/tmp/optional-demo.js",
allow: [optionalDemoPlugin.id], factory: () => makeTool("optional_tool"),
},
},
workspaceDir: optionalDemoPlugin.dir,
}, },
]);
const tools = resolvePluginTools({
context: createContext() as never,
}); });
expect(tools).toHaveLength(0); expect(tools).toHaveLength(0);
}); });
it("allows optional tools by name", () => { it("allows optional tools by tool name", () => {
const tools = resolvePluginTools({ setRegistry([
context: { {
config: { pluginId: "optional-demo",
plugins: { optional: true,
load: { paths: [optionalDemoPlugin.file] }, source: "/tmp/optional-demo.js",
allow: [optionalDemoPlugin.id], factory: () => makeTool("optional_tool"),
},
},
workspaceDir: optionalDemoPlugin.dir,
}, },
]);
const tools = resolvePluginTools({
context: createContext() as never,
toolAllowlist: ["optional_tool"], toolAllowlist: ["optional_tool"],
}); });
expect(tools.map((tool) => tool.name)).toContain("optional_tool");
expect(tools.map((tool) => tool.name)).toEqual(["optional_tool"]);
}); });
it("allows optional tools via plugin groups", () => { it("allows optional tools via plugin-scoped allowlist entries", () => {
const toolsAll = resolvePluginTools({ setRegistry([
context: { {
config: { pluginId: "optional-demo",
plugins: { optional: true,
load: { paths: [optionalDemoPlugin.file] }, source: "/tmp/optional-demo.js",
allow: [optionalDemoPlugin.id], factory: () => makeTool("optional_tool"),
},
},
workspaceDir: optionalDemoPlugin.dir,
}, },
toolAllowlist: ["group:plugins"], ]);
});
expect(toolsAll.map((tool) => tool.name)).toContain("optional_tool");
const toolsPlugin = resolvePluginTools({ const toolsByPlugin = resolvePluginTools({
context: { context: createContext() as never,
config: {
plugins: {
load: { paths: [optionalDemoPlugin.file] },
allow: [optionalDemoPlugin.id],
},
},
workspaceDir: optionalDemoPlugin.dir,
},
toolAllowlist: ["optional-demo"], toolAllowlist: ["optional-demo"],
}); });
expect(toolsPlugin.map((tool) => tool.name)).toContain("optional_tool"); 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", () => { it("rejects plugin id collisions with core tool names", () => {
const tools = resolvePluginTools({ const registry = setRegistry([
context: { {
config: { pluginId: "message",
plugins: { optional: false,
load: { paths: [coreNameCollisionPlugin.file] }, source: "/tmp/message.js",
allow: [coreNameCollisionPlugin.id], factory: () => makeTool("optional_tool"),
},
},
workspaceDir: coreNameCollisionPlugin.dir,
}, },
]);
const tools = resolvePluginTools({
context: createContext() as never,
existingToolNames: new Set(["message"]), existingToolNames: new Set(["message"]),
toolAllowlist: ["message"],
}); });
expect(tools).toHaveLength(0); 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", () => { it("skips conflicting tool names but keeps other tools", () => {
const tools = resolvePluginTools({ const registry = setRegistry([
context: { {
config: { pluginId: "multi",
plugins: { optional: false,
load: { paths: [multiToolPlugin.file] }, source: "/tmp/multi.js",
allow: [multiToolPlugin.id], factory: () => [makeTool("message"), makeTool("other_tool")],
},
},
workspaceDir: multiToolPlugin.dir,
}, },
]);
const tools = resolvePluginTools({
context: createContext() as never,
existingToolNames: new Set(["message"]), existingToolNames: new Set(["message"]),
}); });
expect(tools.map((tool) => tool.name)).toEqual(["other_tool"]); expect(tools.map((tool) => tool.name)).toEqual(["other_tool"]);
expect(registry.diagnostics).toHaveLength(1);
expect(registry.diagnostics[0]?.message).toContain("plugin tool name conflict");
}); });
}); });