test(commands): dedupe command and onboarding test cases

This commit is contained in:
Peter Steinberger
2026-03-02 06:40:52 +00:00
parent 7e29d604ba
commit cded1b960a
16 changed files with 1262 additions and 1591 deletions

View File

@@ -25,6 +25,10 @@ import {
const runtime = createTestRuntime();
let clackPrompterModule: typeof import("../wizard/clack-prompter.js");
function formatChannelStatusJoined(channelAccounts: Record<string, unknown>) {
return formatGatewayChannelsStatusLines({ channelAccounts }).join("\n");
}
describe("channels command", () => {
beforeAll(async () => {
clackPrompterModule = await import("../wizard/clack-prompter.js");
@@ -45,23 +49,53 @@ describe("channels command", () => {
setDefaultChannelPluginRegistryForTests();
});
it("adds a non-default telegram account", async () => {
configMocks.readConfigFileSnapshot.mockResolvedValue({ ...baseConfigSnapshot });
await channelsAddCommand(
{ channel: "telegram", account: "alerts", token: "123:abc" },
runtime,
{ hasFlags: true },
);
function getWrittenConfig<T>(): T {
expect(configMocks.writeConfigFile).toHaveBeenCalledTimes(1);
const next = configMocks.writeConfigFile.mock.calls[0]?.[0] as {
return configMocks.writeConfigFile.mock.calls[0]?.[0] as T;
}
async function runRemoveWithConfirm(
args: Parameters<typeof channelsRemoveCommand>[0],
): Promise<void> {
const prompt = { confirm: vi.fn().mockResolvedValue(true) };
const promptSpy = vi
.spyOn(clackPrompterModule, "createClackPrompter")
.mockReturnValue(prompt as never);
try {
await channelsRemoveCommand(args, runtime, { hasFlags: true });
} finally {
promptSpy.mockRestore();
}
}
async function addTelegramAccount(account: string, token: string): Promise<void> {
await channelsAddCommand({ channel: "telegram", account, token }, runtime, {
hasFlags: true,
});
}
async function addAlertsTelegramAccount(token: string): Promise<{
channels?: {
telegram?: {
enabled?: boolean;
accounts?: Record<string, { botToken?: string }>;
};
};
}> {
await addTelegramAccount("alerts", token);
return getWrittenConfig<{
channels?: {
telegram?: {
enabled?: boolean;
accounts?: Record<string, { botToken?: string }>;
};
};
};
}>();
}
it("adds a non-default telegram account", async () => {
configMocks.readConfigFileSnapshot.mockResolvedValue({ ...baseConfigSnapshot });
const next = await addAlertsTelegramAccount("123:abc");
expect(next.channels?.telegram?.enabled).toBe(true);
expect(next.channels?.telegram?.accounts?.alerts?.botToken).toBe("123:abc");
});
@@ -83,13 +117,9 @@ describe("channels command", () => {
},
});
await channelsAddCommand(
{ channel: "telegram", account: "alerts", token: "alerts-token" },
runtime,
{ hasFlags: true },
);
await addTelegramAccount("alerts", "alerts-token");
const next = configMocks.writeConfigFile.mock.calls[0]?.[0] as {
const next = getWrittenConfig<{
channels?: {
telegram?: {
botToken?: string;
@@ -109,7 +139,7 @@ describe("channels command", () => {
>;
};
};
};
}>();
expect(next.channels?.telegram?.accounts?.default).toEqual({
botToken: "legacy-token",
dmPolicy: "allowlist",
@@ -137,20 +167,7 @@ describe("channels command", () => {
},
});
await channelsAddCommand(
{ channel: "telegram", account: "alerts", token: "alerts-token" },
runtime,
{ hasFlags: true },
);
const next = configMocks.writeConfigFile.mock.calls[0]?.[0] as {
channels?: {
telegram?: {
enabled?: boolean;
accounts?: Record<string, { botToken?: string }>;
};
};
};
const next = await addAlertsTelegramAccount("alerts-token");
expect(next.channels?.telegram?.enabled).toBe(true);
expect(next.channels?.telegram?.accounts?.default).toEqual({});
expect(next.channels?.telegram?.accounts?.alerts?.botToken).toBe("alerts-token");
@@ -169,12 +186,11 @@ describe("channels command", () => {
{ hasFlags: true },
);
expect(configMocks.writeConfigFile).toHaveBeenCalledTimes(1);
const next = configMocks.writeConfigFile.mock.calls[0]?.[0] as {
const next = getWrittenConfig<{
channels?: {
slack?: { enabled?: boolean; botToken?: string; appToken?: string };
};
};
}>();
expect(next.channels?.slack?.enabled).toBe(true);
expect(next.channels?.slack?.botToken).toBe("xoxb-1");
expect(next.channels?.slack?.appToken).toBe("xapp-1");
@@ -199,12 +215,11 @@ describe("channels command", () => {
hasFlags: true,
});
expect(configMocks.writeConfigFile).toHaveBeenCalledTimes(1);
const next = configMocks.writeConfigFile.mock.calls[0]?.[0] as {
const next = getWrittenConfig<{
channels?: {
discord?: { accounts?: Record<string, { token?: string }> };
};
};
}>();
expect(next.channels?.discord?.accounts?.work).toBeUndefined();
expect(next.channels?.discord?.accounts?.default?.token).toBe("d0");
});
@@ -217,11 +232,11 @@ describe("channels command", () => {
{ hasFlags: true },
);
const next = configMocks.writeConfigFile.mock.calls[0]?.[0] as {
const next = getWrittenConfig<{
channels?: {
whatsapp?: { accounts?: Record<string, { name?: string }> };
};
};
}>();
expect(next.channels?.whatsapp?.accounts?.family?.name).toBe("Family Phone");
});
@@ -250,13 +265,13 @@ describe("channels command", () => {
{ hasFlags: true },
);
const next = configMocks.writeConfigFile.mock.calls[0]?.[0] as {
const next = getWrittenConfig<{
channels?: {
signal?: {
accounts?: Record<string, { account?: string; name?: string }>;
};
};
};
}>();
expect(next.channels?.signal?.accounts?.lab?.account).toBe("+15555550123");
expect(next.channels?.signal?.accounts?.lab?.name).toBe("Lab");
expect(next.channels?.signal?.accounts?.default?.name).toBe("Primary");
@@ -270,20 +285,12 @@ describe("channels command", () => {
},
});
const prompt = { confirm: vi.fn().mockResolvedValue(true) };
const promptSpy = vi
.spyOn(clackPrompterModule, "createClackPrompter")
.mockReturnValue(prompt as never);
await runRemoveWithConfirm({ channel: "discord", account: "default" });
await channelsRemoveCommand({ channel: "discord", account: "default" }, runtime, {
hasFlags: true,
});
const next = configMocks.writeConfigFile.mock.calls[0]?.[0] as {
const next = getWrittenConfig<{
channels?: { discord?: { enabled?: boolean } };
};
}>();
expect(next.channels?.discord?.enabled).toBe(false);
promptSpy.mockRestore();
});
it("includes external auth profiles in JSON output", async () => {
@@ -348,14 +355,14 @@ describe("channels command", () => {
{ hasFlags: true },
);
const next = configMocks.writeConfigFile.mock.calls[0]?.[0] as {
const next = getWrittenConfig<{
channels?: {
telegram?: {
name?: string;
accounts?: Record<string, { botToken?: string; name?: string }>;
};
};
};
}>();
expect(next.channels?.telegram?.name).toBeUndefined();
expect(next.channels?.telegram?.accounts?.default?.name).toBe("Primary Bot");
});
@@ -377,14 +384,14 @@ describe("channels command", () => {
hasFlags: true,
});
const next = configMocks.writeConfigFile.mock.calls[0]?.[0] as {
const next = getWrittenConfig<{
channels?: {
discord?: {
name?: string;
accounts?: Record<string, { name?: string; token?: string }>;
};
};
};
}>();
expect(next.channels?.discord?.name).toBeUndefined();
expect(next.channels?.discord?.accounts?.default?.name).toBe("Primary Bot");
expect(next.channels?.discord?.accounts?.work?.token).toBe("d1");
@@ -405,8 +412,9 @@ describe("channels command", () => {
expect(telegramIndex).toBeLessThan(whatsappIndex);
});
it("surfaces Discord privileged intent issues in channels status output", () => {
const lines = formatGatewayChannelsStatusLines({
it.each([
{
name: "surfaces Discord privileged intent issues in channels status output",
channelAccounts: {
discord: [
{
@@ -417,14 +425,14 @@ describe("channels command", () => {
},
],
},
});
expect(lines.join("\n")).toMatch(/Warnings:/);
expect(lines.join("\n")).toMatch(/Message Content Intent is disabled/i);
expect(lines.join("\n")).toMatch(/Run: (?:openclaw|openclaw)( --profile isolated)? doctor/);
});
it("surfaces Discord permission audit issues in channels status output", () => {
const lines = formatGatewayChannelsStatusLines({
patterns: [
/Warnings:/,
/Message Content Intent is disabled/i,
/Run: (?:openclaw|openclaw)( --profile isolated)? doctor/,
],
},
{
name: "surfaces Discord permission audit issues in channels status output",
channelAccounts: {
discord: [
{
@@ -444,14 +452,10 @@ describe("channels command", () => {
},
],
},
});
expect(lines.join("\n")).toMatch(/Warnings:/);
expect(lines.join("\n")).toMatch(/permission audit/i);
expect(lines.join("\n")).toMatch(/Channel 111/i);
});
it("surfaces Telegram privacy-mode hints when allowUnmentionedGroups is enabled", () => {
const lines = formatGatewayChannelsStatusLines({
patterns: [/Warnings:/, /permission audit/i, /Channel 111/i],
},
{
name: "surfaces Telegram privacy-mode hints when allowUnmentionedGroups is enabled",
channelAccounts: {
telegram: [
{
@@ -462,54 +466,54 @@ describe("channels command", () => {
},
],
},
});
expect(lines.join("\n")).toMatch(/Warnings:/);
expect(lines.join("\n")).toMatch(/Telegram Bot API privacy mode/i);
patterns: [/Warnings:/, /Telegram Bot API privacy mode/i],
},
])("$name", ({ channelAccounts, patterns }) => {
const joined = formatChannelStatusJoined(channelAccounts);
for (const pattern of patterns) {
expect(joined).toMatch(pattern);
}
});
it("includes Telegram bot username from probe data", () => {
const lines = formatGatewayChannelsStatusLines({
channelAccounts: {
telegram: [
{
accountId: "default",
enabled: true,
configured: true,
probe: { ok: true, bot: { username: "openclaw_bot" } },
},
],
},
const joined = formatChannelStatusJoined({
telegram: [
{
accountId: "default",
enabled: true,
configured: true,
probe: { ok: true, bot: { username: "openclaw_bot" } },
},
],
});
expect(lines.join("\n")).toMatch(/bot:@openclaw_bot/);
expect(joined).toMatch(/bot:@openclaw_bot/);
});
it("surfaces Telegram group membership audit issues in channels status output", () => {
const lines = formatGatewayChannelsStatusLines({
channelAccounts: {
telegram: [
{
accountId: "default",
enabled: true,
configured: true,
audit: {
hasWildcardUnmentionedGroups: true,
unresolvedGroups: 1,
groups: [
{
chatId: "-1001",
ok: false,
status: "left",
error: "not in group",
},
],
},
const joined = formatChannelStatusJoined({
telegram: [
{
accountId: "default",
enabled: true,
configured: true,
audit: {
hasWildcardUnmentionedGroups: true,
unresolvedGroups: 1,
groups: [
{
chatId: "-1001",
ok: false,
status: "left",
error: "not in group",
},
],
},
],
},
},
],
});
expect(lines.join("\n")).toMatch(/Warnings:/);
expect(lines.join("\n")).toMatch(/membership probing is not possible/i);
expect(lines.join("\n")).toMatch(/Group -1001/i);
expect(joined).toMatch(/Warnings:/);
expect(joined).toMatch(/membership probing is not possible/i);
expect(joined).toMatch(/Group -1001/i);
});
it("surfaces WhatsApp auth/runtime hints when unlinked or disconnected", () => {
@@ -591,16 +595,8 @@ describe("channels command", () => {
},
});
const prompt = { confirm: vi.fn().mockResolvedValue(true) };
const promptSpy = vi
.spyOn(clackPrompterModule, "createClackPrompter")
.mockReturnValue(prompt as never);
await channelsRemoveCommand({ channel: "telegram", account: "default" }, runtime, {
hasFlags: true,
});
await runRemoveWithConfirm({ channel: "telegram", account: "default" });
expect(offsetMocks.deleteTelegramUpdateOffset).not.toHaveBeenCalled();
promptSpy.mockRestore();
});
});