Files
openclaw/src/cli/plugins-cli.policy.test.ts
the sun gif man d4b4660026 config: stop automatic writes and guard Nix mutators (#78047)
Keep startup-derived plugin enablement, gateway auth tokens, control UI origins, and owner-display secrets runtime-only instead of persisting them into openclaw.json.

Refuse config writers, mutating update/plugin lifecycle commands, and doctor repair/token generation in Nix mode with agent-first nix-openclaw guidance.

Verification:
- pnpm check
- pnpm build
- pnpm test -- src/config/io.write-config.test.ts src/config/mutate.test.ts src/config/io.owner-display-secret.test.ts src/gateway/server-startup-config.recovery.test.ts src/gateway/startup-auth.test.ts src/gateway/startup-control-ui-origins.test.ts src/cli/plugins-cli.install.test.ts src/cli/plugins-cli.policy.test.ts src/cli/plugins-cli.uninstall.test.ts src/cli/plugins-cli.update.test.ts src/cli/update-cli.test.ts src/auto-reply/reply/commands-plugins.install.test.ts src/auto-reply/reply/commands-plugins.test.ts src/commands/onboarding-plugin-install.test.ts src/commands/doctor.runs-legacy-state-migrations-yes-mode-without.e2e.test.ts src/commands/doctor/shared/codex-route-warnings.test.ts src/commands/doctor/repair-sequencing.test.ts src/agents/auth-profile-runtime-contract.test.ts src/auto-reply/reply/agent-runner-execution.test.ts
- GitHub CI green on 05a2c71b90

Co-authored-by: Codex <noreply@openai.com>
2026-05-06 14:43:32 +02:00

192 lines
5.8 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, it } from "vitest";
import type { OpenClawConfig } from "../config/config.js";
import {
buildPluginRegistrySnapshotReport,
enablePluginInConfig,
loadConfig,
refreshPluginRegistry,
resetPluginsCliTestState,
runtimeErrors,
runPluginsCommand,
writeConfigFile,
} from "./plugins-cli-test-helpers.js";
const ORIGINAL_OPENCLAW_NIX_MODE = process.env.OPENCLAW_NIX_MODE;
describe("plugins cli policy mutations", () => {
const compatibilityPluginIds = [
{ alias: "openai-codex", pluginId: "openai" },
{ alias: "google-gemini-cli", pluginId: "google" },
{ alias: "minimax-portal-auth", pluginId: "minimax" },
] as const;
beforeEach(() => {
resetPluginsCliTestState();
});
afterEach(() => {
if (ORIGINAL_OPENCLAW_NIX_MODE === undefined) {
delete process.env.OPENCLAW_NIX_MODE;
} else {
process.env.OPENCLAW_NIX_MODE = ORIGINAL_OPENCLAW_NIX_MODE;
}
});
function mockPluginRegistry(ids: string[]) {
buildPluginRegistrySnapshotReport.mockReturnValue({
plugins: ids.map((id) => ({ id })),
diagnostics: [],
registrySource: "derived",
registryDiagnostics: [],
});
}
it("refreshes the persisted plugin registry after enabling a plugin", async () => {
const sourceConfig = {} as OpenClawConfig;
const enabledConfig = {
plugins: {
entries: {
alpha: { enabled: true },
},
},
} as OpenClawConfig;
loadConfig.mockReturnValue(sourceConfig);
enablePluginInConfig.mockReturnValue({
config: enabledConfig,
enabled: true,
pluginId: "alpha",
});
mockPluginRegistry(["alpha"]);
await runPluginsCommand(["plugins", "enable", "alpha"]);
expect(enablePluginInConfig).toHaveBeenCalledWith(sourceConfig, "alpha", {
updateChannelConfig: false,
});
expect(writeConfigFile).toHaveBeenCalledWith(enabledConfig);
expect(refreshPluginRegistry).toHaveBeenCalledWith({
config: enabledConfig,
installRecords: {},
policyPluginIds: ["alpha"],
reason: "policy-changed",
});
});
it("refuses plugin enablement in Nix mode before config mutation", async () => {
const previous = process.env.OPENCLAW_NIX_MODE;
process.env.OPENCLAW_NIX_MODE = "1";
try {
await expect(runPluginsCommand(["plugins", "enable", "alpha"])).rejects.toThrow(
"OPENCLAW_NIX_MODE=1",
);
} finally {
if (previous === undefined) {
delete process.env.OPENCLAW_NIX_MODE;
} else {
process.env.OPENCLAW_NIX_MODE = previous;
}
}
expect(enablePluginInConfig).not.toHaveBeenCalled();
expect(writeConfigFile).not.toHaveBeenCalled();
});
it("refreshes the persisted plugin registry after disabling a plugin", async () => {
loadConfig.mockReturnValue({
plugins: {
entries: {
alpha: { enabled: true },
},
},
} as OpenClawConfig);
mockPluginRegistry(["alpha"]);
await runPluginsCommand(["plugins", "disable", "alpha"]);
const nextConfig = writeConfigFile.mock.calls[0]?.[0] as OpenClawConfig;
expect(nextConfig.plugins?.entries?.alpha?.enabled).toBe(false);
expect(refreshPluginRegistry).toHaveBeenCalledWith({
config: nextConfig,
installRecords: {},
policyPluginIds: ["alpha"],
reason: "policy-changed",
});
});
it.each(compatibilityPluginIds)(
"enables compatibility id $alias through canonical plugin $pluginId",
async ({ alias, pluginId }) => {
const sourceConfig = {} as OpenClawConfig;
const enabledConfig = {
plugins: {
entries: {
[pluginId]: { enabled: true },
},
},
} as OpenClawConfig;
loadConfig.mockReturnValue(sourceConfig);
enablePluginInConfig.mockReturnValue({
config: enabledConfig,
enabled: true,
});
mockPluginRegistry([pluginId]);
await runPluginsCommand(["plugins", "enable", alias]);
expect(enablePluginInConfig).toHaveBeenCalledWith(sourceConfig, pluginId, {
updateChannelConfig: false,
});
expect(writeConfigFile).toHaveBeenCalledWith(enabledConfig);
},
);
it.each(compatibilityPluginIds)(
"disables compatibility id $alias through canonical plugin $pluginId",
async ({ alias, pluginId }) => {
loadConfig.mockReturnValue({
plugins: {
entries: {
[pluginId]: { enabled: true },
},
},
} as OpenClawConfig);
mockPluginRegistry([pluginId]);
await runPluginsCommand(["plugins", "disable", alias]);
const nextConfig = writeConfigFile.mock.calls[0]?.[0] as OpenClawConfig;
expect(nextConfig.plugins?.entries?.[pluginId]?.enabled).toBe(false);
expect(nextConfig.plugins?.entries?.[alias]).toBeUndefined();
},
);
it.each(["enable", "disable"] as const)(
"rejects %s for a plugin that is not discovered",
async (command) => {
mockPluginRegistry(["alpha"]);
await expect(runPluginsCommand(["plugins", command, "missing-plugin"])).rejects.toThrow(
"__exit__:1",
);
expect(runtimeErrors).toContain(
"Plugin not found: missing-plugin. Run `openclaw plugins list` to see installed plugins.",
);
expect(enablePluginInConfig).not.toHaveBeenCalled();
expect(writeConfigFile).not.toHaveBeenCalled();
expect(refreshPluginRegistry).not.toHaveBeenCalled();
},
);
it("does not create a channel config when disabling a channel plugin by policy", async () => {
loadConfig.mockReturnValue({} as OpenClawConfig);
mockPluginRegistry(["twitch"]);
await runPluginsCommand(["plugins", "disable", "twitch"]);
const nextConfig = writeConfigFile.mock.calls[0]?.[0] as OpenClawConfig;
expect(nextConfig.plugins?.entries?.twitch?.enabled).toBe(false);
expect(nextConfig.channels?.twitch).toBeUndefined();
});
});