From 0bf11c1d69fbcfdaba564d5e6d88ee2210be7501 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Mon, 16 Mar 2026 22:18:19 -0700 Subject: [PATCH] Tests: guard channel setup import seams --- extensions/discord/src/plugin-shared.ts | 2 +- extensions/discord/src/shared.ts | 10 +- extensions/imessage/src/shared.ts | 16 ++- extensions/signal/src/shared.ts | 14 +- extensions/slack/src/shared.ts | 20 ++- extensions/telegram/src/shared.ts | 14 +- extensions/whatsapp/src/shared.ts | 20 +-- .../channel-import-guardrails.test.ts | 131 ++++++++++++++++++ 8 files changed, 178 insertions(+), 49 deletions(-) create mode 100644 src/plugin-sdk/channel-import-guardrails.test.ts diff --git a/extensions/discord/src/plugin-shared.ts b/extensions/discord/src/plugin-shared.ts index f67e04d1a51..d14f8050d30 100644 --- a/extensions/discord/src/plugin-shared.ts +++ b/extensions/discord/src/plugin-shared.ts @@ -3,7 +3,7 @@ import { createScopedAccountConfigAccessors, createScopedChannelConfigBase, } from "openclaw/plugin-sdk/channel-config-helpers"; -import type { OpenClawConfig } from "openclaw/plugin-sdk/discord"; +import type { OpenClawConfig } from "../../../src/config/config.js"; import { inspectDiscordAccount } from "./account-inspect.js"; import { listDiscordAccountIds, diff --git a/extensions/discord/src/shared.ts b/extensions/discord/src/shared.ts index 6a691252052..651e6d987f3 100644 --- a/extensions/discord/src/shared.ts +++ b/extensions/discord/src/shared.ts @@ -3,12 +3,10 @@ import { createScopedAccountConfigAccessors, formatAllowFromLowercase, } from "openclaw/plugin-sdk/compat"; -import { - buildChannelConfigSchema, - DiscordConfigSchema, - getChatChannelMeta, - type ChannelPlugin, -} from "openclaw/plugin-sdk/discord"; +import { buildChannelConfigSchema } from "../../../src/channels/plugins/config-schema.js"; +import type { ChannelPlugin } from "../../../src/channels/plugins/types.plugin.js"; +import { getChatChannelMeta } from "../../../src/channels/registry.js"; +import { DiscordConfigSchema } from "../../../src/config/zod-schema.providers-core.js"; import { inspectDiscordAccount } from "./account-inspect.js"; import { listDiscordAccountIds, diff --git a/extensions/imessage/src/shared.ts b/extensions/imessage/src/shared.ts index c4c62f20494..446e76ff39a 100644 --- a/extensions/imessage/src/shared.ts +++ b/extensions/imessage/src/shared.ts @@ -3,17 +3,19 @@ import { collectAllowlistProviderRestrictSendersWarnings, } from "openclaw/plugin-sdk/compat"; import { - buildChannelConfigSchema, - DEFAULT_ACCOUNT_ID, - deleteAccountFromConfigSection, formatTrimmedAllowFromEntries, - getChatChannelMeta, - IMessageConfigSchema, resolveIMessageConfigAllowFrom, resolveIMessageConfigDefaultTo, +} from "../../../src/plugin-sdk/channel-config-helpers.js"; +import { buildChannelConfigSchema } from "../../../src/channels/plugins/config-schema.js"; +import { + deleteAccountFromConfigSection, setAccountEnabledInConfigSection, - type ChannelPlugin, -} from "openclaw/plugin-sdk/imessage"; +} from "../../../src/channels/plugins/config-helpers.js"; +import type { ChannelPlugin } from "../../../src/channels/plugins/types.plugin.js"; +import { getChatChannelMeta } from "../../../src/channels/registry.js"; +import { IMessageConfigSchema } from "../../../src/config/zod-schema.providers-core.js"; +import { DEFAULT_ACCOUNT_ID } from "../../../src/routing/session-key.js"; import { listIMessageAccountIds, resolveDefaultIMessageAccountId, diff --git a/extensions/signal/src/shared.ts b/extensions/signal/src/shared.ts index 7c914f7ddf2..b1c1982f157 100644 --- a/extensions/signal/src/shared.ts +++ b/extensions/signal/src/shared.ts @@ -4,15 +4,15 @@ import { createScopedAccountConfigAccessors, } from "openclaw/plugin-sdk/compat"; import { - buildChannelConfigSchema, - DEFAULT_ACCOUNT_ID, deleteAccountFromConfigSection, - getChatChannelMeta, - normalizeE164, setAccountEnabledInConfigSection, - SignalConfigSchema, - type ChannelPlugin, -} from "openclaw/plugin-sdk/signal"; +} from "../../../src/channels/plugins/config-helpers.js"; +import { buildChannelConfigSchema } from "../../../src/channels/plugins/config-schema.js"; +import type { ChannelPlugin } from "../../../src/channels/plugins/types.plugin.js"; +import { getChatChannelMeta } from "../../../src/channels/registry.js"; +import { SignalConfigSchema } from "../../../src/config/zod-schema.providers-core.js"; +import { DEFAULT_ACCOUNT_ID } from "../../../src/routing/session-key.js"; +import { normalizeE164 } from "../../../src/utils.js"; import { listSignalAccountIds, resolveDefaultSignalAccountId, diff --git a/extensions/slack/src/shared.ts b/extensions/slack/src/shared.ts index d818eaab196..4471e851097 100644 --- a/extensions/slack/src/shared.ts +++ b/extensions/slack/src/shared.ts @@ -3,18 +3,14 @@ import { createScopedAccountConfigAccessors, createScopedChannelConfigBase, } from "openclaw/plugin-sdk/channel-config-helpers"; -import { - formatDocsLink, - hasConfiguredSecretInput, - patchChannelConfigForAccount, - type OpenClawConfig, -} from "openclaw/plugin-sdk/setup"; -import { - buildChannelConfigSchema, - getChatChannelMeta, - SlackConfigSchema, - type ChannelPlugin, -} from "openclaw/plugin-sdk/slack"; +import { buildChannelConfigSchema } from "../../../src/channels/plugins/config-schema.js"; +import { patchChannelConfigForAccount } from "../../../src/channels/plugins/setup-wizard-helpers.js"; +import type { ChannelPlugin } from "../../../src/channels/plugins/types.plugin.js"; +import { getChatChannelMeta } from "../../../src/channels/registry.js"; +import type { OpenClawConfig } from "../../../src/config/config.js"; +import { hasConfiguredSecretInput } from "../../../src/config/types.secrets.js"; +import { SlackConfigSchema } from "../../../src/config/zod-schema.providers-core.js"; +import { formatDocsLink } from "../../../src/terminal/links.js"; import { inspectSlackAccount } from "./account-inspect.js"; import { listSlackAccountIds, diff --git a/extensions/telegram/src/shared.ts b/extensions/telegram/src/shared.ts index a1c7945520d..3dec7b28ef5 100644 --- a/extensions/telegram/src/shared.ts +++ b/extensions/telegram/src/shared.ts @@ -3,14 +3,12 @@ import { createScopedAccountConfigAccessors, formatAllowFromLowercase, } from "openclaw/plugin-sdk/compat"; -import { - buildChannelConfigSchema, - getChatChannelMeta, - normalizeAccountId, - TelegramConfigSchema, - type ChannelPlugin, - type OpenClawConfig, -} from "openclaw/plugin-sdk/telegram"; +import { buildChannelConfigSchema } from "../../../src/channels/plugins/config-schema.js"; +import type { ChannelPlugin } from "../../../src/channels/plugins/types.plugin.js"; +import { getChatChannelMeta } from "../../../src/channels/registry.js"; +import type { OpenClawConfig } from "../../../src/config/config.js"; +import { TelegramConfigSchema } from "../../../src/config/zod-schema.providers-core.js"; +import { normalizeAccountId } from "../../../src/routing/session-key.js"; import { inspectTelegramAccount } from "./account-inspect.js"; import { listTelegramAccountIds, diff --git a/extensions/whatsapp/src/shared.ts b/extensions/whatsapp/src/shared.ts index 43df9bd7e6a..2cdfbd3cf8e 100644 --- a/extensions/whatsapp/src/shared.ts +++ b/extensions/whatsapp/src/shared.ts @@ -1,20 +1,24 @@ import { buildAccountScopedDmSecurityPolicy, - buildChannelConfigSchema, collectAllowlistProviderGroupPolicyWarnings, collectOpenGroupPolicyRouteAllowlistWarnings, - DEFAULT_ACCOUNT_ID, +} from "openclaw/plugin-sdk/compat"; +import { formatWhatsAppConfigAllowFromEntries, - getChatChannelMeta, - normalizeE164, resolveWhatsAppConfigAllowFrom, resolveWhatsAppConfigDefaultTo, - resolveWhatsAppGroupIntroHint, +} from "../../../src/plugin-sdk/channel-config-helpers.js"; +import { buildChannelConfigSchema } from "../../../src/channels/plugins/config-schema.js"; +import type { ChannelPlugin } from "../../../src/channels/plugins/types.plugin.js"; +import { resolveWhatsAppGroupRequireMention, resolveWhatsAppGroupToolPolicy, - WhatsAppConfigSchema, - type ChannelPlugin, -} from "openclaw/plugin-sdk/whatsapp"; +} from "../../../src/channels/plugins/group-mentions.js"; +import { resolveWhatsAppGroupIntroHint } from "../../../src/channels/plugins/whatsapp-shared.js"; +import { getChatChannelMeta } from "../../../src/channels/registry.js"; +import { WhatsAppConfigSchema } from "../../../src/config/zod-schema.providers-whatsapp.js"; +import { DEFAULT_ACCOUNT_ID } from "../../../src/routing/session-key.js"; +import { normalizeE164 } from "../../../src/utils.js"; import { listWhatsAppAccountIds, resolveDefaultWhatsAppAccountId, diff --git a/src/plugin-sdk/channel-import-guardrails.test.ts b/src/plugin-sdk/channel-import-guardrails.test.ts new file mode 100644 index 00000000000..51905b66b02 --- /dev/null +++ b/src/plugin-sdk/channel-import-guardrails.test.ts @@ -0,0 +1,131 @@ +import { readFileSync } from "node:fs"; +import { dirname, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; +import { describe, expect, it } from "vitest"; + +const ROOT_DIR = resolve(dirname(fileURLToPath(import.meta.url)), ".."); + +type GuardedSource = { + path: string; + forbiddenPatterns: RegExp[]; +}; + +const SAME_CHANNEL_SDK_GUARDS: GuardedSource[] = [ + { + path: "extensions/discord/src/plugin-shared.ts", + forbiddenPatterns: [/openclaw\/plugin-sdk\/discord/, /plugin-sdk-internal\/discord/], + }, + { + path: "extensions/discord/src/shared.ts", + forbiddenPatterns: [/openclaw\/plugin-sdk\/discord/, /plugin-sdk-internal\/discord/], + }, + { + path: "extensions/slack/src/shared.ts", + forbiddenPatterns: [/openclaw\/plugin-sdk\/slack/, /plugin-sdk-internal\/slack/], + }, + { + path: "extensions/telegram/src/shared.ts", + forbiddenPatterns: [/openclaw\/plugin-sdk\/telegram/, /plugin-sdk-internal\/telegram/], + }, + { + path: "extensions/imessage/src/shared.ts", + forbiddenPatterns: [/openclaw\/plugin-sdk\/imessage/, /plugin-sdk-internal\/imessage/], + }, + { + path: "extensions/whatsapp/src/shared.ts", + forbiddenPatterns: [/openclaw\/plugin-sdk\/whatsapp/, /plugin-sdk-internal\/whatsapp/], + }, + { + path: "extensions/signal/src/shared.ts", + forbiddenPatterns: [/openclaw\/plugin-sdk\/signal/, /plugin-sdk-internal\/signal/], + }, +]; + +const SETUP_BARREL_GUARDS: GuardedSource[] = [ + { + path: "extensions/signal/src/setup-core.ts", + forbiddenPatterns: [/\bformatCliCommand\b/, /\bformatDocsLink\b/], + }, + { + path: "extensions/signal/src/setup-surface.ts", + forbiddenPatterns: [ + /\bdetectBinary\b/, + /\binstallSignalCli\b/, + /\bformatCliCommand\b/, + /\bformatDocsLink\b/, + ], + }, + { + path: "extensions/slack/src/setup-core.ts", + forbiddenPatterns: [/\bformatDocsLink\b/], + }, + { + path: "extensions/slack/src/setup-surface.ts", + forbiddenPatterns: [/\bformatDocsLink\b/], + }, + { + path: "extensions/discord/src/setup-core.ts", + forbiddenPatterns: [/\bformatDocsLink\b/], + }, + { + path: "extensions/discord/src/setup-surface.ts", + forbiddenPatterns: [/\bformatDocsLink\b/], + }, + { + path: "extensions/imessage/src/setup-core.ts", + forbiddenPatterns: [/\bformatDocsLink\b/], + }, + { + path: "extensions/imessage/src/setup-surface.ts", + forbiddenPatterns: [/\bdetectBinary\b/, /\bformatDocsLink\b/], + }, + { + path: "extensions/telegram/src/setup-core.ts", + forbiddenPatterns: [/\bformatCliCommand\b/, /\bformatDocsLink\b/], + }, + { + path: "extensions/whatsapp/src/setup-surface.ts", + forbiddenPatterns: [/\bformatCliCommand\b/, /\bformatDocsLink\b/], + }, +]; + +function readSource(path: string): string { + return readFileSync(resolve(ROOT_DIR, "..", path), "utf8"); +} + +function readSetupBarrelImportBlock(path: string): string { + const lines = readSource(path).split("\n"); + const targetLineIndex = lines.findIndex((line) => + /from\s*"[^"]*plugin-sdk(?:-internal)?\/setup(?:\.js)?";/.test(line), + ); + if (targetLineIndex === -1) { + return ""; + } + let startLineIndex = targetLineIndex; + while (startLineIndex >= 0 && !lines[startLineIndex].includes("import")) { + startLineIndex -= 1; + } + return lines.slice(startLineIndex, targetLineIndex + 1).join("\n"); +} + +describe("channel import guardrails", () => { + it("keeps channel helper modules off their own SDK barrels", () => { + for (const source of SAME_CHANNEL_SDK_GUARDS) { + const text = readSource(source.path); + for (const pattern of source.forbiddenPatterns) { + expect(text, `${source.path} should not match ${pattern}`).not.toMatch(pattern); + } + } + }); + + it("keeps setup barrels limited to setup primitives", () => { + for (const source of SETUP_BARREL_GUARDS) { + const importBlock = readSetupBarrelImportBlock(source.path); + for (const pattern of source.forbiddenPatterns) { + expect(importBlock, `${source.path} setup import should not match ${pattern}`).not.toMatch( + pattern, + ); + } + } + }); +});