test: speed up discord audit tests

This commit is contained in:
Peter Steinberger
2026-04-07 14:37:45 +01:00
parent ee6ff1b8c2
commit c385a2d45e
3 changed files with 159 additions and 142 deletions

View File

@@ -0,0 +1,138 @@
import type {
DiscordGuildChannelConfig,
DiscordGuildEntry,
} from "openclaw/plugin-sdk/config-runtime";
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
import { isRecord } from "openclaw/plugin-sdk/text-runtime";
export type DiscordChannelPermissionsAuditEntry = {
channelId: string;
ok: boolean;
missing?: string[];
error?: string | null;
matchKey?: string;
matchSource?: "id";
};
export type DiscordChannelPermissionsAudit = {
ok: boolean;
checkedChannels: number;
unresolvedChannels: number;
channels: DiscordChannelPermissionsAuditEntry[];
elapsedMs: number;
};
const REQUIRED_CHANNEL_PERMISSIONS = ["ViewChannel", "SendMessages"] as const;
function shouldAuditChannelConfig(config: DiscordGuildChannelConfig | undefined) {
if (!config) {
return true;
}
if (config.enabled === false) {
return false;
}
return true;
}
export function listConfiguredGuildChannelKeys(
guilds: Record<string, DiscordGuildEntry> | undefined,
): string[] {
if (!guilds) {
return [];
}
const ids = new Set<string>();
for (const entry of Object.values(guilds)) {
if (!entry || typeof entry !== "object") {
continue;
}
const channelsRaw = (entry as { channels?: unknown }).channels;
if (!isRecord(channelsRaw)) {
continue;
}
for (const [key, value] of Object.entries(channelsRaw)) {
const channelId = String(key).trim();
if (!channelId) {
continue;
}
if (channelId === "*") {
continue;
}
if (!shouldAuditChannelConfig(value as DiscordGuildChannelConfig | undefined)) {
continue;
}
ids.add(channelId);
}
}
return [...ids].toSorted((a, b) => a.localeCompare(b));
}
export function collectDiscordAuditChannelIdsForGuilds(
guilds: Record<string, DiscordGuildEntry> | undefined,
) {
const keys = listConfiguredGuildChannelKeys(guilds);
const channelIds = keys.filter((key) => /^\d+$/.test(key));
const unresolvedChannels = keys.length - channelIds.length;
return { channelIds, unresolvedChannels };
}
export async function auditDiscordChannelPermissionsWithFetcher(params: {
token: string;
accountId?: string | null;
channelIds: string[];
timeoutMs: number;
fetchChannelPermissions: (
channelId: string,
params: { token: string; accountId?: string },
) => Promise<{
permissions: string[];
}>;
}): Promise<DiscordChannelPermissionsAudit> {
const started = Date.now();
const token = params.token?.trim() ?? "";
if (!token || params.channelIds.length === 0) {
return {
ok: true,
checkedChannels: 0,
unresolvedChannels: 0,
channels: [],
elapsedMs: Date.now() - started,
};
}
const required = [...REQUIRED_CHANNEL_PERMISSIONS];
const channels: DiscordChannelPermissionsAuditEntry[] = [];
for (const channelId of params.channelIds) {
try {
const perms = await params.fetchChannelPermissions(channelId, {
token,
accountId: params.accountId ?? undefined,
});
const missing = required.filter((p) => !perms.permissions.includes(p));
channels.push({
channelId,
ok: missing.length === 0,
missing: missing.length ? missing : undefined,
error: null,
matchKey: channelId,
matchSource: "id",
});
} catch (err) {
channels.push({
channelId,
ok: false,
error: formatErrorMessage(err),
matchKey: channelId,
matchSource: "id",
});
}
}
return {
ok: channels.every((c) => c.ok),
checkedChannels: channels.length,
unresolvedChannels: 0,
channels,
elapsedMs: Date.now() - started,
};
}

View File

@@ -1,20 +1,12 @@
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { beforeEach, describe, expect, it, vi } from "vitest";
import {
auditDiscordChannelPermissionsWithFetcher,
collectDiscordAuditChannelIdsForGuilds,
} from "./audit-core.js";
const sendModule = await import("./send.js");
const fetchChannelPermissionsDiscordMock = vi.fn();
vi.spyOn(sendModule, "fetchChannelPermissionsDiscord").mockImplementation(
fetchChannelPermissionsDiscordMock,
);
let auditDiscordChannelPermissions: typeof import("./audit.js").auditDiscordChannelPermissions;
let collectDiscordAuditChannelIds: typeof import("./audit.js").collectDiscordAuditChannelIds;
describe("discord audit", () => {
beforeAll(async () => {
({ collectDiscordAuditChannelIds, auditDiscordChannelPermissions } =
await import("./audit.js"));
});
beforeEach(() => {
fetchChannelPermissionsDiscordMock.mockReset();
});
@@ -39,10 +31,7 @@ describe("discord audit", () => {
},
} as unknown as import("openclaw/plugin-sdk/config-runtime").OpenClawConfig;
const collected = collectDiscordAuditChannelIds({
cfg,
accountId: "default",
});
const collected = collectDiscordAuditChannelIdsForGuilds(cfg.channels.discord.guilds);
expect(collected.channelIds).toEqual(["111", "222"]);
expect(collected.unresolvedChannels).toBe(1);
@@ -59,11 +48,12 @@ describe("discord audit", () => {
isDm: false,
});
const audit = await auditDiscordChannelPermissions({
const audit = await auditDiscordChannelPermissionsWithFetcher({
token: "t",
accountId: "default",
channelIds: collected.channelIds,
timeoutMs: 1000,
fetchChannelPermissions: fetchChannelPermissionsDiscordMock,
});
expect(audit.ok).toBe(false);
expect(audit.channels).toHaveLength(2);
@@ -90,7 +80,7 @@ describe("discord audit", () => {
},
} as unknown as import("openclaw/plugin-sdk/config-runtime").OpenClawConfig;
const collected = collectDiscordAuditChannelIds({ cfg, accountId: "default" });
const collected = collectDiscordAuditChannelIdsForGuilds(cfg.channels.discord.guilds);
expect(collected.channelIds).toEqual(["111"]);
expect(collected.unresolvedChannels).toBe(0);
});
@@ -113,7 +103,7 @@ describe("discord audit", () => {
},
} as unknown as import("openclaw/plugin-sdk/config-runtime").OpenClawConfig;
const collected = collectDiscordAuditChannelIds({ cfg, accountId: "default" });
const collected = collectDiscordAuditChannelIdsForGuilds(cfg.channels.discord.guilds);
expect(collected.channelIds).toEqual([]);
expect(collected.unresolvedChannels).toBe(0);
});
@@ -140,7 +130,7 @@ describe("discord audit", () => {
},
} as unknown as import("openclaw/plugin-sdk/config-runtime").OpenClawConfig;
const collected = collectDiscordAuditChannelIds({ cfg, accountId: "default" });
const collected = collectDiscordAuditChannelIdsForGuilds(cfg.channels.discord.guilds);
expect(collected.channelIds).toEqual(["111"]);
expect(collected.unresolvedChannels).toBe(1);
});

View File

@@ -1,76 +1,12 @@
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
import type {
DiscordGuildChannelConfig,
DiscordGuildEntry,
} from "openclaw/plugin-sdk/config-runtime";
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
import { isRecord } from "openclaw/plugin-sdk/text-runtime";
import { inspectDiscordAccount } from "./account-inspect.js";
import {
auditDiscordChannelPermissionsWithFetcher,
collectDiscordAuditChannelIdsForGuilds,
type DiscordChannelPermissionsAudit,
} from "./audit-core.js";
import { fetchChannelPermissionsDiscord } from "./send.js";
export type DiscordChannelPermissionsAuditEntry = {
channelId: string;
ok: boolean;
missing?: string[];
error?: string | null;
matchKey?: string;
matchSource?: "id";
};
export type DiscordChannelPermissionsAudit = {
ok: boolean;
checkedChannels: number;
unresolvedChannels: number;
channels: DiscordChannelPermissionsAuditEntry[];
elapsedMs: number;
};
const REQUIRED_CHANNEL_PERMISSIONS = ["ViewChannel", "SendMessages"] as const;
function shouldAuditChannelConfig(config: DiscordGuildChannelConfig | undefined) {
if (!config) {
return true;
}
if (config.enabled === false) {
return false;
}
return true;
}
function listConfiguredGuildChannelKeys(
guilds: Record<string, DiscordGuildEntry> | undefined,
): string[] {
if (!guilds) {
return [];
}
const ids = new Set<string>();
for (const entry of Object.values(guilds)) {
if (!entry || typeof entry !== "object") {
continue;
}
const channelsRaw = (entry as { channels?: unknown }).channels;
if (!isRecord(channelsRaw)) {
continue;
}
for (const [key, value] of Object.entries(channelsRaw)) {
const channelId = String(key).trim();
if (!channelId) {
continue;
}
// Skip wildcard keys (e.g. "*" meaning "all channels") — they are valid
// config but are not real channel IDs and should not be audited.
if (channelId === "*") {
continue;
}
if (!shouldAuditChannelConfig(value as DiscordGuildChannelConfig | undefined)) {
continue;
}
ids.add(channelId);
}
}
return [...ids].toSorted((a, b) => a.localeCompare(b));
}
export function collectDiscordAuditChannelIds(params: {
cfg: OpenClawConfig;
accountId?: string | null;
@@ -79,10 +15,7 @@ export function collectDiscordAuditChannelIds(params: {
cfg: params.cfg,
accountId: params.accountId,
});
const keys = listConfiguredGuildChannelKeys(account.config.guilds);
const channelIds = keys.filter((key) => /^\d+$/.test(key));
const unresolvedChannels = keys.length - channelIds.length;
return { channelIds, unresolvedChannels };
return collectDiscordAuditChannelIdsForGuilds(account.config.guilds);
}
export async function auditDiscordChannelPermissions(params: {
@@ -91,52 +24,8 @@ export async function auditDiscordChannelPermissions(params: {
channelIds: string[];
timeoutMs: number;
}): Promise<DiscordChannelPermissionsAudit> {
const started = Date.now();
const token = params.token?.trim() ?? "";
if (!token || params.channelIds.length === 0) {
return {
ok: true,
checkedChannels: 0,
unresolvedChannels: 0,
channels: [],
elapsedMs: Date.now() - started,
};
}
const required = [...REQUIRED_CHANNEL_PERMISSIONS];
const channels: DiscordChannelPermissionsAuditEntry[] = [];
for (const channelId of params.channelIds) {
try {
const perms = await fetchChannelPermissionsDiscord(channelId, {
token,
accountId: params.accountId ?? undefined,
});
const missing = required.filter((p) => !perms.permissions.includes(p));
channels.push({
channelId,
ok: missing.length === 0,
missing: missing.length ? missing : undefined,
error: null,
matchKey: channelId,
matchSource: "id",
});
} catch (err) {
channels.push({
channelId,
ok: false,
error: formatErrorMessage(err),
matchKey: channelId,
matchSource: "id",
});
}
}
return {
ok: channels.every((c) => c.ok),
checkedChannels: channels.length,
unresolvedChannels: 0,
channels,
elapsedMs: Date.now() - started,
};
return await auditDiscordChannelPermissionsWithFetcher({
...params,
fetchChannelPermissions: fetchChannelPermissionsDiscord,
});
}