mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 17:20:45 +00:00
test(plugin-sdk): add unit tests for linkOpenClawPeerDependencies
Tests three cases via installPluginFromDir: - symlink created when peerDependencies declares openclaw - no symlink when peer list is empty - idempotent re-install replaces existing symlink - warns and skips when host root cannot be resolved Also removes the single-element Set in favour of a direct name comparison (peerName === "openclaw"), and adds Closes #54428 to address the same root cause in the weixin connector. Closes #54428
This commit is contained in:
committed by
Peter Steinberger
parent
2e9c1faef6
commit
56dd249a07
@@ -9,6 +9,7 @@ import { expectInstallUsesIgnoreScripts } from "../test-utils/npm-spec-install-t
|
||||
import { initializeGlobalHookRunner, resetGlobalHookRunner } from "./hook-runner-global.js";
|
||||
import { createMockPluginRegistry } from "./hooks.test-helpers.js";
|
||||
import * as installSecurityScan from "./install-security-scan.js";
|
||||
import { resolveOpenClawPackageRootSync } from "../infra/openclaw-root.js";
|
||||
import {
|
||||
installPluginFromArchive,
|
||||
installPluginFromDir,
|
||||
@@ -21,6 +22,10 @@ vi.mock("../process/exec.js", () => ({
|
||||
runCommandWithTimeout: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("../infra/openclaw-root.js", () => ({
|
||||
resolveOpenClawPackageRootSync: vi.fn(),
|
||||
}));
|
||||
|
||||
const resolveCompatibilityHostVersionMock = vi.fn();
|
||||
|
||||
vi.mock("./install.runtime.js", async () => {
|
||||
@@ -2350,3 +2355,96 @@ describe("installPluginFromDir", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("linkOpenClawPeerDependencies (via installPluginFromDir)", () => {
|
||||
const resolveRootMock = vi.mocked(resolveOpenClawPackageRootSync);
|
||||
|
||||
function writePluginWithPeerDeps(
|
||||
pluginDir: string,
|
||||
peerDependencies: Record<string, string>,
|
||||
): void {
|
||||
fs.mkdirSync(pluginDir, { recursive: true });
|
||||
fs.writeFileSync(
|
||||
path.join(pluginDir, "package.json"),
|
||||
JSON.stringify({
|
||||
name: "peer-dep-plugin",
|
||||
version: "1.0.0",
|
||||
openclaw: { extensions: ["index.js"] },
|
||||
peerDependencies,
|
||||
}),
|
||||
"utf-8",
|
||||
);
|
||||
fs.writeFileSync(path.join(pluginDir, "index.js"), "export {};\n", "utf-8");
|
||||
}
|
||||
|
||||
it("creates a node_modules/openclaw symlink when peerDependencies declares openclaw", async () => {
|
||||
const { pluginDir, extensionsDir } = setupPluginInstallDirs();
|
||||
const fakeHostRoot = suiteTempRootTracker.makeTempDir();
|
||||
resolveRootMock.mockReturnValue(fakeHostRoot);
|
||||
|
||||
writePluginWithPeerDeps(pluginDir, { openclaw: "*" });
|
||||
|
||||
const { result } = await installFromDirWithWarnings({ pluginDir, extensionsDir });
|
||||
|
||||
expect(result.ok).toBe(true);
|
||||
if (!result.ok) return;
|
||||
|
||||
const symlinkPath = path.join(result.targetDir, "node_modules", "openclaw");
|
||||
const stat = fs.lstatSync(symlinkPath);
|
||||
expect(stat.isSymbolicLink()).toBe(true);
|
||||
expect(fs.realpathSync(symlinkPath)).toBe(fs.realpathSync(fakeHostRoot));
|
||||
});
|
||||
|
||||
it("does not create a symlink when peerDependencies is empty", async () => {
|
||||
const { pluginDir, extensionsDir } = setupPluginInstallDirs();
|
||||
resolveRootMock.mockReturnValue(suiteTempRootTracker.makeTempDir());
|
||||
|
||||
writePluginWithPeerDeps(pluginDir, {});
|
||||
|
||||
const { result } = await installFromDirWithWarnings({ pluginDir, extensionsDir });
|
||||
|
||||
expect(result.ok).toBe(true);
|
||||
if (!result.ok) return;
|
||||
|
||||
const nodeModulesDir = path.join(result.targetDir, "node_modules");
|
||||
const symlinkPath = path.join(nodeModulesDir, "openclaw");
|
||||
expect(fs.existsSync(symlinkPath)).toBe(false);
|
||||
});
|
||||
|
||||
it("is idempotent — re-installing replaces an existing symlink without error", async () => {
|
||||
const { pluginDir, extensionsDir } = setupPluginInstallDirs();
|
||||
const fakeHostRoot = suiteTempRootTracker.makeTempDir();
|
||||
resolveRootMock.mockReturnValue(fakeHostRoot);
|
||||
|
||||
writePluginWithPeerDeps(pluginDir, { openclaw: "*" });
|
||||
|
||||
// First install
|
||||
const { result: first } = await installFromDirWithWarnings({ pluginDir, extensionsDir });
|
||||
expect(first.ok).toBe(true);
|
||||
|
||||
// Second install (update mode) — should replace symlink, not throw
|
||||
const { result: second, warnings } = await installFromDirWithWarnings({
|
||||
pluginDir,
|
||||
extensionsDir,
|
||||
mode: "update",
|
||||
});
|
||||
expect(second.ok).toBe(true);
|
||||
expect(warnings).toHaveLength(0);
|
||||
|
||||
if (!second.ok) return;
|
||||
const symlinkPath = path.join(second.targetDir, "node_modules", "openclaw");
|
||||
expect(fs.lstatSync(symlinkPath).isSymbolicLink()).toBe(true);
|
||||
});
|
||||
|
||||
it("warns and skips when resolveOpenClawPackageRootSync returns null", async () => {
|
||||
const { pluginDir, extensionsDir } = setupPluginInstallDirs();
|
||||
resolveRootMock.mockReturnValue(null);
|
||||
|
||||
writePluginWithPeerDeps(pluginDir, { openclaw: "*" });
|
||||
|
||||
const { result, warnings } = await installFromDirWithWarnings({ pluginDir, extensionsDir });
|
||||
|
||||
expect(result.ok).toBe(true);
|
||||
expect(warnings.some((w) => w.includes("Could not locate openclaw package root"))).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -621,10 +621,7 @@ async function linkOpenClawPeerDependencies(params: {
|
||||
peerDependencies: Record<string, string>;
|
||||
logger: PluginInstallLogger;
|
||||
}): Promise<void> {
|
||||
const OPENCLAW_PEER_NAMES = new Set(["openclaw"]);
|
||||
const peers = Object.keys(params.peerDependencies).filter((name) =>
|
||||
OPENCLAW_PEER_NAMES.has(name),
|
||||
);
|
||||
const peers = Object.keys(params.peerDependencies).filter((name) => name === "openclaw");
|
||||
if (peers.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user