import { describe, it, expect } from "vitest"; import { sanitizeForDiscord } from "../../src/runtime/sanitize"; describe("sanitizeForDiscord", () => { // (a) Repo-root path is replaced with it("replaces repoRoot with ", () => { const text = "Reading config from /home/user/project/disclaw.yaml"; const result = sanitizeForDiscord(text, { repoRoot: "/home/user/project" }); expect(result).toBe("Reading config from /disclaw.yaml"); expect(result).not.toContain("/home/user/project"); }); // (b) Home directory is replaced with ~ it("replaces home directory with ~", () => { const text = "Workspace located at /home/user/.disclaw/workspaces/agent-x"; const result = sanitizeForDiscord(text, { home: "/home/user" }); expect(result).toBe("Workspace located at ~/.disclaw/workspaces/agent-x"); expect(result).not.toContain("/home/user"); }); // (c) Discord bot token pattern is redacted it("redacts Discord bot tokens", () => { const token = "MTA1NjYwNDczNTA3NDU2NjE2.GbXkRa.abcdefghijklmnopqrstuvwxyz123"; const text = `Bot initialized with token ${token} — ready.`; const result = sanitizeForDiscord(text); expect(result).not.toContain(token); expect(result).toContain(""); }); // (d) Anthropic API key is redacted it("redacts Anthropic API keys", () => { const key = "sk-ant-api03-ABCDEFGHIJ1234567890abcdefghij"; const text = `Using key ${key} for requests.`; const result = sanitizeForDiscord(text); expect(result).not.toContain(key); expect(result).toContain(""); }); // (e) Harmless text is not modified (no false positives) it("does not modify harmless text", () => { const text = "Hello! The agent processed your request successfully in 120ms."; const result = sanitizeForDiscord(text, { home: "/home/user", repoRoot: "/home/user/project" }); expect(result).toBe(text); }); // (f) Empty string returns empty string it("handles empty string input", () => { expect(sanitizeForDiscord("")).toBe(""); expect(sanitizeForDiscord("", { home: "/home/user", repoRoot: "/home/user/project" })).toBe(""); }); // (g) No opts provided — plain text stays unchanged when no token patterns present it("returns text unchanged when no opts and no token patterns", () => { const text = "This is a perfectly normal response from the agent."; expect(sanitizeForDiscord(text)).toBe(text); }); // Ordering: repoRoot (sub-path of home) is replaced before home it("replaces repoRoot before home so nested paths are handled correctly", () => { const text = "/home/user/project/src/index.ts and /home/user/other.ts"; const result = sanitizeForDiscord(text, { repoRoot: "/home/user/project", home: "/home/user", }); expect(result).toBe("/src/index.ts and ~/other.ts"); }); // Windows-style backslash paths are sanitized it("replaces Windows backslash paths", () => { const text = "File at C:\\Users\\dev\\.disclaw\\workspaces\\agent-x\\src\\main.ts"; const result = sanitizeForDiscord(text, { repoRoot: "C:\\Users\\dev\\.disclaw\\workspaces\\agent-x", home: "C:\\Users\\dev", }); expect(result).toBe("File at \\src\\main.ts"); }); // Mixed slash styles (common on Windows when tools output forward slashes) it("replaces forward-slash variants of Windows paths", () => { const text = "File at C:/Users/dev/.disclaw/workspaces/agent-x/src/main.ts"; const result = sanitizeForDiscord(text, { repoRoot: "C:\\Users\\dev\\.disclaw\\workspaces\\agent-x", home: "C:\\Users\\dev", }); expect(result).toBe("File at /src/main.ts"); }); describe("pathMappings", () => { it("replaces host path with virtual path", () => { const text = "Working on /home/user/myproject/src/index.ts"; const result = sanitizeForDiscord(text, { pathMappings: [{ host: "/home/user/myproject", virtual: "~/myproject" }], }); expect(result).toBe("Working on ~/myproject/src/index.ts"); expect(result).not.toContain("/home/user/myproject"); }); it("handles both slash styles (forward and backslash)", () => { const textFwd = "File at C:/Code/projekt/main.ts"; const textBwd = "File at C:\\Code\\projekt\\main.ts"; const opts = { pathMappings: [{ host: "C:\\Code\\projekt", virtual: "~/projekt" }], }; expect(sanitizeForDiscord(textFwd, opts)).toBe("File at ~/projekt/main.ts"); expect(sanitizeForDiscord(textBwd, opts)).toBe("File at ~/projekt\\main.ts"); }); it("applies longest match first", () => { const text = "/home/user/projects/specific/file.ts and /home/user/projects/other.ts"; const result = sanitizeForDiscord(text, { pathMappings: [ { host: "/home/user/projects", virtual: "~/projects" }, { host: "/home/user/projects/specific", virtual: "~/specific" }, ], }); // The more specific (longer) mapping must win for the first path expect(result).toBe("~/specific/file.ts and ~/projects/other.ts"); }); it("does not affect text without host paths", () => { const text = "Hello! Everything is running smoothly."; const result = sanitizeForDiscord(text, { pathMappings: [{ host: "/home/user/myproject", virtual: "~/myproject" }], }); expect(result).toBe(text); }); }); });