231 lines
6.1 KiB
TypeScript
231 lines
6.1 KiB
TypeScript
import path from "node:path";
|
|
import { describe, expect, it } from "vitest";
|
|
import {
|
|
ACPX_BUNDLED_BIN,
|
|
ACPX_PINNED_VERSION,
|
|
createAcpxPluginConfigSchema,
|
|
resolveAcpxPluginConfig,
|
|
toAcpMcpServers,
|
|
} from "./config.js";
|
|
|
|
describe("acpx plugin config parsing", () => {
|
|
it("resolves bundled acpx with pinned version by default", () => {
|
|
const resolved = resolveAcpxPluginConfig({
|
|
rawConfig: {
|
|
cwd: "/tmp/workspace",
|
|
},
|
|
workspaceDir: "/tmp/workspace",
|
|
});
|
|
|
|
expect(resolved.command).toBe(ACPX_BUNDLED_BIN);
|
|
expect(resolved.expectedVersion).toBe(ACPX_PINNED_VERSION);
|
|
expect(resolved.allowPluginLocalInstall).toBe(true);
|
|
expect(resolved.cwd).toBe(path.resolve("/tmp/workspace"));
|
|
expect(resolved.strictWindowsCmdWrapper).toBe(true);
|
|
expect(resolved.mcpServers).toEqual({});
|
|
});
|
|
|
|
it("accepts command override and disables plugin-local auto-install", () => {
|
|
const command = "/home/user/repos/acpx/dist/cli.js";
|
|
const resolved = resolveAcpxPluginConfig({
|
|
rawConfig: {
|
|
command,
|
|
},
|
|
workspaceDir: "/tmp/workspace",
|
|
});
|
|
|
|
expect(resolved.command).toBe(path.resolve(command));
|
|
expect(resolved.expectedVersion).toBeUndefined();
|
|
expect(resolved.allowPluginLocalInstall).toBe(false);
|
|
});
|
|
|
|
it("resolves relative command paths against workspace directory", () => {
|
|
const resolved = resolveAcpxPluginConfig({
|
|
rawConfig: {
|
|
command: "../acpx/dist/cli.js",
|
|
},
|
|
workspaceDir: "/home/user/repos/openclaw",
|
|
});
|
|
|
|
expect(resolved.command).toBe(path.resolve("/home/user/repos/openclaw", "../acpx/dist/cli.js"));
|
|
expect(resolved.expectedVersion).toBeUndefined();
|
|
expect(resolved.allowPluginLocalInstall).toBe(false);
|
|
});
|
|
|
|
it("keeps bare command names as-is", () => {
|
|
const resolved = resolveAcpxPluginConfig({
|
|
rawConfig: {
|
|
command: "acpx",
|
|
},
|
|
workspaceDir: "/tmp/workspace",
|
|
});
|
|
|
|
expect(resolved.command).toBe("acpx");
|
|
expect(resolved.expectedVersion).toBeUndefined();
|
|
expect(resolved.allowPluginLocalInstall).toBe(false);
|
|
});
|
|
|
|
it("accepts exact expectedVersion override", () => {
|
|
const command = "/home/user/repos/acpx/dist/cli.js";
|
|
const resolved = resolveAcpxPluginConfig({
|
|
rawConfig: {
|
|
command,
|
|
expectedVersion: "0.1.99",
|
|
},
|
|
workspaceDir: "/tmp/workspace",
|
|
});
|
|
|
|
expect(resolved.command).toBe(path.resolve(command));
|
|
expect(resolved.expectedVersion).toBe("0.1.99");
|
|
expect(resolved.allowPluginLocalInstall).toBe(false);
|
|
});
|
|
|
|
it("treats expectedVersion=any as no version constraint", () => {
|
|
const resolved = resolveAcpxPluginConfig({
|
|
rawConfig: {
|
|
command: "/home/user/repos/acpx/dist/cli.js",
|
|
expectedVersion: "any",
|
|
},
|
|
workspaceDir: "/tmp/workspace",
|
|
});
|
|
|
|
expect(resolved.expectedVersion).toBeUndefined();
|
|
});
|
|
|
|
it("rejects commandArgs overrides", () => {
|
|
expect(() =>
|
|
resolveAcpxPluginConfig({
|
|
rawConfig: {
|
|
commandArgs: ["--foo"],
|
|
},
|
|
workspaceDir: "/tmp/workspace",
|
|
}),
|
|
).toThrow("unknown config key: commandArgs");
|
|
});
|
|
|
|
it("schema rejects empty cwd", () => {
|
|
const schema = createAcpxPluginConfigSchema();
|
|
if (!schema.safeParse) {
|
|
throw new Error("acpx config schema missing safeParse");
|
|
}
|
|
const parsed = schema.safeParse({ cwd: " " });
|
|
|
|
expect(parsed.success).toBe(false);
|
|
});
|
|
|
|
it("accepts strictWindowsCmdWrapper override", () => {
|
|
const resolved = resolveAcpxPluginConfig({
|
|
rawConfig: {
|
|
strictWindowsCmdWrapper: true,
|
|
},
|
|
workspaceDir: "/tmp/workspace",
|
|
});
|
|
|
|
expect(resolved.strictWindowsCmdWrapper).toBe(true);
|
|
});
|
|
|
|
it("rejects non-boolean strictWindowsCmdWrapper", () => {
|
|
expect(() =>
|
|
resolveAcpxPluginConfig({
|
|
rawConfig: {
|
|
strictWindowsCmdWrapper: "yes",
|
|
},
|
|
workspaceDir: "/tmp/workspace",
|
|
}),
|
|
).toThrow("strictWindowsCmdWrapper must be a boolean");
|
|
});
|
|
|
|
it("accepts mcp server maps", () => {
|
|
const resolved = resolveAcpxPluginConfig({
|
|
rawConfig: {
|
|
mcpServers: {
|
|
canva: {
|
|
command: "npx",
|
|
args: ["-y", "mcp-remote@latest", "https://mcp.canva.com/mcp"],
|
|
env: {
|
|
CANVA_TOKEN: "secret",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
workspaceDir: "/tmp/workspace",
|
|
});
|
|
|
|
expect(resolved.mcpServers).toEqual({
|
|
canva: {
|
|
command: "npx",
|
|
args: ["-y", "mcp-remote@latest", "https://mcp.canva.com/mcp"],
|
|
env: {
|
|
CANVA_TOKEN: "secret",
|
|
},
|
|
},
|
|
});
|
|
});
|
|
|
|
it("rejects invalid mcp server definitions", () => {
|
|
expect(() =>
|
|
resolveAcpxPluginConfig({
|
|
rawConfig: {
|
|
mcpServers: {
|
|
canva: {
|
|
command: "npx",
|
|
args: ["-y", 1],
|
|
},
|
|
},
|
|
},
|
|
workspaceDir: "/tmp/workspace",
|
|
}),
|
|
).toThrow(
|
|
"mcpServers.canva must have a command string, optional args array, and optional env object",
|
|
);
|
|
});
|
|
|
|
it("schema accepts mcp server config", () => {
|
|
const schema = createAcpxPluginConfigSchema();
|
|
if (!schema.safeParse) {
|
|
throw new Error("acpx config schema missing safeParse");
|
|
}
|
|
const parsed = schema.safeParse({
|
|
mcpServers: {
|
|
canva: {
|
|
command: "npx",
|
|
args: ["-y", "mcp-remote@latest"],
|
|
env: {
|
|
CANVA_TOKEN: "secret",
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
expect(parsed.success).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe("toAcpMcpServers", () => {
|
|
it("converts plugin config maps into ACP stdio MCP entries", () => {
|
|
expect(
|
|
toAcpMcpServers({
|
|
canva: {
|
|
command: "npx",
|
|
args: ["-y", "mcp-remote@latest", "https://mcp.canva.com/mcp"],
|
|
env: {
|
|
CANVA_TOKEN: "secret",
|
|
},
|
|
},
|
|
}),
|
|
).toEqual([
|
|
{
|
|
name: "canva",
|
|
command: "npx",
|
|
args: ["-y", "mcp-remote@latest", "https://mcp.canva.com/mcp"],
|
|
env: [
|
|
{
|
|
name: "CANVA_TOKEN",
|
|
value: "secret",
|
|
},
|
|
],
|
|
},
|
|
]);
|
|
});
|
|
});
|