mirror of https://github.com/openclaw/openclaw.git
test(contracts): split channel catalog lanes
This commit is contained in:
parent
37de88181b
commit
0bbacca828
|
|
@ -1,270 +0,0 @@
|
|||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { listChannelPluginCatalogEntries } from "../catalog.js";
|
||||
|
||||
describe("channel plugin catalog", () => {
|
||||
function createCatalogEntry(params: {
|
||||
packageName: string;
|
||||
channelId: string;
|
||||
label: string;
|
||||
blurb: string;
|
||||
order?: number;
|
||||
}) {
|
||||
return {
|
||||
name: params.packageName,
|
||||
openclaw: {
|
||||
channel: {
|
||||
id: params.channelId,
|
||||
label: params.label,
|
||||
selectionLabel: params.label,
|
||||
docsPath: `/channels/${params.channelId}`,
|
||||
blurb: params.blurb,
|
||||
...(params.order === undefined ? {} : { order: params.order }),
|
||||
},
|
||||
install: {
|
||||
npmSpec: params.packageName,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function writeCatalogFile(catalogPath: string, entry: Record<string, unknown>) {
|
||||
fs.writeFileSync(
|
||||
catalogPath,
|
||||
JSON.stringify({
|
||||
entries: [entry],
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
function writeDiscoveredChannelPlugin(params: {
|
||||
stateDir: string;
|
||||
packageName: string;
|
||||
channelLabel: string;
|
||||
pluginId: string;
|
||||
blurb: string;
|
||||
}) {
|
||||
const pluginDir = path.join(params.stateDir, "extensions", "demo-channel-plugin");
|
||||
fs.mkdirSync(pluginDir, { recursive: true });
|
||||
fs.writeFileSync(
|
||||
path.join(pluginDir, "package.json"),
|
||||
JSON.stringify({
|
||||
name: params.packageName,
|
||||
openclaw: {
|
||||
extensions: ["./index.js"],
|
||||
channel: {
|
||||
id: "demo-channel",
|
||||
label: params.channelLabel,
|
||||
selectionLabel: params.channelLabel,
|
||||
docsPath: "/channels/demo-channel",
|
||||
blurb: params.blurb,
|
||||
},
|
||||
install: {
|
||||
npmSpec: params.packageName,
|
||||
},
|
||||
},
|
||||
}),
|
||||
"utf8",
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(pluginDir, "openclaw.plugin.json"),
|
||||
JSON.stringify({
|
||||
id: params.pluginId,
|
||||
configSchema: {},
|
||||
}),
|
||||
"utf8",
|
||||
);
|
||||
fs.writeFileSync(path.join(pluginDir, "index.js"), "module.exports = {}", "utf8");
|
||||
return pluginDir;
|
||||
}
|
||||
|
||||
function expectCatalogIdsContain(params: {
|
||||
expectedId: string;
|
||||
catalogPaths?: string[];
|
||||
env?: NodeJS.ProcessEnv;
|
||||
}) {
|
||||
const ids = listChannelPluginCatalogEntries({
|
||||
...(params.catalogPaths ? { catalogPaths: params.catalogPaths } : {}),
|
||||
...(params.env ? { env: params.env } : {}),
|
||||
}).map((entry) => entry.id);
|
||||
expect(ids).toContain(params.expectedId);
|
||||
}
|
||||
|
||||
function findCatalogEntry(params: {
|
||||
channelId: string;
|
||||
catalogPaths?: string[];
|
||||
env?: NodeJS.ProcessEnv;
|
||||
}) {
|
||||
return listChannelPluginCatalogEntries({
|
||||
...(params.catalogPaths ? { catalogPaths: params.catalogPaths } : {}),
|
||||
...(params.env ? { env: params.env } : {}),
|
||||
}).find((entry) => entry.id === params.channelId);
|
||||
}
|
||||
|
||||
function expectCatalogEntryMatch(params: {
|
||||
channelId: string;
|
||||
expected: Record<string, unknown>;
|
||||
catalogPaths?: string[];
|
||||
env?: NodeJS.ProcessEnv;
|
||||
}) {
|
||||
expect(
|
||||
findCatalogEntry({
|
||||
channelId: params.channelId,
|
||||
...(params.catalogPaths ? { catalogPaths: params.catalogPaths } : {}),
|
||||
...(params.env ? { env: params.env } : {}),
|
||||
}),
|
||||
).toMatchObject(params.expected);
|
||||
}
|
||||
|
||||
it.each([
|
||||
{
|
||||
name: "includes external catalog entries",
|
||||
setup: () => {
|
||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-catalog-"));
|
||||
const catalogPath = path.join(dir, "catalog.json");
|
||||
writeCatalogFile(
|
||||
catalogPath,
|
||||
createCatalogEntry({
|
||||
packageName: "@openclaw/demo-channel",
|
||||
channelId: "demo-channel",
|
||||
label: "Demo Channel",
|
||||
blurb: "Demo entry",
|
||||
order: 999,
|
||||
}),
|
||||
);
|
||||
return {
|
||||
channelId: "demo-channel",
|
||||
catalogPaths: [catalogPath],
|
||||
expected: { id: "demo-channel" },
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "preserves plugin ids when they differ from channel ids",
|
||||
setup: () => {
|
||||
const stateDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-channel-catalog-state-"));
|
||||
writeDiscoveredChannelPlugin({
|
||||
stateDir,
|
||||
packageName: "@vendor/demo-channel-plugin",
|
||||
channelLabel: "Demo Channel",
|
||||
pluginId: "@vendor/demo-runtime",
|
||||
blurb: "Demo channel",
|
||||
});
|
||||
return {
|
||||
channelId: "demo-channel",
|
||||
env: {
|
||||
...process.env,
|
||||
OPENCLAW_STATE_DIR: stateDir,
|
||||
OPENCLAW_BUNDLED_PLUGINS_DIR: "/nonexistent/bundled/plugins",
|
||||
},
|
||||
expected: { pluginId: "@vendor/demo-runtime" },
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "keeps discovered plugins ahead of external catalog overrides",
|
||||
setup: () => {
|
||||
const stateDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-catalog-state-"));
|
||||
const catalogPath = path.join(stateDir, "catalog.json");
|
||||
writeDiscoveredChannelPlugin({
|
||||
stateDir,
|
||||
packageName: "@vendor/demo-channel-plugin",
|
||||
channelLabel: "Demo Channel Runtime",
|
||||
pluginId: "@vendor/demo-channel-runtime",
|
||||
blurb: "discovered plugin",
|
||||
});
|
||||
writeCatalogFile(
|
||||
catalogPath,
|
||||
createCatalogEntry({
|
||||
packageName: "@vendor/demo-channel-catalog",
|
||||
channelId: "demo-channel",
|
||||
label: "Demo Channel Catalog",
|
||||
blurb: "external catalog",
|
||||
}),
|
||||
);
|
||||
return {
|
||||
channelId: "demo-channel",
|
||||
catalogPaths: [catalogPath],
|
||||
env: {
|
||||
...process.env,
|
||||
OPENCLAW_STATE_DIR: stateDir,
|
||||
CLAWDBOT_STATE_DIR: undefined,
|
||||
OPENCLAW_BUNDLED_PLUGINS_DIR: "/nonexistent/bundled/plugins",
|
||||
},
|
||||
expected: {
|
||||
install: { npmSpec: "@vendor/demo-channel-plugin" },
|
||||
meta: { label: "Demo Channel Runtime" },
|
||||
pluginId: "@vendor/demo-channel-runtime",
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
] as const)("$name", ({ setup }) => {
|
||||
const setupResult = setup();
|
||||
const { channelId, expected } = setupResult;
|
||||
expectCatalogEntryMatch({
|
||||
channelId,
|
||||
expected,
|
||||
...("catalogPaths" in setupResult ? { catalogPaths: setupResult.catalogPaths } : {}),
|
||||
...("env" in setupResult ? { env: setupResult.env } : {}),
|
||||
});
|
||||
});
|
||||
|
||||
it.each([
|
||||
{
|
||||
name: "uses the provided env for external catalog path resolution",
|
||||
setup: () => {
|
||||
const home = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-catalog-home-"));
|
||||
const catalogPath = path.join(home, "catalog.json");
|
||||
writeCatalogFile(
|
||||
catalogPath,
|
||||
createCatalogEntry({
|
||||
packageName: "@openclaw/env-demo-channel",
|
||||
channelId: "env-demo-channel",
|
||||
label: "Env Demo Channel",
|
||||
blurb: "Env demo entry",
|
||||
order: 1000,
|
||||
}),
|
||||
);
|
||||
return {
|
||||
env: {
|
||||
...process.env,
|
||||
OPENCLAW_PLUGIN_CATALOG_PATHS: "~/catalog.json",
|
||||
OPENCLAW_HOME: home,
|
||||
HOME: home,
|
||||
},
|
||||
expectedId: "env-demo-channel",
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "uses the provided env for default catalog paths",
|
||||
setup: () => {
|
||||
const stateDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-catalog-state-"));
|
||||
const catalogPath = path.join(stateDir, "plugins", "catalog.json");
|
||||
fs.mkdirSync(path.dirname(catalogPath), { recursive: true });
|
||||
writeCatalogFile(
|
||||
catalogPath,
|
||||
createCatalogEntry({
|
||||
packageName: "@openclaw/default-env-demo",
|
||||
channelId: "default-env-demo",
|
||||
label: "Default Env Demo",
|
||||
blurb: "Default env demo entry",
|
||||
}),
|
||||
);
|
||||
return {
|
||||
env: {
|
||||
...process.env,
|
||||
OPENCLAW_STATE_DIR: stateDir,
|
||||
},
|
||||
expectedId: "default-env-demo",
|
||||
};
|
||||
},
|
||||
},
|
||||
] as const)("$name", ({ setup }) => {
|
||||
const { env, expectedId } = setup();
|
||||
expectCatalogIdsContain({ env, expectedId });
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
import { describeChannelPluginCatalogEntriesContract } from "../../../../test/helpers/channels/channel-plugin-catalog-contract-suites.js";
|
||||
|
||||
describeChannelPluginCatalogEntriesContract();
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
import { describeChannelPluginCatalogPathResolutionContract } from "../../../../test/helpers/channels/channel-plugin-catalog-contract-suites.js";
|
||||
|
||||
describeChannelPluginCatalogPathResolutionContract();
|
||||
|
|
@ -0,0 +1,277 @@
|
|||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { listChannelPluginCatalogEntries } from "../../../src/channels/plugins/catalog.js";
|
||||
|
||||
function createCatalogEntry(params: {
|
||||
packageName: string;
|
||||
channelId: string;
|
||||
label: string;
|
||||
blurb: string;
|
||||
order?: number;
|
||||
}) {
|
||||
return {
|
||||
name: params.packageName,
|
||||
openclaw: {
|
||||
channel: {
|
||||
id: params.channelId,
|
||||
label: params.label,
|
||||
selectionLabel: params.label,
|
||||
docsPath: `/channels/${params.channelId}`,
|
||||
blurb: params.blurb,
|
||||
...(params.order === undefined ? {} : { order: params.order }),
|
||||
},
|
||||
install: {
|
||||
npmSpec: params.packageName,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function writeCatalogFile(catalogPath: string, entry: Record<string, unknown>) {
|
||||
fs.writeFileSync(
|
||||
catalogPath,
|
||||
JSON.stringify({
|
||||
entries: [entry],
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
function writeDiscoveredChannelPlugin(params: {
|
||||
stateDir: string;
|
||||
packageName: string;
|
||||
channelLabel: string;
|
||||
pluginId: string;
|
||||
blurb: string;
|
||||
}) {
|
||||
const pluginDir = path.join(params.stateDir, "extensions", "demo-channel-plugin");
|
||||
fs.mkdirSync(pluginDir, { recursive: true });
|
||||
fs.writeFileSync(
|
||||
path.join(pluginDir, "package.json"),
|
||||
JSON.stringify({
|
||||
name: params.packageName,
|
||||
openclaw: {
|
||||
extensions: ["./index.js"],
|
||||
channel: {
|
||||
id: "demo-channel",
|
||||
label: params.channelLabel,
|
||||
selectionLabel: params.channelLabel,
|
||||
docsPath: "/channels/demo-channel",
|
||||
blurb: params.blurb,
|
||||
},
|
||||
install: {
|
||||
npmSpec: params.packageName,
|
||||
},
|
||||
},
|
||||
}),
|
||||
"utf8",
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(pluginDir, "openclaw.plugin.json"),
|
||||
JSON.stringify({
|
||||
id: params.pluginId,
|
||||
configSchema: {},
|
||||
}),
|
||||
"utf8",
|
||||
);
|
||||
fs.writeFileSync(path.join(pluginDir, "index.js"), "module.exports = {}", "utf8");
|
||||
}
|
||||
|
||||
function expectCatalogIdsContain(params: {
|
||||
expectedId: string;
|
||||
catalogPaths?: string[];
|
||||
env?: NodeJS.ProcessEnv;
|
||||
}) {
|
||||
const ids = listChannelPluginCatalogEntries({
|
||||
...(params.catalogPaths ? { catalogPaths: params.catalogPaths } : {}),
|
||||
...(params.env ? { env: params.env } : {}),
|
||||
}).map((entry) => entry.id);
|
||||
expect(ids).toContain(params.expectedId);
|
||||
}
|
||||
|
||||
function findCatalogEntry(params: {
|
||||
channelId: string;
|
||||
catalogPaths?: string[];
|
||||
env?: NodeJS.ProcessEnv;
|
||||
}) {
|
||||
return listChannelPluginCatalogEntries({
|
||||
...(params.catalogPaths ? { catalogPaths: params.catalogPaths } : {}),
|
||||
...(params.env ? { env: params.env } : {}),
|
||||
}).find((entry) => entry.id === params.channelId);
|
||||
}
|
||||
|
||||
function expectCatalogEntryMatch(params: {
|
||||
channelId: string;
|
||||
expected: Record<string, unknown>;
|
||||
catalogPaths?: string[];
|
||||
env?: NodeJS.ProcessEnv;
|
||||
}) {
|
||||
expect(
|
||||
findCatalogEntry({
|
||||
channelId: params.channelId,
|
||||
...(params.catalogPaths ? { catalogPaths: params.catalogPaths } : {}),
|
||||
...(params.env ? { env: params.env } : {}),
|
||||
}),
|
||||
).toMatchObject(params.expected);
|
||||
}
|
||||
|
||||
export function describeChannelPluginCatalogEntriesContract() {
|
||||
describe("channel plugin catalog entries contract", () => {
|
||||
it.each([
|
||||
{
|
||||
name: "includes external catalog entries",
|
||||
setup: () => {
|
||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-catalog-"));
|
||||
const catalogPath = path.join(dir, "catalog.json");
|
||||
writeCatalogFile(
|
||||
catalogPath,
|
||||
createCatalogEntry({
|
||||
packageName: "@openclaw/demo-channel",
|
||||
channelId: "demo-channel",
|
||||
label: "Demo Channel",
|
||||
blurb: "Demo entry",
|
||||
order: 999,
|
||||
}),
|
||||
);
|
||||
return {
|
||||
channelId: "demo-channel",
|
||||
catalogPaths: [catalogPath],
|
||||
expected: { id: "demo-channel" },
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "preserves plugin ids when they differ from channel ids",
|
||||
setup: () => {
|
||||
const stateDir = fs.mkdtempSync(
|
||||
path.join(os.tmpdir(), "openclaw-channel-catalog-state-"),
|
||||
);
|
||||
writeDiscoveredChannelPlugin({
|
||||
stateDir,
|
||||
packageName: "@vendor/demo-channel-plugin",
|
||||
channelLabel: "Demo Channel",
|
||||
pluginId: "@vendor/demo-runtime",
|
||||
blurb: "Demo channel",
|
||||
});
|
||||
return {
|
||||
channelId: "demo-channel",
|
||||
env: {
|
||||
...process.env,
|
||||
OPENCLAW_STATE_DIR: stateDir,
|
||||
OPENCLAW_BUNDLED_PLUGINS_DIR: "/nonexistent/bundled/plugins",
|
||||
},
|
||||
expected: { pluginId: "@vendor/demo-runtime" },
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "keeps discovered plugins ahead of external catalog overrides",
|
||||
setup: () => {
|
||||
const stateDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-catalog-state-"));
|
||||
const catalogPath = path.join(stateDir, "catalog.json");
|
||||
writeDiscoveredChannelPlugin({
|
||||
stateDir,
|
||||
packageName: "@vendor/demo-channel-plugin",
|
||||
channelLabel: "Demo Channel Runtime",
|
||||
pluginId: "@vendor/demo-channel-runtime",
|
||||
blurb: "discovered plugin",
|
||||
});
|
||||
writeCatalogFile(
|
||||
catalogPath,
|
||||
createCatalogEntry({
|
||||
packageName: "@vendor/demo-channel-catalog",
|
||||
channelId: "demo-channel",
|
||||
label: "Demo Channel Catalog",
|
||||
blurb: "external catalog",
|
||||
}),
|
||||
);
|
||||
return {
|
||||
channelId: "demo-channel",
|
||||
catalogPaths: [catalogPath],
|
||||
env: {
|
||||
...process.env,
|
||||
OPENCLAW_STATE_DIR: stateDir,
|
||||
CLAWDBOT_STATE_DIR: undefined,
|
||||
OPENCLAW_BUNDLED_PLUGINS_DIR: "/nonexistent/bundled/plugins",
|
||||
},
|
||||
expected: {
|
||||
install: { npmSpec: "@vendor/demo-channel-plugin" },
|
||||
meta: { label: "Demo Channel Runtime" },
|
||||
pluginId: "@vendor/demo-channel-runtime",
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
] as const)("$name", ({ setup }) => {
|
||||
const setupResult = setup();
|
||||
const { channelId, expected } = setupResult;
|
||||
expectCatalogEntryMatch({
|
||||
channelId,
|
||||
expected,
|
||||
...("catalogPaths" in setupResult ? { catalogPaths: setupResult.catalogPaths } : {}),
|
||||
...("env" in setupResult ? { env: setupResult.env } : {}),
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function describeChannelPluginCatalogPathResolutionContract() {
|
||||
describe("channel plugin catalog path resolution contract", () => {
|
||||
it.each([
|
||||
{
|
||||
name: "uses the provided env for external catalog path resolution",
|
||||
setup: () => {
|
||||
const home = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-catalog-home-"));
|
||||
const catalogPath = path.join(home, "catalog.json");
|
||||
writeCatalogFile(
|
||||
catalogPath,
|
||||
createCatalogEntry({
|
||||
packageName: "@openclaw/env-demo-channel",
|
||||
channelId: "env-demo-channel",
|
||||
label: "Env Demo Channel",
|
||||
blurb: "Env demo entry",
|
||||
order: 1000,
|
||||
}),
|
||||
);
|
||||
return {
|
||||
env: {
|
||||
...process.env,
|
||||
OPENCLAW_PLUGIN_CATALOG_PATHS: "~/catalog.json",
|
||||
OPENCLAW_HOME: home,
|
||||
HOME: home,
|
||||
},
|
||||
expectedId: "env-demo-channel",
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "uses the provided env for default catalog paths",
|
||||
setup: () => {
|
||||
const stateDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-catalog-state-"));
|
||||
const catalogPath = path.join(stateDir, "plugins", "catalog.json");
|
||||
fs.mkdirSync(path.dirname(catalogPath), { recursive: true });
|
||||
writeCatalogFile(
|
||||
catalogPath,
|
||||
createCatalogEntry({
|
||||
packageName: "@openclaw/default-env-demo",
|
||||
channelId: "default-env-demo",
|
||||
label: "Default Env Demo",
|
||||
blurb: "Default env demo entry",
|
||||
}),
|
||||
);
|
||||
return {
|
||||
env: {
|
||||
...process.env,
|
||||
OPENCLAW_STATE_DIR: stateDir,
|
||||
},
|
||||
expectedId: "default-env-demo",
|
||||
};
|
||||
},
|
||||
},
|
||||
] as const)("$name", ({ setup }) => {
|
||||
const { env, expectedId } = setup();
|
||||
expectCatalogIdsContain({ env, expectedId });
|
||||
});
|
||||
});
|
||||
}
|
||||
Loading…
Reference in New Issue