mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-08 20:20:43 +00:00
Reuse the startup runtime plugin registry across provider/tool helper paths while preserving standalone CLI/MCP fallback loading. Includes follow-up fixes for migration/provider/tool registry bootstrap and regression coverage for compatible registry reuse. Co-authored-by: DmitryPogodaev <pogodaev.dm@gmail.com>
340 lines
11 KiB
TypeScript
340 lines
11 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
|
import type { PluginRegistry } from "./registry-types.js";
|
|
import { createEmptyPluginRegistry } from "./registry.js";
|
|
|
|
type MockManifestRegistry = {
|
|
plugins: Array<Record<string, unknown>>;
|
|
diagnostics: unknown[];
|
|
};
|
|
|
|
type MockPluginIndex = {
|
|
plugins: Array<{
|
|
pluginId: string;
|
|
origin: string;
|
|
enabled: boolean;
|
|
enabledByDefault?: boolean;
|
|
}>;
|
|
diagnostics: unknown[];
|
|
};
|
|
|
|
function createEmptyMockManifestRegistry(): MockManifestRegistry {
|
|
return { plugins: [], diagnostics: [] };
|
|
}
|
|
|
|
function createMockPluginIndex(plugins: MockPluginIndex["plugins"]): MockPluginIndex {
|
|
return { plugins, diagnostics: [] };
|
|
}
|
|
|
|
const mocks = vi.hoisted(() => ({
|
|
resolveRuntimePluginRegistry: vi.fn<(params?: unknown) => PluginRegistry | undefined>(
|
|
() => undefined,
|
|
),
|
|
loadPluginManifestRegistry: vi.fn<(params?: Record<string, unknown>) => MockManifestRegistry>(
|
|
() => createEmptyMockManifestRegistry(),
|
|
),
|
|
loadPluginRegistrySnapshot: vi.fn<(_params?: unknown) => MockPluginIndex>(() =>
|
|
createMockPluginIndex([]),
|
|
),
|
|
loadPluginRegistrySnapshotWithMetadata: vi.fn((params?: { index?: MockPluginIndex }) => ({
|
|
source: params?.index ? "provided" : "derived",
|
|
snapshot: params?.index ?? createMockPluginIndex([]),
|
|
diagnostics: [],
|
|
})),
|
|
ensureStandaloneRuntimePluginRegistryLoaded: vi.fn(),
|
|
}));
|
|
|
|
vi.mock("./loader.js", () => ({
|
|
resolveRuntimePluginRegistry: mocks.resolveRuntimePluginRegistry,
|
|
}));
|
|
|
|
vi.mock("./active-runtime-registry.js", () => ({
|
|
getLoadedRuntimePluginRegistry: (params?: { requiredPluginIds?: string[] }) => {
|
|
if (params === undefined) {
|
|
return mocks.resolveRuntimePluginRegistry();
|
|
}
|
|
return mocks.resolveRuntimePluginRegistry({
|
|
onlyPluginIds: params.requiredPluginIds,
|
|
});
|
|
},
|
|
}));
|
|
|
|
vi.mock("./plugin-registry.js", () => ({
|
|
loadPluginRegistrySnapshot: mocks.loadPluginRegistrySnapshot,
|
|
loadPluginRegistrySnapshotWithMetadata: mocks.loadPluginRegistrySnapshotWithMetadata,
|
|
}));
|
|
|
|
vi.mock("./manifest-registry-installed.js", () => ({
|
|
loadPluginManifestRegistryForInstalledIndex: mocks.loadPluginManifestRegistry,
|
|
resolveInstalledManifestRegistryIndexFingerprint: () => "test-installed-index",
|
|
}));
|
|
|
|
vi.mock("./runtime/standalone-runtime-registry-loader.js", () => ({
|
|
ensureStandaloneRuntimePluginRegistryLoaded: mocks.ensureStandaloneRuntimePluginRegistryLoaded,
|
|
}));
|
|
|
|
let ensureStandaloneMigrationProviderRegistryLoaded: typeof import("./migration-provider-runtime.js").ensureStandaloneMigrationProviderRegistryLoaded;
|
|
let resolvePluginMigrationProvider: typeof import("./migration-provider-runtime.js").resolvePluginMigrationProvider;
|
|
let resolvePluginMigrationProviders: typeof import("./migration-provider-runtime.js").resolvePluginMigrationProviders;
|
|
|
|
function createMigrationProvider(id: string) {
|
|
return {
|
|
id,
|
|
label: id,
|
|
plan: vi.fn(),
|
|
apply: vi.fn(),
|
|
};
|
|
}
|
|
|
|
describe("migration provider runtime", () => {
|
|
beforeEach(async () => {
|
|
vi.clearAllMocks();
|
|
mocks.resolveRuntimePluginRegistry.mockReturnValue(createEmptyPluginRegistry());
|
|
mocks.loadPluginManifestRegistry.mockReturnValue(createEmptyMockManifestRegistry());
|
|
mocks.loadPluginRegistrySnapshot.mockReturnValue(createMockPluginIndex([]));
|
|
mocks.loadPluginRegistrySnapshotWithMetadata.mockImplementation(
|
|
(params?: { index?: MockPluginIndex }) => ({
|
|
source: params?.index ? "provided" : "derived",
|
|
snapshot: params?.index ?? mocks.loadPluginRegistrySnapshot(),
|
|
diagnostics: [],
|
|
}),
|
|
);
|
|
const runtime = await import("./migration-provider-runtime.js");
|
|
ensureStandaloneMigrationProviderRegistryLoaded =
|
|
runtime.ensureStandaloneMigrationProviderRegistryLoaded;
|
|
resolvePluginMigrationProvider = runtime.resolvePluginMigrationProvider;
|
|
resolvePluginMigrationProviders = runtime.resolvePluginMigrationProviders;
|
|
});
|
|
|
|
it("standalone-loads bundled migration providers through compat config", () => {
|
|
mocks.loadPluginRegistrySnapshot.mockReturnValue(
|
|
createMockPluginIndex([
|
|
{
|
|
pluginId: "migrate-hermes",
|
|
origin: "bundled",
|
|
enabled: true,
|
|
},
|
|
]),
|
|
);
|
|
mocks.loadPluginManifestRegistry.mockImplementation(() => ({
|
|
diagnostics: [],
|
|
plugins: [
|
|
{
|
|
id: "migrate-hermes",
|
|
origin: "bundled",
|
|
contracts: { migrationProviders: ["hermes"] },
|
|
},
|
|
],
|
|
}));
|
|
|
|
ensureStandaloneMigrationProviderRegistryLoaded({
|
|
cfg: { plugins: { enabled: false } } as OpenClawConfig,
|
|
});
|
|
|
|
expect(mocks.ensureStandaloneRuntimePluginRegistryLoaded).toHaveBeenCalledWith({
|
|
surface: "active",
|
|
requiredPluginIds: ["migrate-hermes"],
|
|
loadOptions: {
|
|
activate: false,
|
|
onlyPluginIds: ["migrate-hermes"],
|
|
config: expect.objectContaining({
|
|
plugins: expect.objectContaining({
|
|
enabled: true,
|
|
entries: {
|
|
"migrate-hermes": { enabled: true },
|
|
},
|
|
}),
|
|
}),
|
|
},
|
|
});
|
|
});
|
|
|
|
it("loads configured external migration-provider plugins from manifest contracts", () => {
|
|
const cfg = {
|
|
plugins: { entries: { "external-migration": { enabled: true } } },
|
|
} as OpenClawConfig;
|
|
const provider = createMigrationProvider("external-import");
|
|
const active = createEmptyPluginRegistry();
|
|
const loaded = createEmptyPluginRegistry();
|
|
loaded.migrationProviders.push({
|
|
pluginId: "external-migration",
|
|
pluginName: "External Migration",
|
|
source: "test",
|
|
provider,
|
|
} as never);
|
|
mocks.resolveRuntimePluginRegistry.mockImplementation((params?: unknown) =>
|
|
params === undefined ? active : loaded,
|
|
);
|
|
mocks.loadPluginRegistrySnapshot.mockReturnValue(
|
|
createMockPluginIndex([
|
|
{
|
|
pluginId: "external-migration",
|
|
origin: "installed",
|
|
enabled: true,
|
|
},
|
|
{
|
|
pluginId: "disabled-external-migration",
|
|
origin: "installed",
|
|
enabled: false,
|
|
},
|
|
]),
|
|
);
|
|
mocks.loadPluginManifestRegistry.mockImplementation((params?: Record<string, unknown>) => ({
|
|
diagnostics: [],
|
|
plugins: params?.includeDisabled
|
|
? [
|
|
{
|
|
id: "external-migration",
|
|
origin: "installed",
|
|
contracts: { migrationProviders: ["external-import"] },
|
|
},
|
|
{
|
|
id: "disabled-external-migration",
|
|
origin: "installed",
|
|
contracts: { migrationProviders: ["external-import"] },
|
|
},
|
|
]
|
|
: [
|
|
{
|
|
id: "external-migration",
|
|
origin: "installed",
|
|
contracts: { migrationProviders: ["external-import"] },
|
|
},
|
|
],
|
|
}));
|
|
|
|
const resolved = resolvePluginMigrationProvider({ providerId: "external-import", cfg });
|
|
|
|
expect(resolved).toBe(provider);
|
|
expect(mocks.loadPluginRegistrySnapshotWithMetadata).toHaveBeenCalledWith({
|
|
config: cfg,
|
|
env: process.env,
|
|
preferPersisted: false,
|
|
});
|
|
expect(mocks.loadPluginManifestRegistry).toHaveBeenCalledWith({
|
|
index: expect.objectContaining({
|
|
plugins: expect.arrayContaining([
|
|
expect.objectContaining({ pluginId: "external-migration" }),
|
|
]),
|
|
}),
|
|
config: cfg,
|
|
env: process.env,
|
|
includeDisabled: true,
|
|
});
|
|
expect(mocks.resolveRuntimePluginRegistry).toHaveBeenCalledWith();
|
|
expect(mocks.resolveRuntimePluginRegistry).toHaveBeenCalledWith({
|
|
onlyPluginIds: ["external-migration"],
|
|
});
|
|
});
|
|
|
|
it("derives a fresh manifest registry so newly bundled migration providers are discoverable", () => {
|
|
const provider = createMigrationProvider("hermes");
|
|
const active = createEmptyPluginRegistry();
|
|
const loaded = createEmptyPluginRegistry();
|
|
loaded.migrationProviders.push({
|
|
pluginId: "migrate-hermes",
|
|
pluginName: "Hermes Migration",
|
|
source: "test",
|
|
provider,
|
|
} as never);
|
|
mocks.resolveRuntimePluginRegistry.mockImplementation((params?: unknown) =>
|
|
params === undefined ? active : loaded,
|
|
);
|
|
mocks.loadPluginRegistrySnapshot.mockReturnValue(
|
|
createMockPluginIndex([
|
|
{
|
|
pluginId: "migrate-hermes",
|
|
origin: "bundled",
|
|
enabled: true,
|
|
},
|
|
]),
|
|
);
|
|
mocks.loadPluginManifestRegistry.mockImplementation(() => ({
|
|
diagnostics: [],
|
|
plugins: [
|
|
{
|
|
id: "migrate-hermes",
|
|
origin: "bundled",
|
|
contracts: { migrationProviders: ["hermes"] },
|
|
},
|
|
],
|
|
}));
|
|
|
|
const resolved = resolvePluginMigrationProvider({ providerId: "hermes" });
|
|
|
|
expect(resolved).toBe(provider);
|
|
expect(mocks.loadPluginRegistrySnapshotWithMetadata).toHaveBeenCalledWith({
|
|
config: {},
|
|
env: process.env,
|
|
preferPersisted: false,
|
|
workspaceDir: undefined,
|
|
});
|
|
expect(mocks.loadPluginManifestRegistry).toHaveBeenCalledWith({
|
|
index: expect.objectContaining({
|
|
plugins: [expect.objectContaining({ pluginId: "migrate-hermes" })],
|
|
}),
|
|
config: {},
|
|
env: process.env,
|
|
includeDisabled: true,
|
|
workspaceDir: undefined,
|
|
});
|
|
expect(mocks.resolveRuntimePluginRegistry).toHaveBeenCalledWith({
|
|
onlyPluginIds: ["migrate-hermes"],
|
|
});
|
|
});
|
|
|
|
it("lists configured external migration providers alongside active providers", () => {
|
|
const activeProvider = createMigrationProvider("active-import");
|
|
const externalProvider = createMigrationProvider("external-import");
|
|
const active = createEmptyPluginRegistry();
|
|
active.migrationProviders.push({
|
|
pluginId: "active-migration",
|
|
pluginName: "Active Migration",
|
|
source: "test",
|
|
provider: activeProvider,
|
|
} as never);
|
|
const loaded = createEmptyPluginRegistry();
|
|
loaded.migrationProviders.push({
|
|
pluginId: "external-migration",
|
|
pluginName: "External Migration",
|
|
source: "test",
|
|
provider: externalProvider,
|
|
} as never);
|
|
mocks.resolveRuntimePluginRegistry.mockImplementation((params?: unknown) =>
|
|
params === undefined ? active : loaded,
|
|
);
|
|
mocks.loadPluginRegistrySnapshot.mockReturnValue(
|
|
createMockPluginIndex([
|
|
{
|
|
pluginId: "external-migration",
|
|
origin: "installed",
|
|
enabled: true,
|
|
},
|
|
]),
|
|
);
|
|
mocks.loadPluginManifestRegistry.mockImplementation((params?: Record<string, unknown>) => ({
|
|
diagnostics: [],
|
|
plugins: params?.includeDisabled
|
|
? [
|
|
{
|
|
id: "external-migration",
|
|
origin: "installed",
|
|
contracts: { migrationProviders: ["external-import"] },
|
|
},
|
|
]
|
|
: [
|
|
{
|
|
id: "external-migration",
|
|
origin: "installed",
|
|
contracts: { migrationProviders: ["external-import"] },
|
|
},
|
|
],
|
|
}));
|
|
|
|
expect(resolvePluginMigrationProviders().map((provider) => provider.id)).toEqual([
|
|
"active-import",
|
|
"external-import",
|
|
]);
|
|
});
|
|
});
|