import { describe, expect, test } from "bun:test"; import { defineRbac } from "../src/index.ts"; describe("RBAC namespace wildcards", () => { const rbac = defineRbac({ admin: ["*"], editor: ["post:*"], moderator: ["post:comment:*"], reader: ["post:read"], }); test("a wildcard grants every depth beneath it", () => { expect(rbac.can({ roles: ["editor"] }, "post:write")).toBe(true); expect(rbac.can({ roles: ["editor"] }, "post:comment:delete")).toBe(true); expect(rbac.can({ roles: ["editor"] }, "post:comment:flag:undo")).toBe(true); }); test("a deeper wildcard grants its own subtree", () => { expect(rbac.can({ roles: ["moderator"] }, "post:comment:delete")).toBe(true); expect(rbac.can({ roles: ["moderator"] }, "post:comment:flag:undo")).toBe(true); }); test("a wildcard does not leak sideways or upward", () => { expect(rbac.can({ roles: ["moderator"] }, "post:write")).toBe(false); expect(rbac.can({ roles: ["moderator"] }, "post")).toBe(false); expect(rbac.can({ roles: ["editor"] }, "page:write")).toBe(false); expect(rbac.can({ roles: ["reader"] }, "post:write")).toBe(false); }); test("root wildcard and unknown subjects behave", () => { expect(rbac.can({ roles: ["admin"] }, "anything:at:all")).toBe(true); expect(rbac.can({ roles: [] }, "post:read")).toBe(false); expect(rbac.can(undefined, "post:read")).toBe(false); }); test("role inheritance terminates on cycles", () => { const cyclic = defineRbac({ a: ["role:b", "p:a"], b: ["role:a", "p:b"] }); expect([...cyclic.permissionsFor(["a"])].sort()).toEqual(["p:a", "p:b"]); }); });