import { beforeEach, describe, expect, it, vi } from "vitest"; import type { PluginManifestRegistry } from "./manifest-registry.js"; const mocks = vi.hoisted(() => { const loadManifestRegistry = vi.fn(); return { findBundledPluginMetadataById: vi.fn(), loadPluginManifestRegistryForInstalledIndex: loadManifestRegistry, loadPluginManifestRegistryForPluginRegistry: loadManifestRegistry, loadPluginRegistrySnapshot: vi.fn(() => ({ plugins: [] })), }; }); vi.mock("./bundled-plugin-metadata.js", () => ({ findBundledPluginMetadataById: mocks.findBundledPluginMetadataById, })); vi.mock("./manifest-registry-installed.js", () => ({ loadPluginManifestRegistryForInstalledIndex: mocks.loadPluginManifestRegistryForInstalledIndex, })); vi.mock("./plugin-registry.js", () => ({ loadPluginManifestRegistryForPluginRegistry: mocks.loadPluginManifestRegistryForPluginRegistry, loadPluginRegistrySnapshot: mocks.loadPluginRegistrySnapshot, })); import { resolvePluginConfigContractsById } from "./config-contracts.js"; type PluginManifestRecord = PluginManifestRegistry["plugins"][number]; function createRegistry(plugins: PluginManifestRegistry["plugins"]): PluginManifestRegistry { return { plugins, diagnostics: [], }; } function createPluginRecord( overrides: Pick & Partial, ): PluginManifestRecord { return { rootDir: `/tmp/${overrides.id}`, manifestPath: `/tmp/${overrides.id}/openclaw.plugin.json`, channelConfigs: undefined, providerAuthEnvVars: undefined, configUiHints: undefined, configSchema: undefined, configContracts: undefined, contracts: undefined, name: undefined, description: undefined, version: undefined, enabledByDefault: undefined, autoEnableWhenConfiguredProviders: undefined, legacyPluginIds: undefined, format: undefined, bundleFormat: undefined, bundleCapabilities: undefined, kind: undefined, channels: [], providers: [], modelSupport: undefined, cliBackends: [], channelEnvVars: undefined, providerAuthAliases: undefined, providerAuthChoices: undefined, skills: [], settingsFiles: undefined, hooks: [], source: `/tmp/${overrides.id}/openclaw.plugin.json`, setupSource: undefined, startupDeferConfiguredChannelFullLoadUntilAfterListen: undefined, channelCatalogMeta: undefined, ...overrides, }; } describe("resolvePluginConfigContractsById", () => { beforeEach(() => { mocks.findBundledPluginMetadataById.mockReset(); mocks.loadPluginManifestRegistryForInstalledIndex.mockReset(); mocks.loadPluginManifestRegistryForInstalledIndex.mockReturnValue(createRegistry([])); mocks.loadPluginRegistrySnapshot.mockReset(); mocks.loadPluginRegistrySnapshot.mockReturnValue({ plugins: [] }); }); it("does not fall back to bundled metadata when registry already resolved a plugin without config contracts", () => { mocks.loadPluginManifestRegistryForInstalledIndex.mockReturnValue( createRegistry([ createPluginRecord({ id: "brave", origin: "bundled", }), ]), ); expect( resolvePluginConfigContractsById({ pluginIds: ["brave"], }), ).toEqual(new Map()); expect(mocks.findBundledPluginMetadataById).not.toHaveBeenCalled(); }); it("can hydrate missing contracts from bundled metadata for resolved bundled plugins", () => { mocks.loadPluginManifestRegistryForInstalledIndex.mockReturnValue( createRegistry([ createPluginRecord({ id: "voice-call", origin: "bundled", configContracts: { compatibilityMigrationPaths: ["plugins.entries.voice-call.config"], }, }), ]), ); mocks.findBundledPluginMetadataById.mockReturnValue({ manifest: { configContracts: { secretInputs: { paths: [{ path: "twilio.authToken", expected: "string" }], }, }, }, }); expect( resolvePluginConfigContractsById({ pluginIds: ["voice-call"], fallbackToBundledMetadataForResolvedBundled: true, }), ).toEqual( new Map([ [ "voice-call", { origin: "bundled", configContracts: { compatibilityMigrationPaths: ["plugins.entries.voice-call.config"], secretInputs: { paths: [{ path: "twilio.authToken", expected: "string" }], }, }, }, ], ]), ); }); it("can hydrate missing contracts for plugin ids known to be bundled by runtime discovery", () => { mocks.loadPluginManifestRegistryForInstalledIndex.mockReturnValue( createRegistry([ createPluginRecord({ id: "voice-call", origin: "config", }), ]), ); mocks.findBundledPluginMetadataById.mockReturnValue({ manifest: { configContracts: { secretInputs: { paths: [{ path: "tts.providers.*.apiKey", expected: "string" }], }, }, }, }); expect( resolvePluginConfigContractsById({ pluginIds: ["voice-call"], fallbackBundledPluginIds: ["voice-call"], }), ).toEqual( new Map([ [ "voice-call", { origin: "bundled", configContracts: { secretInputs: { paths: [{ path: "tts.providers.*.apiKey", expected: "string" }], }, }, }, ], ]), ); }); it("can skip bundled metadata fallback for registry-scoped callers", () => { expect( resolvePluginConfigContractsById({ pluginIds: ["missing"], fallbackToBundledMetadata: false, }), ).toEqual(new Map()); expect(mocks.findBundledPluginMetadataById).not.toHaveBeenCalled(); }); });