mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-14 02:31:24 +00:00
test: speed up discord audit tests
This commit is contained in:
138
extensions/discord/src/audit-core.ts
Normal file
138
extensions/discord/src/audit-core.ts
Normal 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,
|
||||
};
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user