mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-18 16:54:46 +00:00
test: clear channel setup plugin install broad matchers
This commit is contained in:
@@ -316,6 +316,56 @@ function expectPluginLoadedFromLocalPath(
|
||||
expect(result.cfg.plugins?.load?.paths).toContain(expectedPath);
|
||||
}
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
}
|
||||
|
||||
function requireRecord(value: unknown, label: string): Record<string, unknown> {
|
||||
if (!isRecord(value)) {
|
||||
throw new Error(`expected ${label} to be an object`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function requireArray(value: unknown, label: string): unknown[] {
|
||||
if (!Array.isArray(value)) {
|
||||
throw new Error(`expected ${label} to be an array`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function expectRecordFields(value: unknown, label: string, expected: Record<string, unknown>) {
|
||||
const record = requireRecord(value, label);
|
||||
for (const [key, expectedValue] of Object.entries(expected)) {
|
||||
expect(record[key]).toEqual(expectedValue);
|
||||
}
|
||||
}
|
||||
|
||||
type MockWithCalls = { mock: { calls: unknown[][] } };
|
||||
|
||||
function requireMockCallArg(mock: MockWithCalls, callIndex: number, argIndex = 0) {
|
||||
return requireRecord(mock.mock.calls[callIndex]?.[argIndex], "mock call argument");
|
||||
}
|
||||
|
||||
function requireSelectOptions(select: MockWithCalls) {
|
||||
return requireArray(requireMockCallArg(select, 0).options, "select options");
|
||||
}
|
||||
|
||||
function requireOptionByValue(options: unknown[], value: string) {
|
||||
const option = options.find(
|
||||
(candidate) => requireRecord(candidate, "select option").value === value,
|
||||
);
|
||||
return requireRecord(option, `select option ${value}`);
|
||||
}
|
||||
|
||||
function expectLoadOpenClawPluginFields(expected: Record<string, unknown>, callIndex = 0) {
|
||||
expectRecordFields(
|
||||
requireMockCallArg(vi.mocked(loadOpenClawPlugins), callIndex),
|
||||
"loadOpenClawPlugins args",
|
||||
expected,
|
||||
);
|
||||
}
|
||||
|
||||
describe("ensureChannelSetupPluginInstalled", () => {
|
||||
it("installs from npm and enables the plugin", async () => {
|
||||
const runtime = makeRuntime();
|
||||
@@ -341,19 +391,15 @@ describe("ensureChannelSetupPluginInstalled", () => {
|
||||
expect(result.installed).toBe(true);
|
||||
expect(result.cfg.plugins?.entries?.["bundled-chat"]?.enabled).toBe(true);
|
||||
expect(result.cfg.plugins?.allow).toContain("bundled-chat");
|
||||
expect(result.cfg.plugins?.installs).toEqual({
|
||||
"bundled-chat": expect.objectContaining({
|
||||
source: "npm",
|
||||
spec: bundledChatNpmSpec,
|
||||
installPath: "/tmp/bundled-chat",
|
||||
}),
|
||||
expectRecordFields(result.cfg.plugins?.installs?.["bundled-chat"], "plugin install record", {
|
||||
source: "npm",
|
||||
spec: bundledChatNpmSpec,
|
||||
installPath: "/tmp/bundled-chat",
|
||||
});
|
||||
expectRecordFields(requireMockCallArg(installPluginFromNpmSpec, 0), "npm install args", {
|
||||
expectedIntegrity: bundledChatIntegrity,
|
||||
spec: bundledChatNpmSpec,
|
||||
});
|
||||
expect(installPluginFromNpmSpec).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
expectedIntegrity: bundledChatIntegrity,
|
||||
spec: bundledChatNpmSpec,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("installs npm channel plugins into the active profile extensions dir", async () => {
|
||||
@@ -378,12 +424,10 @@ describe("ensureChannelSetupPluginInstalled", () => {
|
||||
runtime,
|
||||
});
|
||||
|
||||
expect(installPluginFromNpmSpec).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
extensionsDir: path.join(profileStateDir, "extensions"),
|
||||
spec: bundledChatNpmSpec,
|
||||
}),
|
||||
);
|
||||
expectRecordFields(requireMockCallArg(installPluginFromNpmSpec, 0), "npm install args", {
|
||||
extensionsDir: path.join(profileStateDir, "extensions"),
|
||||
spec: bundledChatNpmSpec,
|
||||
});
|
||||
});
|
||||
|
||||
it("uses local path when selected", async () => {
|
||||
@@ -476,12 +520,10 @@ describe("ensureChannelSetupPluginInstalled", () => {
|
||||
});
|
||||
|
||||
expect(select).not.toHaveBeenCalled();
|
||||
expect(installPluginFromNpmSpec).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
spec: "@openclaw/wecom@beta",
|
||||
expectedPluginId: "wecom-openclaw-plugin",
|
||||
}),
|
||||
);
|
||||
expectRecordFields(requireMockCallArg(installPluginFromNpmSpec, 0), "npm install args", {
|
||||
spec: "@openclaw/wecom@beta",
|
||||
expectedPluginId: "wecom-openclaw-plugin",
|
||||
});
|
||||
expect(result.cfg.plugins?.installs?.["wecom-openclaw-plugin"]?.spec).toBe("@openclaw/wecom");
|
||||
});
|
||||
|
||||
@@ -499,16 +541,15 @@ describe("ensureChannelSetupPluginInstalled", () => {
|
||||
runtime,
|
||||
});
|
||||
|
||||
expect(select).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
initialValue: "local",
|
||||
options: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
value: "local",
|
||||
hint: bundledPluginRootAt("/opt/openclaw", "bundled-chat"),
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
const selectArgs = requireMockCallArg(select, 0);
|
||||
expect(selectArgs.initialValue).toBe("local");
|
||||
expectRecordFields(
|
||||
requireOptionByValue(requireSelectOptions(select), "local"),
|
||||
"local option",
|
||||
{
|
||||
value: "local",
|
||||
hint: bundledPluginRootAt("/opt/openclaw", "bundled-chat"),
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
@@ -560,20 +601,17 @@ describe("ensureChannelSetupPluginInstalled", () => {
|
||||
runtime,
|
||||
});
|
||||
|
||||
expect(select).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
initialValue: "npm",
|
||||
options: [
|
||||
expect.objectContaining({
|
||||
value: "npm",
|
||||
label: `Download from npm (${bundledChatForkNpmSpec})`,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
value: "skip",
|
||||
}),
|
||||
],
|
||||
}),
|
||||
);
|
||||
const selectArgs = requireMockCallArg(select, 0);
|
||||
expect(selectArgs.initialValue).toBe("npm");
|
||||
const options = requireSelectOptions(select);
|
||||
expect(options).toHaveLength(2);
|
||||
expectRecordFields(options[0], "npm option", {
|
||||
value: "npm",
|
||||
label: `Download from npm (${bundledChatForkNpmSpec})`,
|
||||
});
|
||||
expectRecordFields(options[1], "skip option", {
|
||||
value: "skip",
|
||||
});
|
||||
});
|
||||
|
||||
it("offers ClawHub as the first-class install source for channel catalog entries", async () => {
|
||||
@@ -604,20 +642,17 @@ describe("ensureChannelSetupPluginInstalled", () => {
|
||||
runtime,
|
||||
});
|
||||
|
||||
expect(select).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
initialValue: "clawhub",
|
||||
options: [
|
||||
expect.objectContaining({
|
||||
value: "clawhub",
|
||||
label: "Download from ClawHub (clawhub:openclaw/clawhub-chat@2026.5.2)",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
value: "skip",
|
||||
}),
|
||||
],
|
||||
}),
|
||||
);
|
||||
const selectArgs = requireMockCallArg(select, 0);
|
||||
expect(selectArgs.initialValue).toBe("clawhub");
|
||||
const options = requireSelectOptions(select);
|
||||
expect(options).toHaveLength(2);
|
||||
expectRecordFields(options[0], "clawhub option", {
|
||||
value: "clawhub",
|
||||
label: "Download from ClawHub (clawhub:openclaw/clawhub-chat@2026.5.2)",
|
||||
});
|
||||
expectRecordFields(options[1], "skip option", {
|
||||
value: "skip",
|
||||
});
|
||||
});
|
||||
|
||||
it("falls back to local path after npm install failure", async () => {
|
||||
@@ -698,16 +733,14 @@ describe("ensureChannelSetupPluginInstalled", () => {
|
||||
workspaceDir: "/tmp/openclaw-workspace",
|
||||
});
|
||||
|
||||
expect(loadOpenClawPlugins).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
config: cfg,
|
||||
activationSourceConfig: cfg,
|
||||
autoEnabledReasons: {},
|
||||
workspaceDir: "/tmp/openclaw-workspace",
|
||||
cache: false,
|
||||
includeSetupOnlyChannelPlugins: true,
|
||||
}),
|
||||
);
|
||||
expectLoadOpenClawPluginFields({
|
||||
config: cfg,
|
||||
activationSourceConfig: cfg,
|
||||
autoEnabledReasons: {},
|
||||
workspaceDir: "/tmp/openclaw-workspace",
|
||||
cache: false,
|
||||
includeSetupOnlyChannelPlugins: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("loads the setup plugin registry from the auto-enabled config snapshot", () => {
|
||||
@@ -740,13 +773,11 @@ describe("ensureChannelSetupPluginInstalled", () => {
|
||||
config: cfg,
|
||||
env: process.env,
|
||||
});
|
||||
expect(loadOpenClawPlugins).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
config: autoEnabledConfig,
|
||||
activationSourceConfig: cfg,
|
||||
autoEnabledReasons: {},
|
||||
}),
|
||||
);
|
||||
expectLoadOpenClawPluginFields({
|
||||
config: autoEnabledConfig,
|
||||
activationSourceConfig: cfg,
|
||||
autoEnabledReasons: {},
|
||||
});
|
||||
});
|
||||
|
||||
it("scopes channel reloads when setup starts from an empty registry", () => {
|
||||
@@ -761,17 +792,15 @@ describe("ensureChannelSetupPluginInstalled", () => {
|
||||
workspaceDir: "/tmp/openclaw-workspace",
|
||||
});
|
||||
|
||||
expect(loadOpenClawPlugins).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
config: cfg,
|
||||
activationSourceConfig: cfg,
|
||||
autoEnabledReasons: {},
|
||||
workspaceDir: "/tmp/openclaw-workspace",
|
||||
cache: false,
|
||||
onlyPluginIds: ["@vendor/external-chat-plugin"],
|
||||
includeSetupOnlyChannelPlugins: true,
|
||||
}),
|
||||
);
|
||||
expectLoadOpenClawPluginFields({
|
||||
config: cfg,
|
||||
activationSourceConfig: cfg,
|
||||
autoEnabledReasons: {},
|
||||
workspaceDir: "/tmp/openclaw-workspace",
|
||||
cache: false,
|
||||
onlyPluginIds: ["@vendor/external-chat-plugin"],
|
||||
includeSetupOnlyChannelPlugins: true,
|
||||
});
|
||||
expect(getChannelPluginCatalogEntry).toHaveBeenCalledWith("external-chat", {
|
||||
workspaceDir: "/tmp/openclaw-workspace",
|
||||
});
|
||||
@@ -799,11 +828,9 @@ describe("ensureChannelSetupPluginInstalled", () => {
|
||||
workspaceDir: "/tmp/openclaw-workspace",
|
||||
});
|
||||
|
||||
expect(loadOpenClawPlugins).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
onlyPluginIds: [],
|
||||
}),
|
||||
);
|
||||
expectLoadOpenClawPluginFields({
|
||||
onlyPluginIds: [],
|
||||
});
|
||||
});
|
||||
|
||||
it("scopes channel reloads when the global registry is populated but the pinned channel registry is empty", () => {
|
||||
@@ -834,13 +861,11 @@ describe("ensureChannelSetupPluginInstalled", () => {
|
||||
releasePinnedPluginChannelRegistry(pinnedChannelRegistry);
|
||||
}
|
||||
|
||||
expect(loadOpenClawPlugins).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
activationSourceConfig: cfg,
|
||||
autoEnabledReasons: {},
|
||||
onlyPluginIds: ["@vendor/external-chat-plugin"],
|
||||
}),
|
||||
);
|
||||
expectLoadOpenClawPluginFields({
|
||||
activationSourceConfig: cfg,
|
||||
autoEnabledReasons: {},
|
||||
onlyPluginIds: ["@vendor/external-chat-plugin"],
|
||||
});
|
||||
});
|
||||
|
||||
it("can load a channel-scoped snapshot without activating the global registry", () => {
|
||||
@@ -855,18 +880,16 @@ describe("ensureChannelSetupPluginInstalled", () => {
|
||||
workspaceDir: "/tmp/openclaw-workspace",
|
||||
});
|
||||
|
||||
expect(loadOpenClawPlugins).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
config: cfg,
|
||||
activationSourceConfig: cfg,
|
||||
autoEnabledReasons: {},
|
||||
workspaceDir: "/tmp/openclaw-workspace",
|
||||
cache: false,
|
||||
onlyPluginIds: ["@vendor/external-chat-plugin"],
|
||||
includeSetupOnlyChannelPlugins: true,
|
||||
activate: false,
|
||||
}),
|
||||
);
|
||||
expectLoadOpenClawPluginFields({
|
||||
config: cfg,
|
||||
activationSourceConfig: cfg,
|
||||
autoEnabledReasons: {},
|
||||
workspaceDir: "/tmp/openclaw-workspace",
|
||||
cache: false,
|
||||
onlyPluginIds: ["@vendor/external-chat-plugin"],
|
||||
includeSetupOnlyChannelPlugins: true,
|
||||
activate: false,
|
||||
});
|
||||
expect(getChannelPluginCatalogEntry).toHaveBeenCalledWith("external-chat", {
|
||||
workspaceDir: "/tmp/openclaw-workspace",
|
||||
});
|
||||
@@ -886,11 +909,9 @@ describe("ensureChannelSetupPluginInstalled", () => {
|
||||
workspaceDir: "/tmp/openclaw-workspace",
|
||||
});
|
||||
|
||||
expect(loadOpenClawPlugins).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
onlyPluginIds: ["@vendor/external-chat-plugin"],
|
||||
}),
|
||||
);
|
||||
expectLoadOpenClawPluginFields({
|
||||
onlyPluginIds: ["@vendor/external-chat-plugin"],
|
||||
});
|
||||
expect(getChannelPluginCatalogEntry).toHaveBeenNthCalledWith(1, "external-chat", {
|
||||
workspaceDir: "/tmp/openclaw-workspace",
|
||||
});
|
||||
@@ -920,11 +941,9 @@ describe("ensureChannelSetupPluginInstalled", () => {
|
||||
workspaceDir: "/tmp/openclaw-workspace",
|
||||
});
|
||||
|
||||
expect(loadOpenClawPlugins).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
onlyPluginIds: ["trusted-external-chat-shadow"],
|
||||
}),
|
||||
);
|
||||
expectLoadOpenClawPluginFields({
|
||||
onlyPluginIds: ["trusted-external-chat-shadow"],
|
||||
});
|
||||
expect(getChannelPluginCatalogEntry).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
@@ -939,11 +958,9 @@ describe("ensureChannelSetupPluginInstalled", () => {
|
||||
workspaceDir: "/tmp/openclaw-workspace",
|
||||
});
|
||||
|
||||
expect(loadOpenClawPlugins).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
onlyPluginIds: [],
|
||||
}),
|
||||
);
|
||||
expectLoadOpenClawPluginFields({
|
||||
onlyPluginIds: [],
|
||||
});
|
||||
});
|
||||
|
||||
it("scopes snapshots by a unique discovered manifest match when catalog mapping is missing", () => {
|
||||
@@ -966,18 +983,16 @@ describe("ensureChannelSetupPluginInstalled", () => {
|
||||
workspaceDir: "/tmp/openclaw-workspace",
|
||||
});
|
||||
|
||||
expect(loadOpenClawPlugins).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
config: cfg,
|
||||
activationSourceConfig: cfg,
|
||||
autoEnabledReasons: {},
|
||||
workspaceDir: "/tmp/openclaw-workspace",
|
||||
cache: false,
|
||||
onlyPluginIds: ["custom-external-chat-plugin"],
|
||||
includeSetupOnlyChannelPlugins: true,
|
||||
activate: false,
|
||||
}),
|
||||
);
|
||||
expectLoadOpenClawPluginFields({
|
||||
config: cfg,
|
||||
activationSourceConfig: cfg,
|
||||
autoEnabledReasons: {},
|
||||
workspaceDir: "/tmp/openclaw-workspace",
|
||||
cache: false,
|
||||
onlyPluginIds: ["custom-external-chat-plugin"],
|
||||
includeSetupOnlyChannelPlugins: true,
|
||||
activate: false,
|
||||
});
|
||||
});
|
||||
|
||||
it("scopes snapshots by activation-declared channel ownership when direct channel lists are empty", () => {
|
||||
@@ -992,23 +1007,21 @@ describe("ensureChannelSetupPluginInstalled", () => {
|
||||
workspaceDir: "/tmp/openclaw-workspace",
|
||||
});
|
||||
|
||||
expect(loadOpenClawPlugins).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
onlyPluginIds: ["custom-external-chat-plugin"],
|
||||
}),
|
||||
);
|
||||
expect(loadPluginManifestRegistry).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
config: cfg,
|
||||
workspaceDir: "/tmp/openclaw-workspace",
|
||||
candidates: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
idHint: "custom-external-chat-plugin",
|
||||
origin: "bundled",
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
);
|
||||
expectLoadOpenClawPluginFields({
|
||||
onlyPluginIds: ["custom-external-chat-plugin"],
|
||||
});
|
||||
const manifestCall = loadPluginManifestRegistry.mock.calls
|
||||
.map((call) => requireRecord(call[0], "manifest registry args"))
|
||||
.find((args) =>
|
||||
requireArray(args.candidates, "manifest candidates").some((candidate) => {
|
||||
const record = requireRecord(candidate, "manifest candidate");
|
||||
return record.idHint === "custom-external-chat-plugin" && record.origin === "bundled";
|
||||
}),
|
||||
);
|
||||
expectRecordFields(manifestCall, "manifest registry args", {
|
||||
config: cfg,
|
||||
workspaceDir: "/tmp/openclaw-workspace",
|
||||
});
|
||||
});
|
||||
|
||||
it("uses live manifest discovery for activation-declared setup scoping", () => {
|
||||
@@ -1155,17 +1168,15 @@ describe("ensureChannelSetupPluginInstalled", () => {
|
||||
workspaceDir: "/tmp/openclaw-workspace",
|
||||
});
|
||||
|
||||
expect(loadOpenClawPlugins).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
config: cfg,
|
||||
activationSourceConfig: cfg,
|
||||
autoEnabledReasons: {},
|
||||
workspaceDir: "/tmp/openclaw-workspace",
|
||||
cache: false,
|
||||
onlyPluginIds: ["@vendor/external-chat-plugin"],
|
||||
includeSetupOnlyChannelPlugins: true,
|
||||
activate: false,
|
||||
}),
|
||||
);
|
||||
expectLoadOpenClawPluginFields({
|
||||
config: cfg,
|
||||
activationSourceConfig: cfg,
|
||||
autoEnabledReasons: {},
|
||||
workspaceDir: "/tmp/openclaw-workspace",
|
||||
cache: false,
|
||||
onlyPluginIds: ["@vendor/external-chat-plugin"],
|
||||
includeSetupOnlyChannelPlugins: true,
|
||||
activate: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user