test: clear channels add broad matchers

This commit is contained in:
Peter Steinberger
2026-05-10 10:25:44 +01:00
parent 159dae902a
commit 8f762b6044

View File

@@ -103,6 +103,12 @@ vi.mock("./onboard-channels.js", async () => {
const runtime = createTestRuntime();
type MockCallSource = {
mock: {
calls: ArrayLike<ReadonlyArray<unknown>>;
};
};
function listConfiguredAccountIds(
channelConfig: { accounts?: Record<string, unknown>; token?: string } | undefined,
): string[] {
@@ -116,18 +122,98 @@ function listConfiguredAccountIds(
return [];
}
function expectExternalChatEnabledConfigWrite() {
expect(configMocks.writeConfigFile).toHaveBeenCalledWith(
expect.objectContaining({
channels: {
"external-chat": expect.objectContaining({
enabled: true,
}),
},
}),
function requireRecord(value: unknown, label: string): Record<string, unknown> {
expect(value, label).toBeTypeOf("object");
expect(value, label).not.toBeNull();
return value as Record<string, unknown>;
}
function mockArg(source: MockCallSource, callIndex: number, argIndex: number, label: string) {
const call = source.mock.calls[callIndex];
expect(call, label).toBeDefined();
return call?.[argIndex];
}
function writtenConfig(index = 0) {
return requireRecord(
mockArg(configMocks.writeConfigFile, index, 0, `written config ${index}`),
`written config ${index}`,
);
}
function writtenChannel(channel: string, index = 0) {
return requireRecord(
requireRecord(writtenConfig(index).channels, `written channels ${index}`)[channel],
`written channel ${channel}`,
);
}
function setupOptions() {
return requireRecord(
mockArg(channelWizardMocks.setupChannels, 0, 3, "setup options"),
"setup options",
);
}
function applyAccountConfigCall(fn: MockCallSource, index = 0) {
return requireRecord(
mockArg(fn, index, 0, `apply account config ${index}`),
"apply account config",
);
}
function installCall(index = 0) {
return requireRecord(
mockArg(
ensureChannelSetupPluginInstalled as unknown as MockCallSource,
index,
0,
`install call ${index}`,
),
`install call ${index}`,
);
}
function snapshotCall(index = 0) {
return requireRecord(
mockArg(
loadChannelSetupPluginRegistrySnapshotForChannel as unknown as MockCallSource,
index,
0,
`snapshot call ${index}`,
),
`snapshot call ${index}`,
);
}
function refreshCall(index = 0) {
return requireRecord(
mockArg(
registryRefreshMocks.refreshPluginRegistryAfterConfigMutation,
index,
0,
`refresh call ${index}`,
),
`refresh call ${index}`,
);
}
function commitInstallCall(index = 0) {
return requireRecord(
mockArg(
pluginInstallRecordCommitMocks.commitConfigWithPendingPluginInstalls,
index,
0,
`commit install call ${index}`,
),
`commit install call ${index}`,
);
}
function expectExternalChatEnabledConfigWrite() {
expect(writtenChannel("external-chat").enabled).toBe(true);
}
function createLifecycleChatAddTestPlugin(): ChannelPlugin {
const resolveLifecycleChatAccount = (
cfg: Parameters<NonNullable<ChannelPlugin["config"]["resolveAccount"]>>[0],
@@ -361,16 +447,12 @@ describe("channelsAddCommand", () => {
await channelsAddCommand({}, runtime, { hasFlags: false });
expect(channelWizardMocks.prompter.intro).toHaveBeenCalledWith("Channel setup");
expect(channelWizardMocks.setupChannels).toHaveBeenCalledWith(
config,
runtime,
channelWizardMocks.prompter,
expect.objectContaining({
deferStatusUntilSelection: true,
skipStatusNote: true,
promptAccountIds: true,
}),
);
expect(channelWizardMocks.setupChannels.mock.calls[0]?.[0]).toBe(config);
expect(channelWizardMocks.setupChannels.mock.calls[0]?.[1]).toBe(runtime);
expect(channelWizardMocks.setupChannels.mock.calls[0]?.[2]).toBe(channelWizardMocks.prompter);
expect(setupOptions().deferStatusUntilSelection).toBe(true);
expect(setupOptions().skipStatusNote).toBe(true);
expect(setupOptions().promptAccountIds).toBe(true);
expect(configMocks.writeConfigFile).not.toHaveBeenCalled();
expect(channelWizardMocks.prompter.outro).toHaveBeenCalledWith("No channel changes made.");
});
@@ -454,28 +536,20 @@ describe("channelsAddCommand", () => {
{ hasFlags: true },
);
expect(applyAccountConfig).toHaveBeenCalledWith(
expect.objectContaining({
input: expect.objectContaining({
url: "https://cloud.example.com/",
token: "shared-secret",
baseUrl: "https://cloud.example.com/",
secret: "shared-secret",
}),
}),
);
expect(configMocks.writeConfigFile).toHaveBeenCalledWith(
expect.objectContaining({
channels: {
"nextcloud-talk": {
enabled: true,
baseUrl: "https://cloud.example.com/",
botSecret: "shared-secret",
botSecretFile: undefined,
},
},
}),
const applyInput = requireRecord(
applyAccountConfigCall(applyAccountConfig as unknown as MockCallSource).input,
"apply input",
);
expect(applyInput.url).toBe("https://cloud.example.com/");
expect(applyInput.token).toBe("shared-secret");
expect(applyInput.baseUrl).toBe("https://cloud.example.com/");
expect(applyInput.secret).toBe("shared-secret");
expect(writtenChannel("nextcloud-talk")).toEqual({
enabled: true,
baseUrl: "https://cloud.example.com/",
botSecret: "shared-secret",
botSecretFile: undefined,
});
configMocks.writeConfigFile.mockClear();
applyAccountConfig.mockClear();
@@ -490,14 +564,12 @@ describe("channelsAddCommand", () => {
{ hasFlags: true },
);
expect(applyAccountConfig).toHaveBeenCalledWith(
expect.objectContaining({
input: expect.objectContaining({
baseUrl: "https://cloud.example.com",
secretFile: "/tmp/nextcloud-secret",
}),
}),
const secondApplyInput = requireRecord(
applyAccountConfigCall(applyAccountConfig as unknown as MockCallSource).input,
"second apply input",
);
expect(secondApplyInput.baseUrl).toBe("https://cloud.example.com");
expect(secondApplyInput.secretFile).toBe("/tmp/nextcloud-secret");
});
it("passes channel auth directory overrides through add setup input", async () => {
@@ -544,21 +616,15 @@ describe("channelsAddCommand", () => {
{ hasFlags: true },
);
expect(configMocks.writeConfigFile).toHaveBeenCalledWith(
expect.objectContaining({
channels: {
whatsapp: {
enabled: true,
accounts: {
work: {
enabled: true,
authDir: "/tmp/openclaw-wa-auth",
},
},
},
expect(writtenChannel("whatsapp")).toEqual({
enabled: true,
accounts: {
work: {
enabled: true,
authDir: "/tmp/openclaw-wa-auth",
},
}),
);
},
});
});
it("loads external channel setup snapshots for newly installed and existing plugins", async () => {
@@ -578,23 +644,18 @@ describe("channelsAddCommand", () => {
{ hasFlags: true },
);
expect(ensureChannelSetupPluginInstalled).toHaveBeenCalledWith(
expect.objectContaining({ entry: catalogEntry, promptInstall: false }),
);
expect(installCall().entry).toBe(catalogEntry);
expect(installCall().promptInstall).toBe(false);
expect(loadChannelSetupPluginRegistrySnapshotForChannel).toHaveBeenCalledTimes(1);
expect(loadChannelSetupPluginRegistrySnapshotForChannel).toHaveBeenCalledWith(
expect.objectContaining({ forceSetupOnlyChannelPlugins: true }),
);
expect(registryRefreshMocks.refreshPluginRegistryAfterConfigMutation).toHaveBeenCalledWith(
expect.objectContaining({
config: expect.objectContaining({
channels: expect.objectContaining({
"external-chat": expect.objectContaining({ enabled: true }),
}),
}),
reason: "source-changed",
}),
expect(snapshotCall().forceSetupOnlyChannelPlugins).toBe(true);
const refreshedChannels = requireRecord(
requireRecord(refreshCall().config, "refresh config").channels,
"refresh channels",
);
expect(
requireRecord(refreshedChannels["external-chat"], "refreshed external chat").enabled,
).toBe(true);
expect(refreshCall().reason).toBe("source-changed");
expectExternalChatEnabledConfigWrite();
expect(runtime.error).not.toHaveBeenCalled();
expect(runtime.exit).not.toHaveBeenCalled();
@@ -616,9 +677,7 @@ describe("channelsAddCommand", () => {
expect(ensureChannelSetupPluginInstalled).not.toHaveBeenCalled();
expect(loadChannelSetupPluginRegistrySnapshotForChannel).toHaveBeenCalledTimes(1);
expect(loadChannelSetupPluginRegistrySnapshotForChannel).toHaveBeenCalledWith(
expect.objectContaining({ forceSetupOnlyChannelPlugins: true }),
);
expect(snapshotCall().forceSetupOnlyChannelPlugins).toBe(true);
expectExternalChatEnabledConfigWrite();
});
@@ -667,16 +726,8 @@ describe("channelsAddCommand", () => {
);
expect(loadChannelSetupPluginRegistrySnapshotForChannel).toHaveBeenCalledTimes(1);
expect(configMocks.writeConfigFile).toHaveBeenCalledWith(
expect.objectContaining({
channels: expect.objectContaining({
telegram: expect.objectContaining({
enabled: true,
botToken: "123456:token",
}),
}),
}),
);
expect(writtenChannel("telegram").enabled).toBe(true);
expect(writtenChannel("telegram").botToken).toBe("123456:token");
expect(runtime.error).not.toHaveBeenCalledWith(
expect.stringContaining("Channel telegram does not support non-interactive add"),
);
@@ -720,16 +771,8 @@ describe("channelsAddCommand", () => {
);
expect(getBundledChannelSetupPlugin).toHaveBeenCalledWith("telegram");
expect(configMocks.writeConfigFile).toHaveBeenCalledWith(
expect.objectContaining({
channels: expect.objectContaining({
telegram: expect.objectContaining({
enabled: true,
botToken: "123456:token",
}),
}),
}),
);
expect(writtenChannel("telegram").enabled).toBe(true);
expect(writtenChannel("telegram").botToken).toBe("123456:token");
expect(runtime.error).not.toHaveBeenCalledWith(
expect.stringContaining("Channel telegram does not support non-interactive add"),
);
@@ -775,12 +818,9 @@ describe("channelsAddCommand", () => {
{ hasFlags: true },
);
expect(ensureChannelSetupPluginInstalled).toHaveBeenCalledWith(
expect.objectContaining({ entry: trustedEntry, promptInstall: false }),
);
expect(loadChannelSetupPluginRegistrySnapshotForChannel).toHaveBeenCalledWith(
expect.objectContaining({ pluginId: "@vendor/external-chat-plugin" }),
);
expect(installCall().entry).toBe(trustedEntry);
expect(installCall().promptInstall).toBe(false);
expect(snapshotCall().pluginId).toBe("@vendor/external-chat-plugin");
expectExternalChatEnabledConfigWrite();
expect(runtime.error).not.toHaveBeenCalled();
expect(runtime.exit).not.toHaveBeenCalled();
@@ -822,12 +862,9 @@ describe("channelsAddCommand", () => {
{ hasFlags: true },
);
expect(ensureChannelSetupPluginInstalled).toHaveBeenCalledWith(
expect.objectContaining({ entry: workspaceEntry, promptInstall: false }),
);
expect(loadChannelSetupPluginRegistrySnapshotForChannel).toHaveBeenCalledWith(
expect.objectContaining({ pluginId: "trusted-external-chat-shadow" }),
);
expect(installCall().entry).toBe(workspaceEntry);
expect(installCall().promptInstall).toBe(false);
expect(snapshotCall().pluginId).toBe("trusted-external-chat-shadow");
expectExternalChatEnabledConfigWrite();
expect(runtime.error).not.toHaveBeenCalled();
expect(runtime.exit).not.toHaveBeenCalled();
@@ -883,19 +920,13 @@ describe("channelsAddCommand", () => {
{ hasFlags: true },
);
expect(
pluginInstallRecordCommitMocks.commitConfigWithPendingPluginInstalls,
).toHaveBeenCalledWith({
nextConfig: expect.objectContaining({
plugins: expect.objectContaining({ installs: installRecords }),
}),
baseHash: "config-1",
});
expect(registryRefreshMocks.refreshPluginRegistryAfterConfigMutation).toHaveBeenCalledWith(
expect.objectContaining({
installRecords,
}),
const commitCall = commitInstallCall();
const commitNextConfig = requireRecord(commitCall.nextConfig, "commit next config");
expect(requireRecord(commitNextConfig.plugins, "commit plugins").installs).toEqual(
installRecords,
);
expect(commitCall.baseHash).toBe("config-1");
expect(refreshCall().installRecords).toEqual(installRecords);
});
it("uses the installed plugin id when channel and plugin ids differ", async () => {
@@ -975,26 +1006,21 @@ describe("channelsAddCommand", () => {
expect(configMocks.writeConfigFile.mock.invocationCallOrder[0]).toBeLessThan(
afterAccountConfigWritten.mock.invocationCallOrder[0] ?? Number.POSITIVE_INFINITY,
);
expect(afterAccountConfigWritten).toHaveBeenCalledWith({
previousCfg: baseConfigSnapshot.config,
cfg: expect.objectContaining({
channels: {
signal: {
enabled: true,
accounts: {
ops: {
account: "+15550001",
},
},
const hookCall = requireRecord(afterAccountConfigWritten.mock.calls[0]?.[0], "hook call");
expect(hookCall.previousCfg).toBe(baseConfigSnapshot.config);
expect(requireRecord(hookCall.cfg, "hook config").channels).toEqual({
signal: {
enabled: true,
accounts: {
ops: {
account: "+15550001",
},
},
}),
accountId: "ops",
input: expect.objectContaining({
signalNumber: "+15550001",
}),
runtime,
},
});
expect(hookCall.accountId).toBe("ops");
expect(requireRecord(hookCall.input, "hook input").signalNumber).toBe("+15550001");
expect(hookCall.runtime).toBe(runtime);
configMocks.writeConfigFile.mockClear();
runtime.error.mockClear();