test: cover discord config + slug routing

This commit is contained in:
Peter Steinberger
2026-01-02 11:19:10 +01:00
parent eb44ae76f1
commit 4267a1b87d
4 changed files with 230 additions and 9 deletions

149
src/discord/monitor.test.ts Normal file
View File

@@ -0,0 +1,149 @@
import {
allowListMatches,
normalizeDiscordAllowList,
normalizeDiscordSlug,
resolveDiscordChannelConfig,
resolveDiscordGuildEntry,
resolveGroupDmAllow,
type DiscordGuildEntryResolved,
} from "./monitor.js";
const fakeGuild = (id: string, name: string) =>
({ id, name } as unknown as import("discord.js").Guild);
const makeEntries = (
entries: Record<string, Partial<DiscordGuildEntryResolved>>,
): Record<string, DiscordGuildEntryResolved> => {
const out: Record<string, DiscordGuildEntryResolved> = {};
for (const [key, value] of Object.entries(entries)) {
out[key] = {
slug: value.slug,
requireMention: value.requireMention,
users: value.users,
channels: value.channels,
};
}
return out;
};
describe("discord allowlist helpers", () => {
it("normalizes slugs", () => {
expect(normalizeDiscordSlug("Friends of Clawd"))
.toBe("friends-of-clawd");
expect(normalizeDiscordSlug("#General"))
.toBe("general");
expect(normalizeDiscordSlug("Dev__Chat"))
.toBe("dev-chat");
});
it("matches ids or names", () => {
const allow = normalizeDiscordAllowList(
["123", "steipete", "Friends of Clawd"],
["discord:", "user:", "guild:", "channel:"],
);
expect(allow).not.toBeNull();
expect(allowListMatches(allow!, { id: "123" })).toBe(true);
expect(allowListMatches(allow!, { name: "steipete" })).toBe(true);
expect(allowListMatches(allow!, { name: "friends-of-clawd" })).toBe(true);
expect(allowListMatches(allow!, { name: "other" })).toBe(false);
});
});
describe("discord guild/channel resolution", () => {
it("resolves guild entry by id", () => {
const guildEntries = makeEntries({
"123": { slug: "friends-of-clawd" },
});
const resolved = resolveDiscordGuildEntry({
guild: fakeGuild("123", "Friends of Clawd"),
guildEntries,
});
expect(resolved?.id).toBe("123");
expect(resolved?.slug).toBe("friends-of-clawd");
});
it("resolves guild entry by slug key", () => {
const guildEntries = makeEntries({
"friends-of-clawd": { slug: "friends-of-clawd" },
});
const resolved = resolveDiscordGuildEntry({
guild: fakeGuild("123", "Friends of Clawd"),
guildEntries,
});
expect(resolved?.id).toBe("123");
expect(resolved?.slug).toBe("friends-of-clawd");
});
it("resolves channel config by slug", () => {
const guildInfo: DiscordGuildEntryResolved = {
channels: {
general: { allow: true },
help: { allow: true, requireMention: true },
},
};
const channel = resolveDiscordChannelConfig({
guildInfo,
channelId: "456",
channelName: "General",
channelSlug: "general",
});
expect(channel?.allowed).toBe(true);
expect(channel?.requireMention).toBeUndefined();
const help = resolveDiscordChannelConfig({
guildInfo,
channelId: "789",
channelName: "Help",
channelSlug: "help",
});
expect(help?.allowed).toBe(true);
expect(help?.requireMention).toBe(true);
});
it("denies channel when config present but no match", () => {
const guildInfo: DiscordGuildEntryResolved = {
channels: {
general: { allow: true },
},
};
const channel = resolveDiscordChannelConfig({
guildInfo,
channelId: "999",
channelName: "random",
channelSlug: "random",
});
expect(channel?.allowed).toBe(false);
});
});
describe("discord group DM gating", () => {
it("allows all when no allowlist", () => {
expect(
resolveGroupDmAllow({
channels: undefined,
channelId: "1",
channelName: "dm",
channelSlug: "dm",
}),
).toBe(true);
});
it("matches group DM allowlist", () => {
expect(
resolveGroupDmAllow({
channels: ["clawd-dm"],
channelId: "1",
channelName: "Clawd DM",
channelSlug: "clawd-dm",
}),
).toBe(true);
expect(
resolveGroupDmAllow({
channels: ["clawd-dm"],
channelId: "1",
channelName: "Other",
channelSlug: "other",
}),
).toBe(false);
});
});

View File

@@ -42,13 +42,13 @@ type DiscordHistoryEntry = {
messageId?: string;
};
type DiscordAllowList = {
export type DiscordAllowList = {
allowAll: boolean;
ids: Set<string>;
names: Set<string>;
};
type DiscordGuildEntryResolved = {
export type DiscordGuildEntryResolved = {
id?: string;
slug?: string;
requireMention?: boolean;
@@ -56,7 +56,7 @@ type DiscordGuildEntryResolved = {
channels?: Record<string, { allow?: boolean; requireMention?: boolean }>;
};
type DiscordChannelConfigResolved = {
export type DiscordChannelConfigResolved = {
allowed: boolean;
requireMention?: boolean;
};
@@ -440,7 +440,7 @@ function buildGuildLabel(message: import("discord.js").Message) {
return `${message.guild?.name ?? "Guild"} #${channelName} id:${message.channelId}`;
}
function normalizeDiscordAllowList(
export function normalizeDiscordAllowList(
raw: Array<string | number> | undefined,
prefixes: string[],
): DiscordAllowList | null {
@@ -490,7 +490,7 @@ function normalizeDiscordName(value?: string | null) {
return value.trim().toLowerCase();
}
function normalizeDiscordSlug(value?: string | null) {
export function normalizeDiscordSlug(value?: string | null) {
if (!value) return "";
let text = value.trim().toLowerCase();
if (!text) return "";
@@ -501,7 +501,7 @@ function normalizeDiscordSlug(value?: string | null) {
return text;
}
function allowListMatches(
export function allowListMatches(
allowList: DiscordAllowList,
candidates: {
id?: string;
@@ -523,7 +523,7 @@ function allowListMatches(
return false;
}
function resolveDiscordGuildEntry(params: {
export function resolveDiscordGuildEntry(params: {
guild: import("discord.js").Guild | null;
guildEntries: Record<string, DiscordGuildEntryResolved> | undefined;
}): DiscordGuildEntryResolved | null {
@@ -570,7 +570,7 @@ function resolveDiscordGuildEntry(params: {
return null;
}
function resolveDiscordChannelConfig(params: {
export function resolveDiscordChannelConfig(params: {
guildInfo: DiscordGuildEntryResolved | null;
channelId: string;
channelName?: string;
@@ -594,7 +594,7 @@ function resolveDiscordChannelConfig(params: {
return { allowed: true };
}
function resolveGroupDmAllow(params: {
export function resolveGroupDmAllow(params: {
channels: Array<string | number> | undefined;
channelId: string;
channelName?: string;