mirror of https://github.com/openclaw/openclaw.git
fix(test): trim channel account registry imports
This commit is contained in:
parent
6a3c68d470
commit
c842ca0166
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Reference in New Issue