import { describe, expect, it, vi } from "vitest" ;
import { installedPluginRoot } from "../../test/helpers/bundled-plugin-paths.js" ;
import { PLUGIN_INSTALL_ERROR_CODE } from "../plugins/install.js" ;
import {
resolveBundledInstallPlanForCatalogEntry,
resolveBundledInstallPlanBeforeNpm,
resolveBundledInstallPlanForNpmFailure,
} 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("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();
});
});
Messung V0.5 in Prozent C=100 H=100 G=100
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.14Bemerkung:
(vorverarbeitet am 2026-06-09)
¤
*Bot Zugriff