Files
openclaw/src/auto-reply/reply/commands-plugins.test.ts
2026-03-23 00:29:20 -07:00

109 lines
4.0 KiB
TypeScript

import fs from "node:fs/promises";
import path from "node:path";
import { afterEach, describe, expect, it } from "vitest";
import type { OpenClawConfig } from "../../config/config.js";
import { withTempHome } from "../../config/home-env.test-harness.js";
import { handleCommands } from "./commands-core.js";
import { createCommandWorkspaceHarness } from "./commands-filesystem.test-support.js";
import { buildCommandTestParams } from "./commands.test-harness.js";
const workspaceHarness = createCommandWorkspaceHarness("openclaw-command-plugins-");
async function createClaudeBundlePlugin(params: { workspaceDir: string; pluginId: string }) {
const pluginDir = path.join(params.workspaceDir, ".openclaw", "extensions", params.pluginId);
await fs.mkdir(path.join(pluginDir, ".claude-plugin"), { recursive: true });
await fs.mkdir(path.join(pluginDir, "commands"), { recursive: true });
await fs.writeFile(
path.join(pluginDir, ".claude-plugin", "plugin.json"),
JSON.stringify({ name: params.pluginId }, null, 2),
"utf-8",
);
await fs.writeFile(path.join(pluginDir, "commands", "review.md"), "# Review\n", "utf-8");
}
function buildCfg(): OpenClawConfig {
return {
plugins: {
enabled: true,
},
commands: {
text: true,
plugins: true,
},
};
}
describe("handleCommands /plugins", () => {
afterEach(async () => {
await workspaceHarness.cleanupWorkspaces();
});
it("lists discovered plugins and inspects plugin details", async () => {
await withTempHome("openclaw-command-plugins-home-", async () => {
const workspaceDir = await workspaceHarness.createWorkspace();
await createClaudeBundlePlugin({ workspaceDir, pluginId: "superpowers" });
const listParams = buildCommandTestParams("/plugins list", buildCfg(), undefined, {
workspaceDir,
});
listParams.command.senderIsOwner = true;
const listResult = await handleCommands(listParams);
expect(listResult.reply?.text).toContain("Plugins");
expect(listResult.reply?.text).toContain("superpowers");
expect(listResult.reply?.text).toContain("[disabled]");
const showParams = buildCommandTestParams(
"/plugins inspect superpowers",
buildCfg(),
undefined,
{
workspaceDir,
},
);
showParams.command.senderIsOwner = true;
const showResult = await handleCommands(showParams);
expect(showResult.reply?.text).toContain('"id": "superpowers"');
expect(showResult.reply?.text).toContain('"bundleFormat": "claude"');
expect(showResult.reply?.text).toContain('"shape":');
expect(showResult.reply?.text).toContain('"compatibilityWarnings": []');
const inspectAllParams = buildCommandTestParams(
"/plugins inspect all",
buildCfg(),
undefined,
{
workspaceDir,
},
);
inspectAllParams.command.senderIsOwner = true;
const inspectAllResult = await handleCommands(inspectAllParams);
expect(inspectAllResult.reply?.text).toContain("```json");
expect(inspectAllResult.reply?.text).toContain('"plugin"');
expect(inspectAllResult.reply?.text).toContain('"compatibilityWarnings"');
expect(inspectAllResult.reply?.text).toContain('"superpowers"');
});
});
it("rejects internal writes without operator.admin", async () => {
await withTempHome("openclaw-command-plugins-home-", async () => {
const workspaceDir = await workspaceHarness.createWorkspace();
await createClaudeBundlePlugin({ workspaceDir, pluginId: "superpowers" });
const params = buildCommandTestParams(
"/plugins enable superpowers",
buildCfg(),
{
Provider: "webchat",
Surface: "webchat",
GatewayClientScopes: ["operator.write"],
},
{ workspaceDir },
);
params.command.senderIsOwner = true;
const result = await handleCommands(params);
expect(result.reply?.text).toContain("requires operator.admin");
});
});
});