mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-19 09:58:38 +00:00
test: speed up plugin optional tools suite
This commit is contained in:
@@ -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");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user