Files
openclaw/test/helpers/channels/channel-catalog-contract.ts
2026-04-06 04:17:52 +01:00

214 lines
6.7 KiB
TypeScript

import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { describe, expect, it } from "vitest";
import {
getChannelPluginCatalogEntry,
listChannelPluginCatalogEntries,
} from "../../../src/channels/plugins/catalog.js";
type CatalogEntryMeta = {
id: string;
label: string;
selectionLabel: string;
docsPath: string;
blurb: string;
detailLabel?: string;
aliases?: string[];
};
export function describeChannelCatalogEntryContract(params: {
channelId: string;
npmSpec: string;
alias?: string;
}) {
describe(`${params.channelId} channel catalog contract`, () => {
it("keeps the shipped catalog entry aligned", () => {
const entry = getChannelPluginCatalogEntry(params.channelId);
expect(entry?.install.npmSpec).toBe(params.npmSpec);
if (params.alias) {
expect(entry?.meta.aliases).toContain(params.alias);
}
});
it("appears in the channel catalog listing", () => {
const ids = listChannelPluginCatalogEntries().map((entry) => entry.id);
expect(ids).toContain(params.channelId);
});
});
}
export function describeBundledMetadataOnlyChannelCatalogContract(params: {
pluginId: string;
packageName: string;
npmSpec: string;
meta: CatalogEntryMeta;
defaultChoice?: string;
}) {
describe(`${params.pluginId} bundled metadata-only channel catalog contract`, () => {
it("includes the bundled metadata-only channel entry when the runtime entrypoint is omitted", () => {
const packageRoot = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-bundled-catalog-"));
const bundledDir = path.join(packageRoot, "dist", "extensions", params.pluginId);
fs.mkdirSync(bundledDir, { recursive: true });
fs.writeFileSync(
path.join(packageRoot, "package.json"),
JSON.stringify({ name: "openclaw" }),
"utf8",
);
fs.writeFileSync(
path.join(bundledDir, "package.json"),
JSON.stringify({
name: params.packageName,
openclaw: {
extensions: ["./index.js"],
channel: params.meta,
install: {
npmSpec: params.npmSpec,
defaultChoice: params.defaultChoice,
},
},
}),
"utf8",
);
fs.writeFileSync(path.join(bundledDir, "index.js"), "export default {};\n", "utf8");
fs.writeFileSync(
path.join(bundledDir, "openclaw.plugin.json"),
JSON.stringify({ id: params.pluginId, channels: [params.meta.id], configSchema: {} }),
"utf8",
);
const entry = listChannelPluginCatalogEntries({
env: {
...process.env,
OPENCLAW_BUNDLED_PLUGINS_DIR: path.join(packageRoot, "dist", "extensions"),
},
}).find((item) => item.id === params.meta.id);
expect(entry?.install.npmSpec).toBe(params.npmSpec);
expect(entry?.pluginId).toBe(params.pluginId);
});
});
}
export function describeOfficialFallbackChannelCatalogContract(params: {
channelId: string;
npmSpec: string;
meta: CatalogEntryMeta;
packageName: string;
pluginId: string;
externalNpmSpec: string;
externalLabel: string;
}) {
describe(`${params.channelId} official fallback channel catalog contract`, () => {
it("includes shipped official channel catalog entries when bundled metadata is omitted", () => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-official-catalog-"));
const catalogPath = path.join(dir, "channel-catalog.json");
fs.writeFileSync(
catalogPath,
JSON.stringify({
entries: [
{
name: params.packageName,
openclaw: {
channel: params.meta,
install: {
npmSpec: params.npmSpec,
defaultChoice: "npm",
},
},
},
],
}),
);
const entry = listChannelPluginCatalogEntries({
env: {
...process.env,
OPENCLAW_BUNDLED_PLUGINS_DIR: "/nonexistent/bundled/plugins",
},
officialCatalogPaths: [catalogPath],
}).find((item) => item.id === params.channelId);
expect(entry?.install.npmSpec).toBe(params.npmSpec);
expect(entry?.pluginId).toBeUndefined();
});
it("lets external catalogs override shipped fallback channel metadata", () => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-fallback-catalog-"));
const bundledDir = path.join(dir, "dist", "extensions", params.pluginId);
const officialCatalogPath = path.join(dir, "channel-catalog.json");
const externalCatalogPath = path.join(dir, "catalog.json");
fs.mkdirSync(bundledDir, { recursive: true });
fs.writeFileSync(
path.join(bundledDir, "package.json"),
JSON.stringify({
name: params.packageName,
openclaw: {
channel: {
...params.meta,
label: `${params.meta.label} Bundled`,
selectionLabel: `${params.meta.label} Bundled`,
blurb: "bundled fallback",
},
install: { npmSpec: params.npmSpec },
},
}),
"utf8",
);
fs.writeFileSync(
officialCatalogPath,
JSON.stringify({
entries: [
{
name: params.packageName,
openclaw: {
channel: {
...params.meta,
label: `${params.meta.label} Official`,
selectionLabel: `${params.meta.label} Official`,
blurb: "official fallback",
},
install: { npmSpec: params.npmSpec },
},
},
],
}),
"utf8",
);
fs.writeFileSync(
externalCatalogPath,
JSON.stringify({
entries: [
{
name: params.externalNpmSpec,
openclaw: {
channel: {
...params.meta,
label: params.externalLabel,
selectionLabel: params.externalLabel,
blurb: "external override",
},
install: { npmSpec: params.externalNpmSpec },
},
},
],
}),
"utf8",
);
const entry = listChannelPluginCatalogEntries({
catalogPaths: [externalCatalogPath],
officialCatalogPaths: [officialCatalogPath],
env: {
...process.env,
OPENCLAW_BUNDLED_PLUGINS_DIR: path.join(dir, "dist", "extensions"),
},
}).find((item) => item.id === params.channelId);
expect(entry?.install.npmSpec).toBe(params.externalNpmSpec);
expect(entry?.meta.label).toBe(params.externalLabel);
expect(entry?.pluginId).toBeUndefined();
});
});
}