mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-18 15:44:46 +00:00
test: clear manifest registry broad matchers
This commit is contained in:
@@ -119,9 +119,63 @@ function expectRegistryDiagnosticContains(
|
||||
registry: ReturnType<typeof loadPluginManifestRegistry>,
|
||||
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<typeof loadPluginManifestRegistry>,
|
||||
fragment: string,
|
||||
) {
|
||||
expect(registry.diagnostics.some((diag) => diag.message.includes(fragment))).toBe(false);
|
||||
}
|
||||
|
||||
function requireRecord(value: unknown, label: string): Record<string, unknown> {
|
||||
expect(
|
||||
typeof value === "object" && value !== null && !Array.isArray(value),
|
||||
`${label} object`,
|
||||
).toBe(true);
|
||||
return value as Record<string, unknown>;
|
||||
}
|
||||
|
||||
function expectRecordFields(
|
||||
value: unknown,
|
||||
label: string,
|
||||
expected: Record<string, unknown>,
|
||||
): Record<string, unknown> {
|
||||
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<typeof loadPluginManifestRegistry>,
|
||||
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");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user