import { beforeEach, describe, expect, it, vi } from "vitest"; const installPluginFromNpmSpecMock = vi.fn(); vi.mock("./install.js", () => ({ installPluginFromNpmSpec: (...args: unknown[]) => installPluginFromNpmSpecMock(...args), resolvePluginInstallDir: (pluginId: string) => `/tmp/${pluginId}`, PLUGIN_INSTALL_ERROR_CODE: { NPM_PACKAGE_NOT_FOUND: "npm_package_not_found", }, })); describe("updateNpmInstalledPlugins", () => { beforeEach(() => { installPluginFromNpmSpecMock.mockReset(); }); it("skips integrity drift checks for unpinned npm specs during dry-run updates", async () => { installPluginFromNpmSpecMock.mockResolvedValue({ ok: true, pluginId: "opik-openclaw", targetDir: "/tmp/opik-openclaw", version: "0.2.6", extensions: ["index.ts"], }); const { updateNpmInstalledPlugins } = await import("./update.js"); await updateNpmInstalledPlugins({ config: { plugins: { installs: { "opik-openclaw": { source: "npm", spec: "@opik/opik-openclaw", integrity: "sha512-old", installPath: "/tmp/opik-openclaw", }, }, }, }, pluginIds: ["opik-openclaw"], dryRun: true, }); expect(installPluginFromNpmSpecMock).toHaveBeenCalledWith( expect.objectContaining({ spec: "@opik/opik-openclaw", expectedIntegrity: undefined, }), ); }); it("keeps integrity drift checks for exact-version npm specs during dry-run updates", async () => { installPluginFromNpmSpecMock.mockResolvedValue({ ok: true, pluginId: "opik-openclaw", targetDir: "/tmp/opik-openclaw", version: "0.2.6", extensions: ["index.ts"], }); const { updateNpmInstalledPlugins } = await import("./update.js"); await updateNpmInstalledPlugins({ config: { plugins: { installs: { "opik-openclaw": { source: "npm", spec: "@opik/opik-openclaw@0.2.5", integrity: "sha512-old", installPath: "/tmp/opik-openclaw", }, }, }, }, pluginIds: ["opik-openclaw"], dryRun: true, }); expect(installPluginFromNpmSpecMock).toHaveBeenCalledWith( expect.objectContaining({ spec: "@opik/opik-openclaw@0.2.5", expectedIntegrity: "sha512-old", }), ); }); it("formats package-not-found updates with a stable message", async () => { installPluginFromNpmSpecMock.mockResolvedValue({ ok: false, code: "npm_package_not_found", error: "Package not found on npm: @openclaw/missing.", }); const { updateNpmInstalledPlugins } = await import("./update.js"); const result = await updateNpmInstalledPlugins({ config: { plugins: { installs: { missing: { source: "npm", spec: "@openclaw/missing", installPath: "/tmp/missing", }, }, }, }, pluginIds: ["missing"], dryRun: true, }); expect(result.outcomes).toEqual([ { pluginId: "missing", status: "error", message: "Failed to check missing: npm package not found for @openclaw/missing.", }, ]); }); it("falls back to raw installer error for unknown error codes", async () => { installPluginFromNpmSpecMock.mockResolvedValue({ ok: false, code: "invalid_npm_spec", error: "unsupported npm spec: github:evil/evil", }); const { updateNpmInstalledPlugins } = await import("./update.js"); const result = await updateNpmInstalledPlugins({ config: { plugins: { installs: { bad: { source: "npm", spec: "github:evil/evil", installPath: "/tmp/bad", }, }, }, }, pluginIds: ["bad"], dryRun: true, }); expect(result.outcomes).toEqual([ { pluginId: "bad", status: "error", message: "Failed to check bad: unsupported npm spec: github:evil/evil", }, ]); }); });