import { installedPluginRoot } from "openclaw/plugin-sdk/test-fixtures"; import { describe, expect, it, vi } from "vitest"; import { PLUGIN_INSTALL_ERROR_CODE } from "../plugins/install.js"; import { resolveBundledInstallPlanForCatalogEntry, resolveBundledInstallPlanBeforeNpm, resolveBundledInstallPlanForNpmFailure, resolveOfficialExternalInstallPlanBeforeNpm, resolveOfficialExternalNpmPackageTrust, } from "./plugin-install-plan.js"; describe("plugin install plan helpers", () => { it("prefers bundled plugin for bare plugin-id specs", () => { const findBundledSource = vi.fn().mockReturnValue({ pluginId: "voice-call", localPath: installedPluginRoot("/tmp", "voice-call"), npmSpec: "@openclaw/voice-call", }); const result = resolveBundledInstallPlanBeforeNpm({ rawSpec: "voice-call", findBundledSource, }); expect(findBundledSource).toHaveBeenCalledWith({ kind: "pluginId", value: "voice-call" }); expect(result?.bundledSource.pluginId).toBe("voice-call"); expect(result?.warning).toContain('bare install spec "voice-call"'); }); it("skips bundled pre-plan for scoped npm specs", () => { const findBundledSource = vi.fn(); const result = resolveBundledInstallPlanBeforeNpm({ rawSpec: "@openclaw/voice-call", findBundledSource, }); expect(findBundledSource).not.toHaveBeenCalled(); expect(result).toBeNull(); }); it("resolves exact official external plugin ids before npm fallback", () => { const findOfficialExternalPlugin = vi.fn().mockReturnValue({ pluginId: "brave", npmSpec: "@openclaw/brave-plugin", expectedIntegrity: "sha512-brave", }); const result = resolveOfficialExternalInstallPlanBeforeNpm({ rawSpec: "brave", findOfficialExternalPlugin, }); expect(findOfficialExternalPlugin).toHaveBeenCalledWith("brave"); expect(result).toEqual({ pluginId: "brave", npmSpec: "@openclaw/brave-plugin", expectedIntegrity: "sha512-brave", }); }); it("skips official external plan for explicit npm selectors", () => { const findOfficialExternalPlugin = vi.fn(); expect( resolveOfficialExternalInstallPlanBeforeNpm({ rawSpec: "brave@beta", findOfficialExternalPlugin, }), ).toBeNull(); expect( resolveOfficialExternalInstallPlanBeforeNpm({ rawSpec: "@openclaw/brave-plugin", findOfficialExternalPlugin, }), ).toBeNull(); expect(findOfficialExternalPlugin).not.toHaveBeenCalled(); }); it("skips official external plan without an npm install spec", () => { const result = resolveOfficialExternalInstallPlanBeforeNpm({ rawSpec: "brave", findOfficialExternalPlugin: vi.fn().mockReturnValue({ pluginId: "brave", }), }); expect(result).toBeNull(); }); it("trusts exact official external npm packages without remapping the spec", () => { const findOfficialExternalPackage = vi.fn().mockReturnValue({ pluginId: "discord", npmSpec: "@openclaw/discord", }); const result = resolveOfficialExternalNpmPackageTrust({ npmSpec: "@openclaw/discord", findOfficialExternalPackage, }); expect(findOfficialExternalPackage).toHaveBeenCalledWith("@openclaw/discord"); expect(result).toEqual({ pluginId: "discord", trustedSourceLinkedOfficialInstall: true, }); }); it("does not trust npm package names outside the official external catalog", () => { const findOfficialExternalPackage = vi.fn(); const result = resolveOfficialExternalNpmPackageTrust({ npmSpec: "brave", findOfficialExternalPackage, }); expect(findOfficialExternalPackage).toHaveBeenCalledWith("brave"); expect(result).toBeNull(); }); it("prefers bundled catalog plugin by id before npm spec", () => { const findBundledSource = vi .fn() .mockImplementation(({ kind, value }: { kind: "pluginId" | "npmSpec"; value: string }) => { if (kind === "pluginId" && value === "voice-call") { return { pluginId: "voice-call", localPath: installedPluginRoot("/tmp", "voice-call"), npmSpec: "@openclaw/voice-call", }; } return undefined; }); const result = resolveBundledInstallPlanForCatalogEntry({ pluginId: "voice-call", npmSpec: "@openclaw/voice-call", findBundledSource, }); expect(findBundledSource).toHaveBeenCalledWith({ kind: "pluginId", value: "voice-call" }); expect(result?.bundledSource.localPath).toBe(installedPluginRoot("/tmp", "voice-call")); }); it("rejects npm-spec matches that resolve to a different plugin id", () => { const findBundledSource = vi .fn() .mockImplementation(({ kind }: { kind: "pluginId" | "npmSpec"; value: string }) => { if (kind === "npmSpec") { return { pluginId: "not-voice-call", localPath: installedPluginRoot("/tmp", "not-voice-call"), npmSpec: "@openclaw/voice-call", }; } return undefined; }); const result = resolveBundledInstallPlanForCatalogEntry({ pluginId: "voice-call", npmSpec: "@openclaw/voice-call", findBundledSource, }); expect(result).toBeNull(); }); it("rejects plugin-id bundled matches when the catalog npm spec was overridden", () => { const findBundledSource = vi .fn() .mockImplementation(({ kind }: { kind: "pluginId" | "npmSpec"; value: string }) => { if (kind === "pluginId") { return { pluginId: "whatsapp", localPath: installedPluginRoot("/tmp", "whatsapp"), npmSpec: "@openclaw/whatsapp", }; } return undefined; }); const result = resolveBundledInstallPlanForCatalogEntry({ pluginId: "whatsapp", npmSpec: "@vendor/whatsapp-fork", findBundledSource, }); expect(result).toBeNull(); }); it("uses npm-spec bundled fallback only for package-not-found", () => { const findBundledSource = vi.fn().mockReturnValue({ pluginId: "voice-call", localPath: installedPluginRoot("/tmp", "voice-call"), npmSpec: "@openclaw/voice-call", }); const result = resolveBundledInstallPlanForNpmFailure({ rawSpec: "@openclaw/voice-call", code: PLUGIN_INSTALL_ERROR_CODE.NPM_PACKAGE_NOT_FOUND, findBundledSource, }); expect(findBundledSource).toHaveBeenCalledWith({ kind: "npmSpec", value: "@openclaw/voice-call", }); expect(result?.warning).toContain("npm package unavailable"); }); it("skips fallback for non-not-found npm failures", () => { const findBundledSource = vi.fn(); const result = resolveBundledInstallPlanForNpmFailure({ rawSpec: "@openclaw/voice-call", code: "INSTALL_FAILED", findBundledSource, }); expect(findBundledSource).not.toHaveBeenCalled(); expect(result).toBeNull(); }); });