diff --git a/src/utils/permissions/__tests__/permissionRuleParser.test.ts b/src/utils/permissions/__tests__/permissionRuleParser.test.ts new file mode 100644 index 0000000..fef799e --- /dev/null +++ b/src/utils/permissions/__tests__/permissionRuleParser.test.ts @@ -0,0 +1,152 @@ +import { describe, expect, test } from "bun:test"; +import { + escapeRuleContent, + unescapeRuleContent, + permissionRuleValueFromString, + permissionRuleValueToString, + normalizeLegacyToolName, +} from "../permissionRuleParser"; + +describe("escapeRuleContent", () => { + test("escapes backslashes first", () => { + expect(escapeRuleContent("a\\b")).toBe("a\\\\b"); + }); + + test("escapes opening parentheses", () => { + expect(escapeRuleContent("fn(x)")).toBe("fn\\(x\\)"); + }); + + test("escapes backslash before parens correctly", () => { + expect(escapeRuleContent('echo "test\\nvalue"')).toBe( + 'echo "test\\\\nvalue"' + ); + }); + + test("returns unchanged string with no special chars", () => { + expect(escapeRuleContent("npm install")).toBe("npm install"); + }); + + test("handles empty string", () => { + expect(escapeRuleContent("")).toBe(""); + }); +}); + +describe("unescapeRuleContent", () => { + test("unescapes parentheses", () => { + expect(unescapeRuleContent("fn\\(x\\)")).toBe("fn(x)"); + }); + + test("unescapes backslashes", () => { + expect(unescapeRuleContent("a\\\\b")).toBe("a\\b"); + }); + + test("roundtrips with escapeRuleContent", () => { + const original = 'python -c "print(1)"'; + expect(unescapeRuleContent(escapeRuleContent(original))).toBe(original); + }); + + test("handles content with backslash-paren combo", () => { + const original = 'echo "test\\nvalue"'; + expect(unescapeRuleContent(escapeRuleContent(original))).toBe(original); + }); + + test("returns unchanged string with no escapes", () => { + expect(unescapeRuleContent("npm install")).toBe("npm install"); + }); +}); + +describe("permissionRuleValueFromString", () => { + test("parses tool name only", () => { + expect(permissionRuleValueFromString("Bash")).toEqual({ + toolName: "Bash", + }); + }); + + test("parses tool name with content", () => { + expect(permissionRuleValueFromString("Bash(npm install)")).toEqual({ + toolName: "Bash", + ruleContent: "npm install", + }); + }); + + test("handles escaped parens in content", () => { + const result = permissionRuleValueFromString( + 'Bash(python -c "print\\(1\\)")' + ); + expect(result.toolName).toBe("Bash"); + expect(result.ruleContent).toBe('python -c "print(1)"'); + }); + + test("treats empty content as tool-wide rule", () => { + expect(permissionRuleValueFromString("Bash()")).toEqual({ + toolName: "Bash", + }); + }); + + test("treats wildcard content as tool-wide rule", () => { + expect(permissionRuleValueFromString("Bash(*)")).toEqual({ + toolName: "Bash", + }); + }); + + test("normalizes legacy tool names", () => { + const result = permissionRuleValueFromString("Task"); + expect(result.toolName).toBe("Agent"); + }); + + test("handles MCP-style tool names", () => { + expect(permissionRuleValueFromString("mcp__server__tool")).toEqual({ + toolName: "mcp__server__tool", + }); + }); +}); + +describe("permissionRuleValueToString", () => { + test("formats tool name only", () => { + expect(permissionRuleValueToString({ toolName: "Bash" })).toBe("Bash"); + }); + + test("formats tool name with content", () => { + expect( + permissionRuleValueToString({ + toolName: "Bash", + ruleContent: "npm install", + }) + ).toBe("Bash(npm install)"); + }); + + test("escapes parens in content", () => { + expect( + permissionRuleValueToString({ + toolName: "Bash", + ruleContent: 'python -c "print(1)"', + }) + ).toBe('Bash(python -c "print\\(1\\)")'); + }); + + test("roundtrips with permissionRuleValueFromString", () => { + const original = { toolName: "Bash", ruleContent: "npm install" }; + const str = permissionRuleValueToString(original); + const parsed = permissionRuleValueFromString(str); + expect(parsed).toEqual(original); + }); +}); + +describe("normalizeLegacyToolName", () => { + test("maps Task to Agent", () => { + expect(normalizeLegacyToolName("Task")).toBe("Agent"); + }); + + test("maps KillShell to TaskStop", () => { + expect(normalizeLegacyToolName("KillShell")).toBe("TaskStop"); + }); + + test("returns unknown name as-is", () => { + expect(normalizeLegacyToolName("UnknownTool")).toBe("UnknownTool"); + }); + + test("preserves current canonical names", () => { + expect(normalizeLegacyToolName("Bash")).toBe("Bash"); + expect(normalizeLegacyToolName("Agent")).toBe("Agent"); + }); +});