test: share channel audit plugin fixtures

This commit is contained in:
Peter Steinberger
2026-04-23 18:23:39 +01:00
parent 9ee800e81d
commit 73e247321b
3 changed files with 142 additions and 185 deletions

View File

@@ -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<string, unknown> }
| 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", () => {

View File

@@ -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<string, unknown> }
| 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),

View File

@@ -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<ChannelPlugin["security"]>["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<string, unknown> }
| 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,
},
};
}