test: cover discord config + slug routing
This commit is contained in:
@@ -206,6 +206,65 @@ describe("config identity defaults", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
import fs from "node:fs/promises";
|
||||||
|
describe("config discord", () => {
|
||||||
|
let previousHome: string | undefined;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
previousHome = process.env.HOME;
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
process.env.HOME = previousHome;
|
||||||
|
});
|
||||||
|
|
||||||
|
it("loads discord guild map + dm group settings", async () => {
|
||||||
|
await withTempHome(async (home) => {
|
||||||
|
const configDir = path.join(home, ".clawdis");
|
||||||
|
await fs.mkdir(configDir, { recursive: true });
|
||||||
|
await fs.writeFile(
|
||||||
|
path.join(configDir, "clawdis.json"),
|
||||||
|
JSON.stringify(
|
||||||
|
{
|
||||||
|
discord: {
|
||||||
|
enabled: true,
|
||||||
|
dm: {
|
||||||
|
enabled: true,
|
||||||
|
allowFrom: ["steipete"],
|
||||||
|
groupEnabled: true,
|
||||||
|
groupChannels: ["clawd-dm"],
|
||||||
|
},
|
||||||
|
guilds: {
|
||||||
|
"123": {
|
||||||
|
slug: "friends-of-clawd",
|
||||||
|
requireMention: false,
|
||||||
|
users: ["steipete"],
|
||||||
|
channels: {
|
||||||
|
general: { allow: true },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
2,
|
||||||
|
),
|
||||||
|
"utf-8",
|
||||||
|
);
|
||||||
|
|
||||||
|
vi.resetModules();
|
||||||
|
const { loadConfig } = await import("./config.js");
|
||||||
|
const cfg = loadConfig();
|
||||||
|
|
||||||
|
expect(cfg.discord?.enabled).toBe(true);
|
||||||
|
expect(cfg.discord?.dm?.groupEnabled).toBe(true);
|
||||||
|
expect(cfg.discord?.dm?.groupChannels).toEqual(["clawd-dm"]);
|
||||||
|
expect(cfg.discord?.guilds?.["123"]?.slug).toBe("friends-of-clawd");
|
||||||
|
expect(cfg.discord?.guilds?.["123"]?.channels?.general?.allow).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("Nix integration (U3, U5, U9)", () => {
|
describe("Nix integration (U3, U5, U9)", () => {
|
||||||
describe("U3: isNixMode env var detection", () => {
|
describe("U3: isNixMode env var detection", () => {
|
||||||
it("isNixMode is false when CLAWDIS_NIX_MODE is not set", async () => {
|
it("isNixMode is false when CLAWDIS_NIX_MODE is not set", async () => {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import path from "node:path";
|
|||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
buildGroupDisplayName,
|
||||||
deriveSessionKey,
|
deriveSessionKey,
|
||||||
loadSessionStore,
|
loadSessionStore,
|
||||||
resolveSessionKey,
|
resolveSessionKey,
|
||||||
@@ -51,6 +52,18 @@ describe("sessions", () => {
|
|||||||
).toBe("discord:group:12345");
|
).toBe("discord:group:12345");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("builds discord display name with guild+channel slugs", () => {
|
||||||
|
expect(
|
||||||
|
buildGroupDisplayName({
|
||||||
|
surface: "discord",
|
||||||
|
room: "#general",
|
||||||
|
space: "friends-of-clawd",
|
||||||
|
id: "123",
|
||||||
|
key: "discord:group:123",
|
||||||
|
}),
|
||||||
|
).toBe("discord:friends-of-clawd#general");
|
||||||
|
});
|
||||||
|
|
||||||
it("collapses direct chats to main by default", () => {
|
it("collapses direct chats to main by default", () => {
|
||||||
expect(resolveSessionKey("per-sender", { From: "+1555" })).toBe("main");
|
expect(resolveSessionKey("per-sender", { From: "+1555" })).toBe("main");
|
||||||
});
|
});
|
||||||
|
|||||||
149
src/discord/monitor.test.ts
Normal file
149
src/discord/monitor.test.ts
Normal 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);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -42,13 +42,13 @@ type DiscordHistoryEntry = {
|
|||||||
messageId?: string;
|
messageId?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type DiscordAllowList = {
|
export type DiscordAllowList = {
|
||||||
allowAll: boolean;
|
allowAll: boolean;
|
||||||
ids: Set<string>;
|
ids: Set<string>;
|
||||||
names: Set<string>;
|
names: Set<string>;
|
||||||
};
|
};
|
||||||
|
|
||||||
type DiscordGuildEntryResolved = {
|
export type DiscordGuildEntryResolved = {
|
||||||
id?: string;
|
id?: string;
|
||||||
slug?: string;
|
slug?: string;
|
||||||
requireMention?: boolean;
|
requireMention?: boolean;
|
||||||
@@ -56,7 +56,7 @@ type DiscordGuildEntryResolved = {
|
|||||||
channels?: Record<string, { allow?: boolean; requireMention?: boolean }>;
|
channels?: Record<string, { allow?: boolean; requireMention?: boolean }>;
|
||||||
};
|
};
|
||||||
|
|
||||||
type DiscordChannelConfigResolved = {
|
export type DiscordChannelConfigResolved = {
|
||||||
allowed: boolean;
|
allowed: boolean;
|
||||||
requireMention?: boolean;
|
requireMention?: boolean;
|
||||||
};
|
};
|
||||||
@@ -440,7 +440,7 @@ function buildGuildLabel(message: import("discord.js").Message) {
|
|||||||
return `${message.guild?.name ?? "Guild"} #${channelName} id:${message.channelId}`;
|
return `${message.guild?.name ?? "Guild"} #${channelName} id:${message.channelId}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeDiscordAllowList(
|
export function normalizeDiscordAllowList(
|
||||||
raw: Array<string | number> | undefined,
|
raw: Array<string | number> | undefined,
|
||||||
prefixes: string[],
|
prefixes: string[],
|
||||||
): DiscordAllowList | null {
|
): DiscordAllowList | null {
|
||||||
@@ -490,7 +490,7 @@ function normalizeDiscordName(value?: string | null) {
|
|||||||
return value.trim().toLowerCase();
|
return value.trim().toLowerCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeDiscordSlug(value?: string | null) {
|
export function normalizeDiscordSlug(value?: string | null) {
|
||||||
if (!value) return "";
|
if (!value) return "";
|
||||||
let text = value.trim().toLowerCase();
|
let text = value.trim().toLowerCase();
|
||||||
if (!text) return "";
|
if (!text) return "";
|
||||||
@@ -501,7 +501,7 @@ function normalizeDiscordSlug(value?: string | null) {
|
|||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
function allowListMatches(
|
export function allowListMatches(
|
||||||
allowList: DiscordAllowList,
|
allowList: DiscordAllowList,
|
||||||
candidates: {
|
candidates: {
|
||||||
id?: string;
|
id?: string;
|
||||||
@@ -523,7 +523,7 @@ function allowListMatches(
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveDiscordGuildEntry(params: {
|
export function resolveDiscordGuildEntry(params: {
|
||||||
guild: import("discord.js").Guild | null;
|
guild: import("discord.js").Guild | null;
|
||||||
guildEntries: Record<string, DiscordGuildEntryResolved> | undefined;
|
guildEntries: Record<string, DiscordGuildEntryResolved> | undefined;
|
||||||
}): DiscordGuildEntryResolved | null {
|
}): DiscordGuildEntryResolved | null {
|
||||||
@@ -570,7 +570,7 @@ function resolveDiscordGuildEntry(params: {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveDiscordChannelConfig(params: {
|
export function resolveDiscordChannelConfig(params: {
|
||||||
guildInfo: DiscordGuildEntryResolved | null;
|
guildInfo: DiscordGuildEntryResolved | null;
|
||||||
channelId: string;
|
channelId: string;
|
||||||
channelName?: string;
|
channelName?: string;
|
||||||
@@ -594,7 +594,7 @@ function resolveDiscordChannelConfig(params: {
|
|||||||
return { allowed: true };
|
return { allowed: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveGroupDmAllow(params: {
|
export function resolveGroupDmAllow(params: {
|
||||||
channels: Array<string | number> | undefined;
|
channels: Array<string | number> | undefined;
|
||||||
channelId: string;
|
channelId: string;
|
||||||
channelName?: string;
|
channelName?: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user