diff --git a/src/plugins/bundled-plugin-metadata.test.ts b/src/plugins/bundled-plugin-metadata.test.ts index 4c3c0e8782f..83dce8b6665 100644 --- a/src/plugins/bundled-plugin-metadata.test.ts +++ b/src/plugins/bundled-plugin-metadata.test.ts @@ -37,6 +37,22 @@ function expectGeneratedPathResolution(tempRoot: string, expectedRelativePath: s ).toBe(path.join(tempRoot, expectedRelativePath)); } +function expectArtifactPresence( + artifacts: readonly string[] | undefined, + params: { contains?: readonly string[]; excludes?: readonly string[] }, +) { + if (params.contains) { + for (const artifact of params.contains) { + expect(artifacts).toContain(artifact); + } + } + if (params.excludes) { + for (const artifact of params.excludes) { + expect(artifacts).not.toContain(artifact); + } + } +} + async function writeGeneratedMetadataModule(params: { repoRoot: string; outputPath?: string; @@ -64,11 +80,13 @@ describe("bundled plugin metadata", () => { const discord = BUNDLED_PLUGIN_METADATA.find((entry) => entry.dirName === "discord"); expect(discord?.source).toEqual({ source: "./index.ts", built: "index.js" }); expect(discord?.setupSource).toEqual({ source: "./setup-entry.ts", built: "setup-entry.js" }); - expect(discord?.publicSurfaceArtifacts).toContain("api.js"); - expect(discord?.publicSurfaceArtifacts).toContain("runtime-api.js"); - expect(discord?.publicSurfaceArtifacts).toContain("session-key-api.js"); - expect(discord?.publicSurfaceArtifacts).not.toContain("test-api.js"); - expect(discord?.runtimeSidecarArtifacts).toContain("runtime-api.js"); + expectArtifactPresence(discord?.publicSurfaceArtifacts, { + contains: ["api.js", "runtime-api.js", "session-key-api.js"], + excludes: ["test-api.js"], + }); + expectArtifactPresence(discord?.runtimeSidecarArtifacts, { + contains: ["runtime-api.js"], + }); expect(discord?.manifest.id).toBe("discord"); expect(discord?.manifest.channelConfigs?.discord).toEqual( expect.objectContaining({ diff --git a/src/plugins/channel-plugin-ids.test.ts b/src/plugins/channel-plugin-ids.test.ts index 2744e943586..d7b5bc9636f 100644 --- a/src/plugins/channel-plugin-ids.test.ts +++ b/src/plugins/channel-plugin-ids.test.ts @@ -72,6 +72,10 @@ function expectStartupPluginIds(config: OpenClawConfig, expected: readonly strin ).toEqual(expected); } +function expectManifestRegistryFixture() { + expect(loadPluginManifestRegistry).toHaveBeenCalled(); +} + describe("resolveGatewayStartupPluginIds", () => { beforeEach(() => { listPotentialConfiguredChannelIds.mockReset().mockReturnValue(["demo-channel"]); @@ -119,5 +123,6 @@ describe("resolveGatewayStartupPluginIds", () => { ], ] as const)("%s", (_name, config, expected) => { expectStartupPluginIds(config, expected); + expectManifestRegistryFixture(); }); }); diff --git a/src/plugins/cli.test.ts b/src/plugins/cli.test.ts index fd658b32ede..68d63cd64d2 100644 --- a/src/plugins/cli.test.ts +++ b/src/plugins/cli.test.ts @@ -46,6 +46,14 @@ function createCliRegistry() { }; } +function expectPluginLoaderConfig(config: OpenClawConfig) { + expect(mocks.loadOpenClawPlugins).toHaveBeenCalledWith( + expect.objectContaining({ + config, + }), + ); +} + describe("registerPluginCliCommands", () => { beforeEach(() => { mocks.memoryRegister.mockClear(); @@ -101,11 +109,7 @@ describe("registerPluginCliCommands", () => { config: rawConfig, env: process.env, }); - expect(mocks.loadOpenClawPlugins).toHaveBeenCalledWith( - expect.objectContaining({ - config: autoEnabledConfig, - }), - ); + expectPluginLoaderConfig(autoEnabledConfig); expect(mocks.memoryRegister).toHaveBeenCalledWith( expect.objectContaining({ config: autoEnabledConfig, diff --git a/src/plugins/config-state.test.ts b/src/plugins/config-state.test.ts index 68f33b2664e..6d8d2806cdf 100644 --- a/src/plugins/config-state.test.ts +++ b/src/plugins/config-state.test.ts @@ -20,6 +20,15 @@ function expectResolvedEnableState( expect(resolveEnableState(...params)).toEqual(expected); } +function expectMemoryPluginState( + config: Parameters[0], + expected: ReturnType, +) { + expect(resolveEnableState("memory-core", "bundled", normalizePluginsConfig(config))).toEqual( + expected, + ); +} + describe("normalizePluginsConfig", () => { it.each([ [{}, "memory-core"], @@ -177,22 +186,18 @@ describe("resolveEnableState", () => { ); it("keeps the selected memory slot plugin enabled even when omitted from plugins.allow", () => { - const state = resolveEnableState( - "memory-core", - "bundled", - normalizePluginsConfig({ + expectMemoryPluginState( + { allow: ["telegram"], slots: { memory: "memory-core" }, - }), + }, + { enabled: true }, ); - expect(state).toEqual({ enabled: true }); }); it("keeps explicit disable authoritative for the selected memory slot plugin", () => { - const state = resolveEnableState( - "memory-core", - "bundled", - normalizePluginsConfig({ + expectMemoryPluginState( + { allow: ["telegram"], slots: { memory: "memory-core" }, entries: { @@ -200,9 +205,9 @@ describe("resolveEnableState", () => { enabled: false, }, }, - }), + }, + { enabled: false, reason: "disabled in config" }, ); - expect(state).toEqual({ enabled: false, reason: "disabled in config" }); }); it.each([ diff --git a/src/plugins/enable.test.ts b/src/plugins/enable.test.ts index 3f6b29014dd..00fb82faddd 100644 --- a/src/plugins/enable.test.ts +++ b/src/plugins/enable.test.ts @@ -15,6 +15,13 @@ function expectEnableResult( params.assert(result); } +function expectEnabledAllowlist( + result: ReturnType, + expected: string[], +) { + expect(result.config.plugins?.allow).toEqual(expected); +} + describe("enablePluginInConfig", () => { it.each([ { @@ -36,7 +43,7 @@ describe("enablePluginInConfig", () => { pluginId: "google", expectedEnabled: true, assert: (result: ReturnType) => { - expect(result.config.plugins?.allow).toEqual(["memory-core", "google"]); + expectEnabledAllowlist(result, ["memory-core", "google"]); }, }, { @@ -73,7 +80,7 @@ describe("enablePluginInConfig", () => { expectedEnabled: true, assert: (result: ReturnType) => { expect(result.config.channels?.telegram?.enabled).toBe(true); - expect(result.config.plugins?.allow).toEqual(["memory-core", "telegram"]); + expectEnabledAllowlist(result, ["memory-core", "telegram"]); }, }, { diff --git a/src/plugins/installs.test.ts b/src/plugins/installs.test.ts index 2d81553a421..d042325f191 100644 --- a/src/plugins/installs.test.ts +++ b/src/plugins/installs.test.ts @@ -9,6 +9,20 @@ function expectRecordedInstall(pluginId: string, next: ReturnType>, +) { + return { + resolvedName: undefined, + resolvedVersion: undefined, + resolvedSpec: undefined, + integrity: undefined, + shasum: undefined, + resolvedAt: undefined, + ...overrides, + }; +} + describe("buildNpmResolutionInstallFields", () => { it.each([ { @@ -21,26 +35,19 @@ describe("buildNpmResolutionInstallFields", () => { shasum: "deadbeef", resolvedAt: "2026-02-22T00:00:00.000Z", }, - expected: { + expected: createExpectedResolutionFields({ resolvedName: "@openclaw/demo", resolvedVersion: "1.2.3", resolvedSpec: "@openclaw/demo@1.2.3", integrity: "sha512-abc", shasum: "deadbeef", resolvedAt: "2026-02-22T00:00:00.000Z", - }, + }), }, { name: "returns undefined fields when resolution is missing", input: undefined, - expected: { - resolvedName: undefined, - resolvedVersion: undefined, - resolvedSpec: undefined, - integrity: undefined, - shasum: undefined, - resolvedAt: undefined, - }, + expected: createExpectedResolutionFields({}), }, ] as const)("$name", ({ input, expected }) => { expect(buildNpmResolutionInstallFields(input)).toEqual(expected); diff --git a/src/plugins/lazy-service-module.test.ts b/src/plugins/lazy-service-module.test.ts index 677559b2fbe..0f8b24bf138 100644 --- a/src/plugins/lazy-service-module.test.ts +++ b/src/plugins/lazy-service-module.test.ts @@ -18,6 +18,22 @@ function createLazyModuleLifecycle() { }; } +async function expectLifecycleStarted(params: { + overrideEnvVar?: string; + loadDefaultModule?: () => Promise>; + loadOverrideModule?: (spec: string) => Promise>; + startExportNames: string[]; + stopExportNames?: string[]; +}) { + return startLazyPluginServiceModule({ + ...(params.overrideEnvVar ? { overrideEnvVar: params.overrideEnvVar } : {}), + loadDefaultModule: params.loadDefaultModule ?? (async () => createLazyModuleLifecycle().module), + ...(params.loadOverrideModule ? { loadOverrideModule: params.loadOverrideModule } : {}), + startExportNames: params.startExportNames, + ...(params.stopExportNames ? { stopExportNames: params.stopExportNames } : {}), + }); +} + describe("startLazyPluginServiceModule", () => { afterEach(() => { delete process.env.OPENCLAW_LAZY_SERVICE_SKIP; @@ -27,7 +43,7 @@ describe("startLazyPluginServiceModule", () => { it("starts the default module and returns its stop hook", async () => { const lifecycle = createLazyModuleLifecycle(); - const handle = await startLazyPluginServiceModule({ + const handle = await expectLifecycleStarted({ loadDefaultModule: async () => lifecycle.module, startExportNames: ["startDefault"], stopExportNames: ["stopDefault"], @@ -58,7 +74,7 @@ describe("startLazyPluginServiceModule", () => { const start = createAsyncHookMock(); const loadOverrideModule = vi.fn(async () => ({ startOverride: start })); - await startLazyPluginServiceModule({ + await expectLifecycleStarted({ overrideEnvVar: "OPENCLAW_LAZY_SERVICE_OVERRIDE", loadDefaultModule: async () => ({ startDefault: createAsyncHookMock() }), loadOverrideModule, diff --git a/src/plugins/services.test.ts b/src/plugins/services.test.ts index eb89e51ac16..0d9637a090b 100644 --- a/src/plugins/services.test.ts +++ b/src/plugins/services.test.ts @@ -39,6 +39,10 @@ function expectServiceContext( expect(ctx.config).toBe(config); expect(ctx.workspaceDir).toBe("/tmp/workspace"); expect(ctx.stateDir).toBe(STATE_DIR); + expectServiceLogger(ctx); +} + +function expectServiceLogger(ctx: OpenClawPluginServiceContext) { expect(ctx.logger).toBeDefined(); expect(typeof ctx.logger.info).toBe("function"); expect(typeof ctx.logger.warn).toBe("function"); diff --git a/src/plugins/slots.test.ts b/src/plugins/slots.test.ts index 9d2cd063c1c..c0ca9ea0a88 100644 --- a/src/plugins/slots.test.ts +++ b/src/plugins/slots.test.ts @@ -63,6 +63,11 @@ describe("applyExclusiveSlotSelection", () => { } } + function expectUnchangedSelection(result: ReturnType) { + expect(result.changed).toBe(false); + expect(result.warnings).toHaveLength(0); + } + it("selects the slot and disables other entries for the same kind", () => { const config = createMemoryConfig({ slots: { memory: "memory-core" }, @@ -94,8 +99,7 @@ describe("applyExclusiveSlotSelection", () => { registry: { plugins: [{ id: "memory", kind: "memory" }] }, }); - expect(result.changed).toBe(false); - expect(result.warnings).toHaveLength(0); + expectUnchangedSelection(result); expect(result.config).toBe(config); }); @@ -140,8 +144,7 @@ describe("applyExclusiveSlotSelection", () => { selectedId: "custom", }); - expect(result.changed).toBe(false); - expect(result.warnings).toHaveLength(0); + expectUnchangedSelection(result); expect(result.config).toBe(config); }); }); diff --git a/src/plugins/source-display.test.ts b/src/plugins/source-display.test.ts index 6866f53e0cf..6a01843c764 100644 --- a/src/plugins/source-display.test.ts +++ b/src/plugins/source-display.test.ts @@ -3,24 +3,11 @@ import { describe, expect, it } from "vitest"; import { withPathResolutionEnv } from "../test-utils/env.js"; import { formatPluginSourceForTable, resolvePluginSourceRoots } from "./source-display.js"; -function createPluginSourceRoots() { - const stockRoot = path.resolve( - path.sep, - "opt", - "homebrew", - "lib", - "node_modules", - "openclaw", - "extensions", - ); - const globalRoot = path.resolve(path.sep, "Users", "x", ".openclaw", "extensions"); - const workspaceRoot = path.resolve(path.sep, "Users", "x", "ws", ".openclaw", "extensions"); - return { - stock: stockRoot, - global: globalRoot, - workspace: workspaceRoot, - }; -} +const PLUGIN_SOURCE_ROOTS = { + stock: path.resolve(path.sep, "opt", "homebrew", "lib", "node_modules", "openclaw", "extensions"), + global: path.resolve(path.sep, "Users", "x", ".openclaw", "extensions"), + workspace: path.resolve(path.sep, "Users", "x", "ws", ".openclaw", "extensions"), +}; function expectFormattedSource(params: { origin: "bundled" | "workspace" | "global"; @@ -30,13 +17,12 @@ function expectFormattedSource(params: { expectedValue: string; expectedRootKey: "stock" | "workspace" | "global"; }) { - const roots = createPluginSourceRoots(); const out = formatPluginSourceForTable( { origin: params.origin, - source: path.join(roots[params.sourceKey], params.dirName, params.fileName), + source: path.join(PLUGIN_SOURCE_ROOTS[params.sourceKey], params.dirName, params.fileName), }, - roots, + PLUGIN_SOURCE_ROOTS, ); expect(out.value).toBe(params.expectedValue); expect(out.rootKey).toBe(params.expectedRootKey);