test(plugins): cover install lifecycle edges

This commit is contained in:
Vincent Koc
2026-05-01 15:44:18 -07:00
parent f7fd8033b4
commit c8d4fefe18
2 changed files with 65 additions and 0 deletions

View File

@@ -299,6 +299,40 @@ describe("installPluginFromNpmSpec", () => {
).toBe(false);
});
it("allows duplicate npm installs in update mode", async () => {
const stateDir = suiteTempRootTracker.makeTempDir();
const npmRoot = path.join(stateDir, "npm");
const installRoot = path.join(npmRoot, "node_modules", "@openclaw", "voice-call");
fs.mkdirSync(installRoot, { recursive: true });
fs.writeFileSync(path.join(installRoot, "old.txt"), "old", "utf-8");
mockNpmViewAndInstall({
spec: "@openclaw/voice-call@0.0.2",
packageName: "@openclaw/voice-call",
version: "0.0.2",
pluginId: "voice-call",
npmRoot,
});
const result = await installPluginFromNpmSpec({
spec: "@openclaw/voice-call@0.0.2",
npmDir: npmRoot,
mode: "update",
logger: { info: () => {}, warn: () => {} },
});
expect(result.ok).toBe(true);
if (!result.ok) {
throw new Error(result.error);
}
expect(result.targetDir).toBe(installRoot);
expect(result.npmResolution?.version).toBe("0.0.2");
expectNpmInstallIntoRoot({
calls: runCommandWithTimeoutMock.mock.calls,
npmRoot,
spec: "@openclaw/voice-call@0.0.2",
});
});
it("aborts when integrity drift callback rejects the fetched artifact", async () => {
mockNpmViewMetadataResult(runCommandWithTimeoutMock, {
name: "@openclaw/voice-call",

View File

@@ -1061,6 +1061,37 @@ describe("uninstallPlugin", () => {
});
await expect(fs.access(installPath)).rejects.toThrow();
});
it("does not delete symlinked git install targets that resolve outside the managed git root", async () => {
const stateDir = path.join(tempDir, "state");
const extensionsDir = path.join(stateDir, "extensions");
const linkParentDir = path.join(stateDir, "git", "git-abc123");
const installPath = path.join(linkParentDir, "repo");
const outsideDir = path.join(tempDir, "outside");
await fs.mkdir(linkParentDir, { recursive: true });
await fs.mkdir(outsideDir, { recursive: true });
await fs.writeFile(path.join(outsideDir, "index.js"), "// keep me");
await fs.symlink(outsideDir, installPath, "dir");
const result = await uninstallPlugin({
config: createPluginConfig({
entries: createSinglePluginEntries(),
installs: {
"my-plugin": createGitInstallRecord("my-plugin", installPath),
},
}),
pluginId: "my-plugin",
deleteFiles: true,
extensionsDir,
});
expectSuccessfulUninstallActions(result, {
directory: false,
});
await expect(fs.access(outsideDir)).resolves.toBeUndefined();
const linkStat = await fs.lstat(installPath);
expect(linkStat.isSymbolicLink()).toBe(true);
});
});
describe("resolveUninstallDirectoryTarget", () => {