disclaw/tests/unit/sanitize-for-discord.test.ts
Nick Tabeling 21052bc646
Some checks failed
CI / build-and-test (ubuntu-latest) (pull_request) Has been cancelled
CI / build-and-test (windows-latest) (pull_request) Has been cancelled
CI / lint (pull_request) Has been cancelled
feat(DIS-152): mapped project paths (host:virtual) for agents
Adds optional path_mappings to agent.yaml so host directories are
transparently mapped to virtual paths in agent responses and CLAUDE.md.
Sanitizer replaces host paths (longest first, both slash styles) before
repoRoot/home substitutions. /new-agent accepts a host:virtual path
option with Windows-safe splitting and fs.existsSync validation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-13 10:41:10 +02:00

128 lines
5.3 KiB
TypeScript

import { describe, it, expect } from "vitest";
import { sanitizeForDiscord } from "../../src/runtime/sanitize";
describe("sanitizeForDiscord", () => {
// (a) Repo-root path is replaced with <disclaw>
it("replaces repoRoot with <disclaw>", () => {
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>/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("<redacted>");
});
// (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("<redacted>");
});
// (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("<disclaw>/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 <disclaw>\\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 <disclaw>/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);
});
});
});