From ec482c75648e265e42ef1096f62e5f3b514a7cde Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 10 May 2026 11:57:13 +0100 Subject: [PATCH] test: clear manifest registry broad matchers --- src/plugins/manifest-registry.test.ts | 210 +++++++++++++++----------- 1 file changed, 118 insertions(+), 92 deletions(-) diff --git a/src/plugins/manifest-registry.test.ts b/src/plugins/manifest-registry.test.ts index 0795d8f44f6..f4e62a56c86 100644 --- a/src/plugins/manifest-registry.test.ts +++ b/src/plugins/manifest-registry.test.ts @@ -119,9 +119,63 @@ function expectRegistryDiagnosticContains( registry: ReturnType, fragment: string, ) { - expect(registry.diagnostics.map((diag) => diag.message)).toEqual( - expect.arrayContaining([expect.stringContaining(fragment)]), - ); + expect(registry.diagnostics.some((diag) => diag.message.includes(fragment))).toBe(true); +} + +function expectNoRegistryDiagnosticContains( + registry: ReturnType, + fragment: string, +) { + expect(registry.diagnostics.some((diag) => diag.message.includes(fragment))).toBe(false); +} + +function requireRecord(value: unknown, label: string): Record { + expect( + typeof value === "object" && value !== null && !Array.isArray(value), + `${label} object`, + ).toBe(true); + return value as Record; +} + +function expectRecordFields( + value: unknown, + label: string, + expected: Record, +): Record { + const record = requireRecord(value, label); + for (const [key, expectedValue] of Object.entries(expected)) { + expect(record[key], `${label}.${key}`).toEqual(expectedValue); + } + return record; +} + +function expectArrayIncludesAll(value: unknown, expected: readonly unknown[], label: string) { + expect(Array.isArray(value), `${label} array`).toBe(true); + for (const item of expected) { + expect(value as unknown[], `${label} item ${String(item)}`).toContain(item); + } +} + +function expectDiagnosticFields( + registry: ReturnType, + expected: { level?: string; pluginId?: string; source?: string; messageIncludes?: string }, +) { + const diagnostic = registry.diagnostics.find((entry) => { + if (expected.level && entry.level !== expected.level) { + return false; + } + if (expected.pluginId && entry.pluginId !== expected.pluginId) { + return false; + } + if (expected.source && entry.source !== expected.source) { + return false; + } + if (expected.messageIncludes && !entry.message.includes(expected.messageIncludes)) { + return false; + } + return true; + }); + expect(diagnostic, `diagnostic ${expected.messageIncludes ?? ""}`).toBeDefined(); } function prepareLinkedManifestFixture(params: { id: string; mode: "symlink" | "hardlink" }): { @@ -455,17 +509,13 @@ describe("loadPluginManifestRegistry", () => { config: { plugins: { entries: { "external-chat": { enabled: false } } } }, candidates: [candidate], }); - expect(disabledRegistry.diagnostics.map((diagnostic) => diagnostic.message)).not.toEqual( - expect.arrayContaining([expect.stringContaining("without channelConfigs metadata")]), - ); + expectNoRegistryDiagnosticContains(disabledRegistry, "without channelConfigs metadata"); const allowlistRegistry = loadPluginManifestRegistry({ config: { plugins: { allow: ["other-plugin"] } }, candidates: [candidate], }); - expect(allowlistRegistry.diagnostics.map((diagnostic) => diagnostic.message)).not.toEqual( - expect.arrayContaining([expect.stringContaining("without channelConfigs metadata")]), - ); + expectNoRegistryDiagnosticContains(allowlistRegistry, "without channelConfigs metadata"); }); it("suppresses duplicate warnings for explicit installed globals overriding bundled plugins", () => { @@ -590,12 +640,10 @@ describe("loadPluginManifestRegistry", () => { }); expect(registry.plugins).toHaveLength(1); - expect(registry.plugins[0]).toEqual( - expect.objectContaining({ - origin: "config", - trustedOfficialInstall: true, - }), - ); + expectRecordFields(registry.plugins[0], "plugin", { + origin: "config", + trustedOfficialInstall: true, + }); }); it("does not trust unrecorded globals that spoof official ids", () => { @@ -1064,16 +1112,12 @@ describe("loadPluginManifestRegistry", () => { expect(registry.plugins[0]?.providerAuthEnvVars).toEqual({ openai: ["OPENAI_API_KEY"], }); - expect(registry.diagnostics).toContainEqual( - expect.objectContaining({ - level: "warn", - pluginId: "external-openai", - source: path.join(dir, "openclaw.plugin.json"), - message: expect.stringContaining( - "providerAuthEnvVars is deprecated compatibility metadata", - ), - }), - ); + expectDiagnosticFields(registry, { + level: "warn", + pluginId: "external-openai", + source: path.join(dir, "openclaw.plugin.json"), + messageIncludes: "providerAuthEnvVars is deprecated compatibility metadata", + }); }); it("does not report deprecated providerAuthEnvVars when setup providers mirror env vars", () => { @@ -1096,12 +1140,9 @@ describe("loadPluginManifestRegistry", () => { origin: "global", }); - expect(registry.diagnostics).not.toContainEqual( - expect.objectContaining({ - message: expect.stringContaining( - "providerAuthEnvVars is deprecated compatibility metadata", - ), - }), + expectNoRegistryDiagnosticContains( + registry, + "providerAuthEnvVars is deprecated compatibility metadata", ); }); @@ -1148,14 +1189,12 @@ describe("loadPluginManifestRegistry", () => { }); expect(registry.plugins[0]?.channels).toEqual(["external-chat"]); - expect(registry.diagnostics).toContainEqual( - expect.objectContaining({ - level: "warn", - pluginId: "external-chat", - source: path.join(dir, "openclaw.plugin.json"), - message: expect.stringContaining("without channelConfigs metadata"), - }), - ); + expectDiagnosticFields(registry, { + level: "warn", + pluginId: "external-chat", + source: path.join(dir, "openclaw.plugin.json"), + messageIncludes: "without channelConfigs metadata", + }); }); it("sanitizes manifest-controlled fields in channel config descriptor diagnostics", () => { @@ -1208,13 +1247,11 @@ describe("loadPluginManifestRegistry", () => { origin: "global", }); - expect(registry.plugins[0]?.channelConfigs?.["external-chat"]?.schema).toMatchObject({ + expectRecordFields(registry.plugins[0]?.channelConfigs?.["external-chat"]?.schema, "schema", { type: "object", additionalProperties: false, }); - expect(registry.diagnostics.map((diagnostic) => diagnostic.message)).not.toEqual( - expect.arrayContaining([expect.stringContaining("without channelConfigs metadata")]), - ); + expectNoRegistryDiagnosticContains(registry, "without channelConfigs metadata"); }); it("hydrates supplemental official external catalog contracts for lagging npm manifests", () => { @@ -1235,17 +1272,15 @@ describe("loadPluginManifestRegistry", () => { ]); expect(registry.plugins[0]?.contracts?.tools).toEqual(["wecom_mcp"]); - expect(registry.plugins[0]?.channelConfigs?.wecom).toEqual( - expect.objectContaining({ + const wecomConfig = expectRecordFields( + registry.plugins[0]?.channelConfigs?.wecom, + "wecom config", + { label: "WeCom", - schema: expect.objectContaining({ - type: "object", - }), - }), - ); - expect(registry.diagnostics.map((diagnostic) => diagnostic.message)).not.toEqual( - expect.arrayContaining([expect.stringContaining("without channelConfigs metadata")]), + }, ); + expectRecordFields(wecomConfig.schema, "wecom schema", { type: "object" }); + expectNoRegistryDiagnosticContains(registry, "without channelConfigs metadata"); }); it("fills missing official external catalog descriptors for partial npm channel configs", () => { @@ -1276,18 +1311,20 @@ describe("loadPluginManifestRegistry", () => { }), ]); - expect(registry.plugins[0]?.channelConfigs?.wecom).toEqual( - expect.objectContaining({ + const wecomConfig = expectRecordFields( + registry.plugins[0]?.channelConfigs?.wecom, + "wecom config", + { label: "WeCom", description: "Enterprise WeChat conversation channel.", - schema: expect.objectContaining({ - additionalProperties: false, - properties: { - corpId: { type: "string" }, - }, - }), - }), + }, ); + expectRecordFields(wecomConfig.schema, "wecom schema", { + additionalProperties: false, + properties: { + corpId: { type: "string" }, + }, + }); }); it("drops prototype-polluting channel config keys from plugin manifests", () => { @@ -1338,7 +1375,7 @@ describe("loadPluginManifestRegistry", () => { expect(Object.prototype.hasOwnProperty.call(channelConfigs, "__proto__")).toBe(false); expect(Object.prototype.hasOwnProperty.call(channelConfigs, "constructor")).toBe(false); expect(Object.prototype.hasOwnProperty.call(channelConfigs, "prototype")).toBe(false); - expect(channelConfigs["safe-chat"]?.schema).toMatchObject({ + expectRecordFields(channelConfigs["safe-chat"]?.schema, "safe-chat schema", { type: "object", additionalProperties: false, }); @@ -1754,13 +1791,11 @@ describe("loadPluginManifestRegistry", () => { }), ]); - expect(registry.plugins[0]?.channelConfigs?.telegram).toEqual( - expect.objectContaining({ - schema: expect.objectContaining({ - type: "object", - }), - }), + const telegramConfig = requireRecord( + registry.plugins[0]?.channelConfigs?.telegram, + "telegram config", ); + expectRecordFields(telegramConfig.schema, "telegram schema", { type: "object" }); }); it("preserves manifest-owned config contracts from plugin manifests", () => { @@ -1935,9 +1970,7 @@ describe("loadPluginManifestRegistry", () => { }); expect(registry.plugins.map((plugin) => plugin.id)).toEqual(["codex"]); - expect(registry.diagnostics.map((diag) => diag.message)).not.toEqual( - expect.arrayContaining([expect.stringContaining("openclaw.install.minHostVersion must use")]), - ); + expectNoRegistryDiagnosticContains(registry, "openclaw.install.minHostVersion must use"); }); it("does not runtime-gate bundled source plugins by install minHostVersion", () => { @@ -1963,9 +1996,7 @@ describe("loadPluginManifestRegistry", () => { }); expect(registry.plugins.map((plugin) => plugin.id)).toContain("codex"); - expect(registry.diagnostics.map((diag) => diag.message)).not.toEqual( - expect.arrayContaining([expect.stringContaining("requires OpenClaw")]), - ); + expectNoRegistryDiagnosticContains(registry, "requires OpenClaw"); }); it.each([ @@ -2115,8 +2146,8 @@ describe("loadPluginManifestRegistry", () => { bundleFormat: "codex", hooks: ["hooks"], skills: ["skills"], - bundleCapabilities: expect.arrayContaining(["hooks", "skills"]), }, + expectedCapabilities: ["hooks", "skills"], }, { name: "loads Claude bundle manifests with command roots and settings files", @@ -2143,8 +2174,8 @@ describe("loadPluginManifestRegistry", () => { bundleFormat: "claude", skills: ["skill-packs/starter", "commands-pack"], settingsFiles: ["settings.json"], - bundleCapabilities: expect.arrayContaining(["skills", "commands", "settings"]), }, + expectedCapabilities: ["skills", "commands", "settings"], }, { name: "loads manifestless Claude bundles into the registry", @@ -2164,8 +2195,8 @@ describe("loadPluginManifestRegistry", () => { bundleFormat: "claude", skills: ["commands"], settingsFiles: ["settings.json"], - bundleCapabilities: expect.arrayContaining(["skills", "commands", "settings"]), }, + expectedCapabilities: ["skills", "commands", "settings"], }, { name: "loads Cursor bundle manifests into the registry", @@ -2191,16 +2222,10 @@ describe("loadPluginManifestRegistry", () => { format: "bundle", bundleFormat: "cursor", skills: ["skills", ".cursor/commands"], - bundleCapabilities: expect.arrayContaining([ - "skills", - "commands", - "rules", - "hooks", - "mcpServers", - ]), }, + expectedCapabilities: ["skills", "commands", "rules", "hooks", "mcpServers"], }, - ] as const)("$name", ({ idHint, bundleFormat, setup, expected }) => { + ] as const)("$name", ({ idHint, bundleFormat, setup, expected, expectedCapabilities }) => { const registry = loadBundleRegistry({ idHint, bundleFormat, @@ -2208,7 +2233,12 @@ describe("loadPluginManifestRegistry", () => { }); expect(registry.plugins).toHaveLength(1); - expect(registry.plugins[0]).toMatchObject(expected); + expectRecordFields(registry.plugins[0], "bundle plugin", expected); + expectArrayIncludesAll( + registry.plugins[0]?.bundleCapabilities, + expectedCapabilities, + "bundle capabilities", + ); }); it("prefers higher-precedence origins for the same physical directory (config > workspace > global > bundled)", () => { @@ -2376,12 +2406,8 @@ describe("loadPluginManifestRegistry", () => { }); expect(olderHost.plugins).toStrictEqual([]); - expect(olderHost.diagnostics.map((diag) => diag.message)).toEqual( - expect.arrayContaining([expect.stringContaining("this host is 2026.3.21")]), - ); + expectRegistryDiagnosticContains(olderHost, "this host is 2026.3.21"); expect(newerHost.plugins.map((plugin) => plugin.id)).toContain("synology-chat"); - expect(newerHost.diagnostics.map((diag) => diag.message)).not.toEqual( - expect.arrayContaining([expect.stringContaining("this host is 2026.3.21")]), - ); + expectNoRegistryDiagnosticContains(newerHost, "this host is 2026.3.21"); }); });