From 73e247321bb7097f2d0403e5340d0c2dad63b07d Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Thu, 23 Apr 2026 18:23:39 +0100 Subject: [PATCH] test: share channel audit plugin fixtures --- ...udit-channel-source-config-discord.test.ts | 77 ++----- .../audit-channel-source-config-slack.test.ts | 199 ++++++------------ src/security/audit-channel-test-helpers.ts | 51 +++++ 3 files changed, 142 insertions(+), 185 deletions(-) create mode 100644 src/security/audit-channel-test-helpers.ts diff --git a/src/security/audit-channel-source-config-discord.test.ts b/src/security/audit-channel-source-config-discord.test.ts index 6028b62dd04..1136d0be274 100644 --- a/src/security/audit-channel-source-config-discord.test.ts +++ b/src/security/audit-channel-source-config-discord.test.ts @@ -1,71 +1,40 @@ import { describe, expect, it } from "vitest"; -import type { ChannelPlugin } from "../channels/plugins/types.js"; import type { OpenClawConfig } from "../config/config.js"; +import { stubAuditChannelPlugin } from "./audit-channel-test-helpers.js"; import { collectChannelSecurityFindings } from "./audit-channel.js"; function stubDiscordPlugin(params: { resolveAccount: (cfg: OpenClawConfig, accountId: string | null | undefined) => unknown; inspectAccount?: (cfg: OpenClawConfig, accountId: string | null | undefined) => unknown; isConfigured?: (account: unknown, cfg: OpenClawConfig) => boolean; -}): ChannelPlugin { - return { +}) { + return stubAuditChannelPlugin({ id: "discord", - meta: { - id: "discord", - label: "Discord", - selectionLabel: "Discord", - docsPath: "/docs/testing", - blurb: "test stub", - }, - capabilities: { - chatTypes: ["direct", "group"], - }, + label: "Discord", commands: { nativeCommandsAutoEnabled: true, nativeSkillsAutoEnabled: true, }, - security: { - collectAuditFindings: ({ account }) => { - const config = (account as { config?: { guilds?: unknown } }).config ?? {}; - const guilds = - config.guilds && typeof config.guilds === "object" && !Array.isArray(config.guilds) - ? config.guilds - : {}; - if (Object.keys(guilds).length === 0) { - return []; - } - return [ - { - checkId: "channels.discord.commands.native.no_allowlists", - severity: "warn" as const, - title: "Discord slash commands have no allowlists", - detail: "test stub", - }, - ]; - }, + collectAuditFindings: ({ account }) => { + const config = (account as { config?: { guilds?: unknown } }).config ?? {}; + const guilds = + config.guilds && typeof config.guilds === "object" && !Array.isArray(config.guilds) + ? config.guilds + : {}; + if (Object.keys(guilds).length === 0) { + return []; + } + return [ + { + checkId: "channels.discord.commands.native.no_allowlists", + severity: "warn" as const, + title: "Discord slash commands have no allowlists", + detail: "test stub", + }, + ]; }, - config: { - listAccountIds: () => ["default"], - inspectAccount: - params.inspectAccount ?? - ((cfg, accountId) => { - const resolvedAccountId = - typeof accountId === "string" && accountId ? accountId : "default"; - const account = params.resolveAccount(cfg, resolvedAccountId) as - | { config?: Record } - | undefined; - return { - accountId: resolvedAccountId, - enabled: true, - configured: true, - config: account?.config ?? {}, - }; - }), - resolveAccount: (cfg, accountId) => params.resolveAccount(cfg, accountId), - isEnabled: () => true, - isConfigured: (account, cfg) => params.isConfigured?.(account, cfg) ?? true, - }, - }; + ...params, + }); } describe("security audit channel source-config fallback discord", () => { diff --git a/src/security/audit-channel-source-config-slack.test.ts b/src/security/audit-channel-source-config-slack.test.ts index 3b7f85090bd..013047c64a1 100644 --- a/src/security/audit-channel-source-config-slack.test.ts +++ b/src/security/audit-channel-source-config-slack.test.ts @@ -1,71 +1,75 @@ import { describe, expect, it } from "vitest"; -import type { ChannelPlugin } from "../channels/plugins/types.js"; import type { OpenClawConfig } from "../config/config.js"; +import { stubAuditChannelPlugin } from "./audit-channel-test-helpers.js"; import { collectChannelSecurityFindings } from "./audit-channel.js"; function stubSlackPlugin(params: { resolveAccount: (cfg: OpenClawConfig, accountId: string | null | undefined) => unknown; inspectAccount?: (cfg: OpenClawConfig, accountId: string | null | undefined) => unknown; isConfigured?: (account: unknown, cfg: OpenClawConfig) => boolean; -}): ChannelPlugin { - return { +}) { + return stubAuditChannelPlugin({ id: "slack", - meta: { - id: "slack", - label: "Slack", - selectionLabel: "Slack", - docsPath: "/docs/testing", - blurb: "test stub", - }, - capabilities: { - chatTypes: ["direct", "group"], - }, + label: "Slack", commands: { nativeCommandsAutoEnabled: false, nativeSkillsAutoEnabled: false, }, - security: { - collectAuditFindings: async ({ account }) => { - const config = - (account as { config?: { slashCommand?: { enabled?: boolean }; allowFrom?: unknown } }) - .config ?? {}; - const slashCommandEnabled = config.slashCommand?.enabled === true; - const allowFrom = - Array.isArray(config.allowFrom) && config.allowFrom.length > 0 ? config.allowFrom : []; - if (!slashCommandEnabled || allowFrom.length > 0) { - return []; - } - return [ - { - checkId: "channels.slack.commands.slash.no_allowlists", - severity: "warn" as const, - title: "Slack slash commands have no allowlists", - detail: "test stub", - }, - ]; + collectAuditFindings: async ({ account }) => { + const config = + (account as { config?: { slashCommand?: { enabled?: boolean }; allowFrom?: unknown } }) + .config ?? {}; + const slashCommandEnabled = config.slashCommand?.enabled === true; + const allowFrom = + Array.isArray(config.allowFrom) && config.allowFrom.length > 0 ? config.allowFrom : []; + if (!slashCommandEnabled || allowFrom.length > 0) { + return []; + } + return [ + { + checkId: "channels.slack.commands.slash.no_allowlists", + severity: "warn" as const, + title: "Slack slash commands have no allowlists", + detail: "test stub", + }, + ]; + }, + ...params, + }); +} + +function makeSlackHttpConfig(): OpenClawConfig { + return { + channels: { + slack: { + enabled: true, + mode: "http", + groupPolicy: "open", + slashCommand: { enabled: true }, }, }, - config: { - listAccountIds: () => ["default"], - inspectAccount: - params.inspectAccount ?? - ((cfg, accountId) => { - const resolvedAccountId = - typeof accountId === "string" && accountId ? accountId : "default"; - const account = params.resolveAccount(cfg, resolvedAccountId) as - | { config?: Record } - | undefined; - return { - accountId: resolvedAccountId, - enabled: true, - configured: true, - config: account?.config ?? {}, - }; - }), - resolveAccount: (cfg, accountId) => params.resolveAccount(cfg, accountId), - isEnabled: () => true, - isConfigured: (account, cfg) => params.isConfigured?.(account, cfg) ?? true, - }, + } as OpenClawConfig; +} + +function makeSlackInspection( + channel: unknown, + overrides: { + enabled?: boolean; + configured?: boolean; + botTokenStatus?: string; + signingSecretStatus?: string; + }, +) { + return { + accountId: "default", + enabled: overrides.enabled ?? true, + configured: overrides.configured ?? true, + mode: "http", + botTokenSource: "config", + botTokenStatus: overrides.botTokenStatus ?? "configured_unavailable", + signingSecretSource: "config", + signingSecretStatus: overrides.signingSecretStatus ?? "configured_unavailable", + config: channel, }; } @@ -74,54 +78,21 @@ describe("security audit channel source-config fallback slack", () => { const cases = [ { name: "slack resolved inspection only exposes signingSecret status", - sourceConfig: { - channels: { - slack: { - enabled: true, - mode: "http", - groupPolicy: "open", - slashCommand: { enabled: true }, - }, - }, - } as OpenClawConfig, - resolvedConfig: { - channels: { - slack: { - enabled: true, - mode: "http", - groupPolicy: "open", - slashCommand: { enabled: true }, - }, - }, - } as OpenClawConfig, + sourceConfig: makeSlackHttpConfig(), + resolvedConfig: makeSlackHttpConfig(), plugin: (sourceConfig: OpenClawConfig) => stubSlackPlugin({ inspectAccount: (cfg) => { const channel = cfg.channels?.slack ?? {}; if (cfg === sourceConfig) { - return { - accountId: "default", + return makeSlackInspection(channel, { enabled: false, - configured: true, - mode: "http", - botTokenSource: "config", - botTokenStatus: "configured_unavailable", - signingSecretSource: "config", - signingSecretStatus: "configured_unavailable", - config: channel, - }; + }); } - return { - accountId: "default", - enabled: true, - configured: true, - mode: "http", - botTokenSource: "config", + return makeSlackInspection(channel, { botTokenStatus: "available", - signingSecretSource: "config", signingSecretStatus: "available", - config: channel, - }; + }); }, resolveAccount: (cfg) => ({ config: cfg.channels?.slack ?? {} }), isConfigured: (account) => Boolean((account as { configured?: boolean }).configured), @@ -129,54 +100,20 @@ describe("security audit channel source-config fallback slack", () => { }, { name: "slack source config still wins when resolved inspection is unconfigured", - sourceConfig: { - channels: { - slack: { - enabled: true, - mode: "http", - groupPolicy: "open", - slashCommand: { enabled: true }, - }, - }, - } as OpenClawConfig, - resolvedConfig: { - channels: { - slack: { - enabled: true, - mode: "http", - groupPolicy: "open", - slashCommand: { enabled: true }, - }, - }, - } as OpenClawConfig, + sourceConfig: makeSlackHttpConfig(), + resolvedConfig: makeSlackHttpConfig(), plugin: (sourceConfig: OpenClawConfig) => stubSlackPlugin({ inspectAccount: (cfg) => { const channel = cfg.channels?.slack ?? {}; if (cfg === sourceConfig) { - return { - accountId: "default", - enabled: true, - configured: true, - mode: "http", - botTokenSource: "config", - botTokenStatus: "configured_unavailable", - signingSecretSource: "config", - signingSecretStatus: "configured_unavailable", - config: channel, - }; + return makeSlackInspection(channel, {}); } - return { - accountId: "default", - enabled: true, + return makeSlackInspection(channel, { configured: false, - mode: "http", - botTokenSource: "config", botTokenStatus: "available", - signingSecretSource: "config", signingSecretStatus: "missing", - config: channel, - }; + }); }, resolveAccount: (cfg) => ({ config: cfg.channels?.slack ?? {} }), isConfigured: (account) => Boolean((account as { configured?: boolean }).configured), diff --git a/src/security/audit-channel-test-helpers.ts b/src/security/audit-channel-test-helpers.ts new file mode 100644 index 00000000000..d2e7fdbd4bd --- /dev/null +++ b/src/security/audit-channel-test-helpers.ts @@ -0,0 +1,51 @@ +import type { ChannelPlugin } from "../channels/plugins/types.js"; +import type { OpenClawConfig } from "../config/config.js"; + +export function stubAuditChannelPlugin(params: { + id: string; + label: string; + commands: ChannelPlugin["commands"]; + collectAuditFindings: NonNullable["collectAuditFindings"]; + resolveAccount: (cfg: OpenClawConfig, accountId: string | null | undefined) => unknown; + inspectAccount?: (cfg: OpenClawConfig, accountId: string | null | undefined) => unknown; + isConfigured?: (account: unknown, cfg: OpenClawConfig) => boolean; +}): ChannelPlugin { + return { + id: params.id, + meta: { + id: params.id, + label: params.label, + selectionLabel: params.label, + docsPath: "/docs/testing", + blurb: "test stub", + }, + capabilities: { + chatTypes: ["direct", "group"], + }, + commands: params.commands, + security: { + collectAuditFindings: params.collectAuditFindings, + }, + config: { + listAccountIds: () => ["default"], + inspectAccount: + params.inspectAccount ?? + ((cfg, accountId) => { + const resolvedAccountId = + typeof accountId === "string" && accountId ? accountId : "default"; + const account = params.resolveAccount(cfg, resolvedAccountId) as + | { config?: Record } + | undefined; + return { + accountId: resolvedAccountId, + enabled: true, + configured: true, + config: account?.config ?? {}, + }; + }), + resolveAccount: (cfg, accountId) => params.resolveAccount(cfg, accountId), + isEnabled: () => true, + isConfigured: (account, cfg) => params.isConfigured?.(account, cfg) ?? true, + }, + }; +}