import { describe, expect, it } from "vitest"; import { AUTHORIZATION_ALLOWED, AUTHORIZATION_DENIED, type AuthorizationActor, type AuthorizationDecision, createUserActor, type GrantId, isAuthorizationAllowed, isUserActor, parseAuthorizationScope, parseGrantId, parsePermissionDeclaration, parsePermissionId, type PermissionDeclaration, type PermissionId, safeParseAuthorizationScope, safeParseGrantId, safeParsePermissionDeclaration, safeParsePermissionId, SYSTEM_ACTOR, } from "../src/index.js"; describe("PermissionId", () => { it("parses a valid lowercase dot-namespaced permission id", () => { expect(parsePermissionId("calendar.events.read")).toBe("calendar.events.read"); }); it("is opaque at the type level: a plain string is not assignable without parsing", () => { // @ts-expect-error a bare string must not satisfy the branded PermissionId type. const notAllowed: PermissionId = "calendar.events.read"; expect(notAllowed).toBe("calendar.events.read"); }); it("rejects a non-namespaced, empty, oversized, or non-string permission id", () => { expect(() => parsePermissionId("read")).toThrow(); expect(() => parsePermissionId("")).toThrow(); expect(() => parsePermissionId("Calendar.Events.Read")).toThrow(); expect(() => parsePermissionId("x".repeat(161))).toThrow(); expect(() => parsePermissionId(42)).toThrow(); expect(() => parsePermissionId(null)).toThrow(); }); it("rejects a wildcard segment: there is no such thing as a wildcard permission", () => { expect(() => parsePermissionId("media.*")).toThrow(); expect(() => parsePermissionId("media.*.read")).toThrow(); expect(() => parsePermissionId("*")).toThrow(); }); it("exposes a safe-parse variant that never throws", () => { expect(safeParsePermissionId("calendar.events.read").success).toBe(true); expect(safeParsePermissionId("*").success).toBe(false); }); }); describe("PermissionDeclaration", () => { function validDeclaration(): Record { return { id: "calendar.events.read", baseline: "restricted", description: "Read calendar events.", }; } it.each([ "public", "authenticated", "restricted", ] as const)("parses a well-formed declaration with baseline %s", (baseline) => { const declaration = parsePermissionDeclaration({ ...validDeclaration(), baseline }); expect(declaration).toEqual({ id: "calendar.events.read", baseline, description: "Read calendar events.", }); }); it("is exactly three fields — this is the one shape both a module manifest and Core's registry parse into", () => { const declaration: PermissionDeclaration = parsePermissionDeclaration(validDeclaration()); expect(Object.keys(declaration).sort()).toEqual(["baseline", "description", "id"]); }); it("rejects an unknown declaration property", () => { const result = safeParsePermissionDeclaration({ ...validDeclaration(), scope: "calendar-event:123", }); expect(result.success).toBe(false); }); it("rejects a missing baseline", () => { const { baseline: _baseline, ...withoutBaseline } = validDeclaration(); expect(safeParsePermissionDeclaration(withoutBaseline).success).toBe(false); }); it("rejects an invalid baseline", () => { expect( safeParsePermissionDeclaration({ ...validDeclaration(), baseline: "superuser" }).success, ).toBe(false); }); it("rejects a missing, empty, or whitespace-only description", () => { const { description: _description, ...withoutDescription } = validDeclaration(); expect(safeParsePermissionDeclaration(withoutDescription).success).toBe(false); expect(safeParsePermissionDeclaration({ ...validDeclaration(), description: "" }).success).toBe( false, ); expect( safeParsePermissionDeclaration({ ...validDeclaration(), description: " " }).success, ).toBe(false); }); it("rejects a description beyond the fixed maximum length", () => { expect( safeParsePermissionDeclaration({ ...validDeclaration(), description: "x".repeat(240) }) .success, ).toBe(true); expect( safeParsePermissionDeclaration({ ...validDeclaration(), description: "x".repeat(241) }) .success, ).toBe(false); }); it("rejects a malformed permission id", () => { expect( safeParsePermissionDeclaration({ ...validDeclaration(), id: "not-namespaced" }).success, ).toBe(false); }); it("never registers or evaluates anything — parsing only proves the value is well-formed", () => { // Nothing here consults a registry: `parsePermissionDeclaration` succeeding says nothing // about whether `calendar.events.read` is actually registered anywhere. expect(() => parsePermissionDeclaration(validDeclaration())).not.toThrow(); }); }); describe("GrantId", () => { it("parses exactly the canonical lowercase hyphenated UUID PostgreSQL emits", () => { const id: GrantId = parseGrantId("11111111-1111-1111-1111-111111111111"); expect(id).toBe("11111111-1111-1111-1111-111111111111"); expect(parseGrantId("0b36b1b6-9a29-4c4e-9e2d-4c0e4c5e6f70")).toBe( "0b36b1b6-9a29-4c4e-9e2d-4c0e4c5e6f70", ); }); it("is opaque at the type level: a plain string is not assignable without parsing", () => { // @ts-expect-error a bare string must not satisfy the branded GrantId type. const notAllowed: GrantId = "11111111-1111-1111-1111-111111111111"; expect(notAllowed).toBe("11111111-1111-1111-1111-111111111111"); }); it("rejects every non-canonical spelling rather than repairing it", () => { for (const value of [ "", "abc", "not-a-uuid", // Uppercase is rejected, never folded, so exactly one spelling of any grant is valid. "0B36B1B6-9A29-4C4E-9E2D-4C0E4C5E6F70", // Whitespace padding is malformed input, not something to silently trim: trimming would let // two spellings of the same id reach the store. " 0b36b1b6-9a29-4c4e-9e2d-4c0e4c5e6f70", "0b36b1b6-9a29-4c4e-9e2d-4c0e4c5e6f70 ", "\t0b36b1b6-9a29-4c4e-9e2d-4c0e4c5e6f70\n", // Shortened, overlong, unhyphenated, wrong grouping, and non-hex. "0b36b1b6-9a29-4c4e-9e2d-4c0e4c5e6f7", "0b36b1b6-9a29-4c4e-9e2d-4c0e4c5e6f700", "0b36b1b69a294c4e9e2d4c0e4c5e6f70", "0b36b1b6-9a294-c4e-9e2d-4c0e4c5e6f70", "0g36b1b6-9a29-4c4e-9e2d-4c0e4c5e6f70", "0b36b1b6-9a29-4c4e-9e2d-4c0e4c5e6f70\n0b36b1b6-9a29-4c4e-9e2d-4c0e4c5e6f71", "x".repeat(129), ]) { expect(safeParseGrantId(value).success, `expected ${JSON.stringify(value)} rejected`).toBe( false, ); expect(() => parseGrantId(value)).toThrow(); } for (const value of [42, null, undefined, {}, []]) { expect(safeParseGrantId(value).success).toBe(false); } }); it("exposes a safe-parse variant that never throws", () => { expect(safeParseGrantId("11111111-1111-1111-1111-111111111111").success).toBe(true); expect(safeParseGrantId("").success).toBe(false); }); }); describe("AuthorizationScope", () => { it("accepts a non-empty opaque scope and rejects empty/whitespace/oversized scope", () => { expect(parseAuthorizationScope("calendar-event:123")).toBe("calendar-event:123"); expect(safeParseAuthorizationScope("").success).toBe(false); expect(safeParseAuthorizationScope(" ").success).toBe(false); expect(safeParseAuthorizationScope("x".repeat(257)).success).toBe(false); }); }); describe("AuthorizationActor", () => { it("creates a frozen user actor carrying only the opaque user id", () => { const actor = createUserActor("user_123"); expect(actor).toEqual({ type: "user", userId: "user_123" }); expect(Object.isFrozen(actor)).toBe(true); expect(isUserActor(actor)).toBe(true); }); it("rejects an invalid user id when creating a user actor", () => { expect(() => createUserActor("")).toThrow(); expect(() => createUserActor(42)).toThrow(); }); it("exposes a frozen system actor singleton distinguishable from a user actor", () => { expect(SYSTEM_ACTOR).toEqual({ type: "system" }); expect(Object.isFrozen(SYSTEM_ACTOR)).toBe(true); expect(isUserActor(SYSTEM_ACTOR)).toBe(false); }); it("discriminates actors on type without leaking extra fields", () => { const actors: AuthorizationActor[] = [SYSTEM_ACTOR, createUserActor("user_123")]; for (const actor of actors) { expect(["user", "system"]).toContain(actor.type); } expect(Object.keys(SYSTEM_ACTOR)).toEqual(["type"]); }); }); describe("AuthorizationDecision", () => { it("exposes exactly two frozen, minimal decision singletons", () => { expect(AUTHORIZATION_ALLOWED).toEqual({ allowed: true }); expect(AUTHORIZATION_DENIED).toEqual({ allowed: false }); expect(Object.isFrozen(AUTHORIZATION_ALLOWED)).toBe(true); expect(Object.isFrozen(AUTHORIZATION_DENIED)).toBe(true); expect(Object.keys(AUTHORIZATION_ALLOWED)).toEqual(["allowed"]); expect(Object.keys(AUTHORIZATION_DENIED)).toEqual(["allowed"]); }); it("never carries a reason, matched grant, permission echo, or any other detail", () => { const decisions: AuthorizationDecision[] = [AUTHORIZATION_ALLOWED, AUTHORIZATION_DENIED]; for (const decision of decisions) { expect(Object.keys(decision)).not.toContain("reason"); expect(Object.keys(decision)).not.toContain("grantId"); expect(Object.keys(decision)).not.toContain("permission"); expect(Object.keys(decision)).not.toContain("code"); } }); it("exposes a boolean helper matching the allowed field", () => { expect(isAuthorizationAllowed(AUTHORIZATION_ALLOWED)).toBe(true); expect(isAuthorizationAllowed(AUTHORIZATION_DENIED)).toBe(false); }); });