Files
openclaw/src/cli/plugins-cli.uninstall.test.ts
2026-03-23 13:52:27 -07:00

192 lines
5.3 KiB
TypeScript

import { beforeEach, describe, expect, it } from "vitest";
import type { OpenClawConfig } from "../config/config.js";
import {
buildPluginStatusReport,
loadConfig,
parseClawHubPluginSpec,
promptYesNo,
resetPluginsCliTestState,
runPluginsCommand,
runtimeErrors,
runtimeLogs,
uninstallPlugin,
writeConfigFile,
} from "./plugins-cli-test-helpers.js";
describe("plugins cli uninstall", () => {
beforeEach(() => {
resetPluginsCliTestState();
});
it("shows uninstall dry-run preview without mutating config", async () => {
loadConfig.mockReturnValue({
plugins: {
entries: {
alpha: {
enabled: true,
},
},
installs: {
alpha: {
source: "path",
sourcePath: "/tmp/openclaw-state/extensions/alpha",
installPath: "/tmp/openclaw-state/extensions/alpha",
},
},
},
} as OpenClawConfig);
buildPluginStatusReport.mockReturnValue({
plugins: [{ id: "alpha", name: "alpha" }],
diagnostics: [],
});
await runPluginsCommand(["plugins", "uninstall", "alpha", "--dry-run"]);
expect(uninstallPlugin).not.toHaveBeenCalled();
expect(writeConfigFile).not.toHaveBeenCalled();
expect(runtimeLogs.some((line) => line.includes("Dry run, no changes made."))).toBe(true);
});
it("uninstalls with --force and --keep-files without prompting", async () => {
const baseConfig = {
plugins: {
entries: {
alpha: { enabled: true },
},
installs: {
alpha: {
source: "path",
sourcePath: "/tmp/openclaw-state/extensions/alpha",
installPath: "/tmp/openclaw-state/extensions/alpha",
},
},
},
} as OpenClawConfig;
const nextConfig = {
plugins: {
entries: {},
installs: {},
},
} as OpenClawConfig;
loadConfig.mockReturnValue(baseConfig);
buildPluginStatusReport.mockReturnValue({
plugins: [{ id: "alpha", name: "alpha" }],
diagnostics: [],
});
uninstallPlugin.mockResolvedValue({
ok: true,
config: nextConfig,
warnings: [],
actions: {
entry: true,
install: true,
allowlist: false,
loadPath: false,
memorySlot: false,
directory: false,
},
});
await runPluginsCommand(["plugins", "uninstall", "alpha", "--force", "--keep-files"]);
expect(promptYesNo).not.toHaveBeenCalled();
expect(uninstallPlugin).toHaveBeenCalledWith(
expect.objectContaining({
pluginId: "alpha",
deleteFiles: false,
}),
);
expect(writeConfigFile).toHaveBeenCalledWith(nextConfig);
});
it("exits when uninstall target is not managed by plugin install records", async () => {
loadConfig.mockReturnValue({
plugins: {
entries: {},
installs: {},
},
} as OpenClawConfig);
buildPluginStatusReport.mockReturnValue({
plugins: [{ id: "alpha", name: "alpha" }],
diagnostics: [],
});
await expect(runPluginsCommand(["plugins", "uninstall", "alpha", "--force"])).rejects.toThrow(
"__exit__:1",
);
expect(runtimeErrors.at(-1)).toContain("is not managed by plugins config/install records");
expect(uninstallPlugin).not.toHaveBeenCalled();
});
it("accepts the recorded ClawHub spec as an uninstall target", async () => {
loadConfig.mockReturnValue({
plugins: {
entries: {
"linkmind-context": { enabled: true },
},
installs: {
"linkmind-context": {
source: "npm",
spec: "clawhub:linkmind-context",
clawhubPackage: "linkmind-context",
},
},
},
} as OpenClawConfig);
buildPluginStatusReport.mockReturnValue({
plugins: [{ id: "linkmind-context", name: "linkmind-context" }],
diagnostics: [],
});
parseClawHubPluginSpec.mockImplementation((raw: string) =>
raw === "clawhub:linkmind-context" ? { name: "linkmind-context" } : null,
);
await runPluginsCommand(["plugins", "uninstall", "clawhub:linkmind-context", "--force"]);
expect(uninstallPlugin).toHaveBeenCalledWith(
expect.objectContaining({
pluginId: "linkmind-context",
}),
);
});
it("accepts a versionless ClawHub spec when the install was pinned", async () => {
loadConfig.mockReturnValue({
plugins: {
entries: {
"linkmind-context": { enabled: true },
},
installs: {
"linkmind-context": {
source: "npm",
spec: "clawhub:linkmind-context@1.2.3",
},
},
},
} as OpenClawConfig);
buildPluginStatusReport.mockReturnValue({
plugins: [{ id: "linkmind-context", name: "linkmind-context" }],
diagnostics: [],
});
parseClawHubPluginSpec.mockImplementation((raw: string) => {
if (raw === "clawhub:linkmind-context") {
return { name: "linkmind-context" };
}
if (raw === "clawhub:linkmind-context@1.2.3") {
return { name: "linkmind-context", version: "1.2.3" };
}
return null;
});
await runPluginsCommand(["plugins", "uninstall", "clawhub:linkmind-context", "--force"]);
expect(uninstallPlugin).toHaveBeenCalledWith(
expect.objectContaining({
pluginId: "linkmind-context",
}),
);
});
});