PermissionMapper.test.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320
  1. /**
  2. * Unit tests for PermissionMapper
  3. *
  4. * Tests permission translation between OAC granular and binary formats.
  5. */
  6. import { describe, it, expect } from "vitest";
  7. import {
  8. resolvePermissionRule,
  9. isGranularRule,
  10. mapPermissionsFromOAC,
  11. mapPermissionsToOAC,
  12. createGranularRule,
  13. extractPatterns,
  14. mergePermissionRules,
  15. hasGranularPermissions,
  16. hasAskPermissions,
  17. analyzePermissionDegradation,
  18. } from "../../../src/mappers/PermissionMapper";
  19. describe("PermissionMapper", () => {
  20. // ==========================================================================
  21. // resolvePermissionRule
  22. // ==========================================================================
  23. describe("resolvePermissionRule()", () => {
  24. describe("boolean rules", () => {
  25. it("returns true for true", () => {
  26. expect(resolvePermissionRule(true)).toBe(true);
  27. });
  28. it("returns false for false", () => {
  29. expect(resolvePermissionRule(false)).toBe(false);
  30. });
  31. });
  32. describe("literal rules", () => {
  33. it("returns true for 'allow'", () => {
  34. expect(resolvePermissionRule("allow")).toBe(true);
  35. });
  36. it("returns false for 'deny'", () => {
  37. expect(resolvePermissionRule("deny")).toBe(false);
  38. });
  39. it("returns true for 'ask' with permissive strategy", () => {
  40. expect(resolvePermissionRule("ask", "permissive")).toBe(true);
  41. });
  42. it("returns false for 'ask' with restrictive strategy", () => {
  43. expect(resolvePermissionRule("ask", "restrictive")).toBe(false);
  44. });
  45. it("returns false for 'ask' with ask-as-deny strategy", () => {
  46. expect(resolvePermissionRule("ask", "ask-as-deny")).toBe(false);
  47. });
  48. });
  49. describe("granular rules (records)", () => {
  50. it("returns true if any allow exists with permissive strategy", () => {
  51. const rule = { "*.ts": "allow" as const, "*.js": "deny" as const };
  52. expect(resolvePermissionRule(rule, "permissive")).toBe(true);
  53. });
  54. it("returns false if no allow and has deny with restrictive strategy", () => {
  55. const rule = { "*.ts": "deny" as const };
  56. expect(resolvePermissionRule(rule, "restrictive")).toBe(false);
  57. });
  58. it("returns true if allow and no deny with restrictive strategy", () => {
  59. const rule = { "*.ts": "allow" as const };
  60. expect(resolvePermissionRule(rule, "restrictive")).toBe(true);
  61. });
  62. });
  63. });
  64. // ==========================================================================
  65. // isGranularRule
  66. // ==========================================================================
  67. describe("isGranularRule()", () => {
  68. it("returns false for boolean", () => {
  69. expect(isGranularRule(true)).toBe(false);
  70. expect(isGranularRule(false)).toBe(false);
  71. });
  72. it("returns false for literal strings", () => {
  73. expect(isGranularRule("allow")).toBe(false);
  74. expect(isGranularRule("deny")).toBe(false);
  75. expect(isGranularRule("ask")).toBe(false);
  76. });
  77. it("returns true for object rules", () => {
  78. expect(isGranularRule({ "*.ts": "allow" })).toBe(true);
  79. expect(isGranularRule({ "*": "deny" })).toBe(true);
  80. });
  81. });
  82. // ==========================================================================
  83. // mapPermissionsFromOAC
  84. // ==========================================================================
  85. describe("mapPermissionsFromOAC()", () => {
  86. it("converts simple allow/deny to binary", () => {
  87. const permissions = { bash: "allow" as const, write: "deny" as const };
  88. const result = mapPermissionsFromOAC(permissions, "claude");
  89. expect(result.permissions).toEqual({ bash: true, write: false });
  90. expect(result.warnings).toHaveLength(0);
  91. });
  92. it("generates warnings for granular permissions", () => {
  93. const permissions = {
  94. bash: { "rm *": "deny" as const, "*": "allow" as const },
  95. };
  96. const result = mapPermissionsFromOAC(permissions, "claude");
  97. expect(result.warnings.length).toBeGreaterThan(0);
  98. expect(result.warnings[0]).toContain("Granular");
  99. });
  100. it("generates warnings for 'ask' permissions", () => {
  101. const permissions = { bash: "ask" as const };
  102. const result = mapPermissionsFromOAC(permissions, "claude");
  103. expect(result.warnings.length).toBeGreaterThan(0);
  104. expect(result.warnings[0]).toContain("ask");
  105. });
  106. it("respects degradation strategy for 'ask'", () => {
  107. const permissions = { bash: "ask" as const };
  108. const permissive = mapPermissionsFromOAC(permissions, "claude", "permissive");
  109. expect(permissive.permissions).toEqual({ bash: true });
  110. const restrictive = mapPermissionsFromOAC(permissions, "claude", "restrictive");
  111. expect(restrictive.permissions).toEqual({ bash: false });
  112. });
  113. });
  114. // ==========================================================================
  115. // mapPermissionsToOAC
  116. // ==========================================================================
  117. describe("mapPermissionsToOAC()", () => {
  118. it("converts binary to allow/deny", () => {
  119. const permissions = { bash: true, write: false };
  120. const result = mapPermissionsToOAC(permissions, "claude");
  121. expect(result.permissions).toEqual({ bash: "allow", write: "deny" });
  122. expect(result.warnings).toHaveLength(0);
  123. });
  124. it("skips undefined permissions", () => {
  125. const permissions = { bash: true, write: undefined };
  126. const result = mapPermissionsToOAC(permissions, "claude");
  127. expect(result.permissions).toEqual({ bash: "allow" });
  128. });
  129. });
  130. // ==========================================================================
  131. // createGranularRule
  132. // ==========================================================================
  133. describe("createGranularRule()", () => {
  134. it("creates rule from allow patterns", () => {
  135. const rule = createGranularRule(["*.ts", "*.tsx"]);
  136. expect(rule).toEqual({ "*.ts": "allow", "*.tsx": "allow" });
  137. });
  138. it("creates rule from allow and deny patterns", () => {
  139. const rule = createGranularRule(["*"], ["node_modules/**"]);
  140. expect(rule).toEqual({ "*": "allow", "node_modules/**": "deny" });
  141. });
  142. it("creates rule with ask patterns", () => {
  143. const rule = createGranularRule([], [], ["package.json"]);
  144. expect(rule).toEqual({ "package.json": "ask" });
  145. });
  146. it("creates combined rule", () => {
  147. const rule = createGranularRule(["src/**"], ["dist/**"], ["*.config.js"]);
  148. expect(rule).toEqual({
  149. "src/**": "allow",
  150. "dist/**": "deny",
  151. "*.config.js": "ask",
  152. });
  153. });
  154. });
  155. // ==========================================================================
  156. // extractPatterns
  157. // ==========================================================================
  158. describe("extractPatterns()", () => {
  159. it("extracts patterns from boolean", () => {
  160. expect(extractPatterns(true)).toEqual({
  161. allow: ["*"],
  162. deny: [],
  163. ask: [],
  164. });
  165. expect(extractPatterns(false)).toEqual({
  166. allow: [],
  167. deny: ["*"],
  168. ask: [],
  169. });
  170. });
  171. it("extracts patterns from literals", () => {
  172. expect(extractPatterns("allow")).toEqual({
  173. allow: ["*"],
  174. deny: [],
  175. ask: [],
  176. });
  177. expect(extractPatterns("deny")).toEqual({
  178. allow: [],
  179. deny: ["*"],
  180. ask: [],
  181. });
  182. expect(extractPatterns("ask")).toEqual({
  183. allow: [],
  184. deny: [],
  185. ask: ["*"],
  186. });
  187. });
  188. it("extracts patterns from granular rule", () => {
  189. const rule = { "*.ts": "allow" as const, "*.js": "deny" as const, "*.config": "ask" as const };
  190. const result = extractPatterns(rule);
  191. expect(result.allow).toContain("*.ts");
  192. expect(result.deny).toContain("*.js");
  193. expect(result.ask).toContain("*.config");
  194. });
  195. });
  196. // ==========================================================================
  197. // mergePermissionRules
  198. // ==========================================================================
  199. describe("mergePermissionRules()", () => {
  200. it("merges multiple rules", () => {
  201. const rule1 = { "*.ts": "allow" as const };
  202. const rule2 = { "*.js": "deny" as const };
  203. const merged = mergePermissionRules(rule1, rule2);
  204. expect(merged).toEqual({ "*.ts": "allow", "*.js": "deny" });
  205. });
  206. it("later rules override earlier ones", () => {
  207. const rule1 = { "*": "allow" as const };
  208. const rule2 = { "*": "deny" as const };
  209. const merged = mergePermissionRules(rule1, rule2);
  210. expect(merged).toBe("deny"); // Simplified to single value
  211. });
  212. it("merges boolean and literal rules", () => {
  213. const merged = mergePermissionRules(true, "deny");
  214. expect(merged).toBe("deny");
  215. });
  216. });
  217. // ==========================================================================
  218. // hasGranularPermissions
  219. // ==========================================================================
  220. describe("hasGranularPermissions()", () => {
  221. it("returns false for simple permissions", () => {
  222. const permissions = { bash: "allow" as const, write: true };
  223. expect(hasGranularPermissions(permissions)).toBe(false);
  224. });
  225. it("returns true for granular permissions", () => {
  226. const permissions = { bash: { "*": "allow" as const } };
  227. expect(hasGranularPermissions(permissions)).toBe(true);
  228. });
  229. });
  230. // ==========================================================================
  231. // hasAskPermissions
  232. // ==========================================================================
  233. describe("hasAskPermissions()", () => {
  234. it("returns false when no ask permissions", () => {
  235. const permissions = { bash: "allow" as const, write: "deny" as const };
  236. expect(hasAskPermissions(permissions)).toBe(false);
  237. });
  238. it("returns true for top-level ask", () => {
  239. const permissions = { bash: "ask" as const };
  240. expect(hasAskPermissions(permissions)).toBe(true);
  241. });
  242. it("returns true for nested ask", () => {
  243. const permissions = { bash: { "rm *": "ask" as const } };
  244. expect(hasAskPermissions(permissions)).toBe(true);
  245. });
  246. });
  247. // ==========================================================================
  248. // analyzePermissionDegradation
  249. // ==========================================================================
  250. describe("analyzePermissionDegradation()", () => {
  251. it("returns empty warnings for simple permissions", () => {
  252. const permissions = { bash: "allow" as const };
  253. const warnings = analyzePermissionDegradation(permissions, "claude");
  254. expect(warnings).toHaveLength(0);
  255. });
  256. it("warns about granular permission degradation", () => {
  257. const permissions = { bash: { "*": "allow" as const } };
  258. const warnings = analyzePermissionDegradation(permissions, "claude");
  259. expect(warnings.length).toBeGreaterThan(0);
  260. expect(warnings[0]).toContain("granular");
  261. });
  262. it("warns about ask permission conversion", () => {
  263. const permissions = { bash: "ask" as const };
  264. const warnings = analyzePermissionDegradation(permissions, "claude");
  265. expect(warnings.length).toBeGreaterThan(0);
  266. expect(warnings[0]).toContain("ask");
  267. });
  268. });
  269. });