Files
openclaw/src/plugins/enable.test.ts
Peter Steinberger dd472deab4 fix(plugins): preserve settings for compatibility IDs (#114521)
Honor canonical plugin identity across allowlists, denylists, and plugin mutations. Preserve and deterministically deep-merge alias configuration, and explicitly persist canonical entries in CLI enable and disable commands.

Closes #114320

Co-authored-by: kevin2966n <147227108+kevin2966n@users.noreply.github.com>
2026-07-27 06:54:14 -04:00

259 lines
7.9 KiB
TypeScript

// Covers plugin enablement decisions and disabled-state handling.
import { describe, expect, it } from "vitest";
import type { OpenClawConfig } from "../config/config.js";
import { enableExplicitlySelectedPluginInConfig, enablePluginInConfig } from "./enable.js";
function expectEnableResult(
cfg: OpenClawConfig,
pluginId: string,
params: {
enabled: boolean;
assert: (result: ReturnType<typeof enablePluginInConfig>) => void;
},
) {
const result = enablePluginInConfig(cfg, pluginId);
expect(result.enabled).toBe(params.enabled);
params.assert(result);
}
function expectEnabledAllowlist(
result: ReturnType<typeof enablePluginInConfig>,
expected: string[],
) {
expect(result.config.plugins?.allow).toEqual(expected);
}
function expectBuiltInChannelEnabled(result: ReturnType<typeof enablePluginInConfig>) {
expect(result.config.channels?.telegram?.enabled).toBe(true);
expect(result.config.plugins?.entries?.telegram?.enabled).toBe(true);
}
function expectBuiltInChannelEnabledWithAllowlist(
result: ReturnType<typeof enablePluginInConfig>,
expectedAllowlist?: string[],
) {
expectBuiltInChannelEnabled(result);
if (expectedAllowlist) {
expectEnabledAllowlist(result, expectedAllowlist);
}
}
describe("enablePluginInConfig", () => {
it.each([
{
name: "enables a plugin entry",
cfg: {} as OpenClawConfig,
pluginId: "google",
expectedEnabled: true,
assert: (result: ReturnType<typeof enablePluginInConfig>) => {
expect(result.config.plugins?.entries?.google?.enabled).toBe(true);
},
},
{
name: "refuses enable when plugin is outside configured allowlist",
cfg: {
plugins: {
allow: ["memory-core"],
},
} as OpenClawConfig,
pluginId: "google",
expectedEnabled: false,
assert: (result: ReturnType<typeof enablePluginInConfig>) => {
expect(result.reason).toBe("blocked by allowlist");
expectEnabledAllowlist(result, ["memory-core"]);
},
},
{
name: "enables plugin already present in configured allowlist",
cfg: {
plugins: {
allow: ["google"],
},
} as OpenClawConfig,
pluginId: "google",
expectedEnabled: true,
assert: (result: ReturnType<typeof enablePluginInConfig>) => {
expect(result.config.plugins?.entries?.google?.enabled).toBe(true);
expectEnabledAllowlist(result, ["google"]);
},
},
{
name: "enables a canonical plugin allowed through a mixed-case compatibility id",
cfg: {
plugins: {
allow: [" GOOGLE-GEMINI-CLI "],
},
} as OpenClawConfig,
pluginId: "google",
expectedEnabled: true,
assert: (result: ReturnType<typeof enablePluginInConfig>) => {
expect(result.pluginId).toBe("google");
expect(result.config.plugins?.entries?.google?.enabled).toBe(true);
expectEnabledAllowlist(result, ["google"]);
},
},
{
name: "canonicalizes a mixed-case compatibility target before enabling it",
cfg: {} as OpenClawConfig,
pluginId: " GOOGLE-GEMINI-CLI ",
expectedEnabled: true,
assert: (result: ReturnType<typeof enablePluginInConfig>) => {
expect(result.pluginId).toBe("google");
expect(result.config.plugins?.entries?.google?.enabled).toBe(true);
expect(result.config.plugins?.entries?.[" GOOGLE-GEMINI-CLI "]).toBeUndefined();
},
},
{
name: "refuses enable when plugin is denylisted",
cfg: {
plugins: {
deny: ["google"],
},
} as OpenClawConfig,
pluginId: "google",
expectedEnabled: false,
assert: (result: ReturnType<typeof enablePluginInConfig>) => {
expect(result.reason).toBe("blocked by denylist");
},
},
{
name: "refuses a canonical plugin denied through a mixed-case compatibility id",
cfg: {
plugins: {
deny: [" GOOGLE-GEMINI-CLI "],
},
} as OpenClawConfig,
pluginId: "google",
expectedEnabled: false,
assert: (result: ReturnType<typeof enablePluginInConfig>) => {
expect(result.pluginId).toBe("google");
expect(result.reason).toBe("blocked by denylist");
expect(result.config.plugins?.entries?.google).toBeUndefined();
},
},
{
name: "writes built-in channels to channels.<id>.enabled and plugins.entries",
cfg: {} as OpenClawConfig,
pluginId: "telegram",
expectedEnabled: true,
assert: expectBuiltInChannelEnabled,
},
{
name: "refuses built-in channel enable when channel is outside configured allowlist",
cfg: {
plugins: {
allow: ["memory-core"],
},
} as OpenClawConfig,
pluginId: "telegram",
expectedEnabled: false,
assert: (result: ReturnType<typeof enablePluginInConfig>) => {
expect(result.reason).toBe("blocked by allowlist");
expect(result.config.plugins?.allow).toEqual(["memory-core"]);
expect(result.config.channels?.telegram?.enabled).toBeUndefined();
},
},
{
name: "enables built-in channel already present in configured allowlist",
cfg: {
plugins: {
allow: ["telegram"],
},
} as OpenClawConfig,
pluginId: "telegram",
expectedEnabled: true,
assert: (result: ReturnType<typeof enablePluginInConfig>) => {
expectBuiltInChannelEnabledWithAllowlist(result, ["telegram"]);
},
},
{
name: "re-enables built-in channels after explicit plugin-level disable",
cfg: {
channels: {
telegram: {
enabled: true,
},
},
plugins: {
entries: {
telegram: {
enabled: false,
},
},
},
} as OpenClawConfig,
pluginId: "telegram",
expectedEnabled: true,
assert: expectBuiltInChannelEnabledWithAllowlist,
},
])("$name", ({ cfg, pluginId, expectedEnabled, assert }) => {
expectEnableResult(cfg, pluginId, {
enabled: expectedEnabled,
assert,
});
});
it("can enable a built-in channel plugin entry without mutating channel config", () => {
const result = enablePluginInConfig({} as OpenClawConfig, "twitch", {
updateChannelConfig: false,
});
expect(result.enabled).toBe(true);
expect(result.config.plugins?.entries?.twitch?.enabled).toBe(true);
expect(result.config.channels?.twitch).toBeUndefined();
});
});
describe("enableExplicitlySelectedPluginInConfig", () => {
it("appends ClickClack to a restrictive allowlist before enabling it", () => {
const result = enableExplicitlySelectedPluginInConfig(
{
plugins: {
allow: ["memory-core"],
},
} as OpenClawConfig,
"clickclack",
);
expect(result.enabled).toBe(true);
expect(result.config.plugins?.allow).toEqual(["memory-core", "clickclack"]);
expect(result.config.plugins?.entries?.clickclack?.enabled).toBe(true);
expect(result.config.channels?.clickclack?.enabled).toBe(true);
});
it("keeps unrelated explicit plugin enables blocked by a restrictive allowlist", () => {
const cfg = {
plugins: {
allow: ["memory-core"],
},
} as OpenClawConfig;
const result = enableExplicitlySelectedPluginInConfig(cfg, "google");
expect(result).toEqual({
config: cfg,
enabled: false,
pluginId: "google",
reason: "blocked by allowlist",
});
});
it("keeps ClickClack blocked by the denylist without changing the allowlist", () => {
const cfg = {
plugins: {
allow: ["memory-core"],
deny: ["clickclack"],
},
} as OpenClawConfig;
const result = enableExplicitlySelectedPluginInConfig(cfg, "clickclack");
expect(result).toEqual({
config: cfg,
enabled: false,
pluginId: "clickclack",
reason: "blocked by denylist",
});
});
});