fix(test): trim channel account registry imports

This commit is contained in:
Vincent Koc 2026-03-30 13:50:39 +09:00
parent 6a3c68d470
commit c842ca0166
1 changed files with 280 additions and 2 deletions

View File

@ -1,5 +1,10 @@
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { setDefaultChannelPluginRegistryForTests } from "./channel-test-helpers.js";
import { createPatchedAccountSetupAdapter } from "../channels/plugins/setup-helpers.js";
import type { ChannelPlugin } from "../channels/plugins/types.js";
import { createScopedChannelConfigAdapter } from "../plugin-sdk/channel-config-helpers.js";
import { setActivePluginRegistry } from "../plugins/runtime.js";
import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "../routing/session-key.js";
import { createChannelTestPluginBase, createTestRegistry } from "../test-utils/channel-plugins.js";
import { configMocks, offsetMocks } from "./channels.mock-harness.js";
import { baseConfigSnapshot, createTestRuntime } from "./test-runtime-config-helpers.js";
@ -25,10 +30,283 @@ import {
const runtime = createTestRuntime();
let clackPrompterModule: typeof import("../wizard/clack-prompter.js");
type ChannelSectionConfig = {
enabled?: boolean;
name?: string;
token?: string;
botToken?: string;
appToken?: string;
account?: string;
accounts?: Record<string, Record<string, unknown>>;
};
function formatChannelStatusJoined(channelAccounts: Record<string, unknown>) {
return formatGatewayChannelsStatusLines({ channelAccounts }).join("\n");
}
function listConfiguredAccountIds(channelConfig: ChannelSectionConfig | undefined): string[] {
const accountIds = Object.keys(channelConfig?.accounts ?? {});
if (accountIds.length > 0) {
return accountIds;
}
if (
channelConfig?.token ||
channelConfig?.botToken ||
channelConfig?.appToken ||
channelConfig?.account ||
channelConfig?.name
) {
return [DEFAULT_ACCOUNT_ID];
}
return [];
}
function resolveScopedAccount(
cfg: Parameters<NonNullable<ChannelPlugin["config"]["resolveAccount"]>>[0],
channelKey: string,
accountId?: string | null,
): Record<string, unknown> {
const resolvedAccountId = normalizeAccountId(accountId);
const channel = cfg.channels?.[channelKey] as ChannelSectionConfig | undefined;
const scoped = channel?.accounts?.[resolvedAccountId];
const base =
resolvedAccountId === DEFAULT_ACCOUNT_ID
? channel
: undefined;
return {
...(base ?? {}),
...(scoped ?? {}),
enabled:
typeof scoped?.enabled === "boolean"
? scoped.enabled
: typeof channel?.enabled === "boolean"
? channel.enabled
: true,
};
}
function createScopedCommandTestPlugin(params: {
id: "discord" | "signal" | "slack" | "telegram" | "whatsapp";
label: string;
buildPatch: (input: {
token?: string;
botToken?: string;
appToken?: string;
signalNumber?: string;
}) => Record<string, unknown>;
clearBaseFields: string[];
onAccountConfigChanged?: NonNullable<ChannelPlugin["lifecycle"]>["onAccountConfigChanged"];
onAccountRemoved?: NonNullable<ChannelPlugin["lifecycle"]>["onAccountRemoved"];
collectStatusIssues?: NonNullable<NonNullable<ChannelPlugin["status"]>["collectStatusIssues"]>;
}): ChannelPlugin {
return {
...createChannelTestPluginBase({
id: params.id,
label: params.label,
docsPath: `/channels/${params.id}`,
}),
config: createScopedChannelConfigAdapter({
sectionKey: params.id,
listAccountIds: (cfg) =>
listConfiguredAccountIds(cfg.channels?.[params.id] as ChannelSectionConfig | undefined),
resolveAccount: (cfg, accountId) => resolveScopedAccount(cfg, params.id, accountId),
defaultAccountId: () => DEFAULT_ACCOUNT_ID,
clearBaseFields: params.clearBaseFields,
resolveAllowFrom: () => [],
formatAllowFrom: (allowFrom) => allowFrom.map(String),
}),
setup: createPatchedAccountSetupAdapter({
channelKey: params.id,
buildPatch: (input) =>
params.buildPatch({
token: input.token,
botToken: input.botToken,
appToken: input.appToken,
signalNumber: input.signalNumber,
}),
}),
lifecycle:
params.onAccountConfigChanged || params.onAccountRemoved
? {
...(params.onAccountConfigChanged
? { onAccountConfigChanged: params.onAccountConfigChanged }
: {}),
...(params.onAccountRemoved ? { onAccountRemoved: params.onAccountRemoved } : {}),
}
: undefined,
status: params.collectStatusIssues
? {
collectStatusIssues: params.collectStatusIssues,
}
: undefined,
} as ChannelPlugin;
}
function createTelegramCommandTestPlugin(): ChannelPlugin {
const resolveTelegramAccount = (
cfg: Parameters<NonNullable<ChannelPlugin["config"]["resolveAccount"]>>[0],
accountId?: string | null,
) => resolveScopedAccount(cfg, "telegram", accountId) as { botToken?: string };
return createScopedCommandTestPlugin({
id: "telegram",
label: "Telegram",
buildPatch: ({ token }) => (token ? { botToken: token } : {}),
clearBaseFields: ["botToken", "name", "dmPolicy", "allowFrom", "groupPolicy", "streaming"],
onAccountConfigChanged: async ({ prevCfg, nextCfg, accountId }) => {
const prevTelegram = resolveTelegramAccount(prevCfg, accountId);
const nextTelegram = resolveTelegramAccount(nextCfg, accountId);
if ((prevTelegram.botToken ?? "").trim() !== (nextTelegram.botToken ?? "").trim()) {
await offsetMocks.deleteTelegramUpdateOffset({ accountId });
}
},
onAccountRemoved: async ({ accountId }) => {
await offsetMocks.deleteTelegramUpdateOffset({ accountId });
},
collectStatusIssues: (accounts) =>
accounts.flatMap((account) => {
if (account.enabled !== true || account.configured !== true) {
return [];
}
const issues = [];
if (account.allowUnmentionedGroups === true) {
issues.push({
channel: "telegram",
accountId: String(account.accountId ?? DEFAULT_ACCOUNT_ID),
kind: "config",
message:
"Config allows unmentioned group messages (requireMention=false). Telegram Bot API privacy mode will block most group messages unless disabled.",
});
}
const audit = account.audit as
| {
hasWildcardUnmentionedGroups?: boolean;
groups?: Array<{ chatId?: string; ok?: boolean; status?: string; error?: string }>;
}
| undefined;
if (audit?.hasWildcardUnmentionedGroups === true) {
issues.push({
channel: "telegram",
accountId: String(account.accountId ?? DEFAULT_ACCOUNT_ID),
kind: "config",
message:
'Telegram groups config uses "*" with requireMention=false; membership probing is not possible without explicit group IDs.',
});
}
for (const group of audit?.groups ?? []) {
if (group.ok === true || !group.chatId) {
continue;
}
issues.push({
channel: "telegram",
accountId: String(account.accountId ?? DEFAULT_ACCOUNT_ID),
kind: "runtime",
message: `Group ${group.chatId} not reachable by bot.${group.status ? ` status=${group.status}` : ""}${group.error ? `: ${group.error}` : ""}`,
});
}
return issues;
}),
});
}
function setMinimalChannelsCommandRegistryForTests(): void {
setActivePluginRegistry(
createTestRegistry([
{
pluginId: "telegram",
plugin: createTelegramCommandTestPlugin(),
source: "test",
},
{
pluginId: "whatsapp",
plugin: createScopedCommandTestPlugin({
id: "whatsapp",
label: "WhatsApp",
buildPatch: () => ({}),
clearBaseFields: ["name"],
}),
source: "test",
},
{
pluginId: "discord",
plugin: createScopedCommandTestPlugin({
id: "discord",
label: "Discord",
buildPatch: ({ token }) => (token ? { token } : {}),
clearBaseFields: ["token", "name"],
collectStatusIssues: (accounts) =>
accounts.flatMap((account) => {
if (account.enabled !== true || account.configured !== true) {
return [];
}
const issues = [];
const messageContent = (
account.application as
| { intents?: { messageContent?: string } }
| undefined
)?.intents?.messageContent;
if (messageContent === "disabled") {
issues.push({
channel: "discord",
accountId: String(account.accountId ?? DEFAULT_ACCOUNT_ID),
kind: "intent",
message:
"Message Content Intent is disabled. Bot may not see normal channel messages.",
});
}
const audit = account.audit as
| {
channels?: Array<{
channelId?: string;
ok?: boolean;
missing?: string[];
error?: string;
}>;
}
| undefined;
for (const channel of audit?.channels ?? []) {
if (channel.ok === true || !channel.channelId) {
continue;
}
issues.push({
channel: "discord",
accountId: String(account.accountId ?? DEFAULT_ACCOUNT_ID),
kind: "permissions",
message: `Channel ${channel.channelId} permission audit failed.${channel.missing?.length ? ` missing ${channel.missing.join(", ")}` : ""}${channel.error ? `: ${channel.error}` : ""}`,
});
}
return issues;
}),
}),
source: "test",
},
{
pluginId: "slack",
plugin: createScopedCommandTestPlugin({
id: "slack",
label: "Slack",
buildPatch: ({ botToken, appToken }) => ({
...(botToken ? { botToken } : {}),
...(appToken ? { appToken } : {}),
}),
clearBaseFields: ["botToken", "appToken", "name"],
}),
source: "test",
},
{
pluginId: "signal",
plugin: createScopedCommandTestPlugin({
id: "signal",
label: "Signal",
buildPatch: ({ signalNumber }) => (signalNumber ? { account: signalNumber } : {}),
clearBaseFields: ["account", "name"],
}),
source: "test",
},
]),
);
}
describe("channels command", () => {
beforeAll(async () => {
clackPrompterModule = await import("../wizard/clack-prompter.js");
@ -46,7 +324,7 @@ describe("channels command", () => {
version: 1,
profiles: {},
});
setDefaultChannelPluginRegistryForTests();
setMinimalChannelsCommandRegistryForTests();
});
function getWrittenConfig<T>(): T {