mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-09 01:50:43 +00:00
221 lines
6.9 KiB
TypeScript
221 lines
6.9 KiB
TypeScript
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();
|
|
});
|
|
});
|