feat(authz): merge declaration modules into a frozen catalog
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import { defineAuthz } from "../src/registry.ts";
|
||||
import { emptyCatalog, mergeCatalogs } from "../src/catalog.ts";
|
||||
|
||||
describe("mergeCatalogs", () => {
|
||||
test("merges disjoint modules", () => {
|
||||
const catalog = mergeCatalogs([
|
||||
{ source: "a.ts", module: defineAuthz({ permissions: { "post:read": {} } }) },
|
||||
{ source: "b.ts", module: defineAuthz({ permissions: { "user:read": {} } }) },
|
||||
]);
|
||||
expect([...catalog.permissions.keys()].sort()).toEqual(["post:read", "user:read"]);
|
||||
});
|
||||
|
||||
test("re-declaring a permission with deep-equal metadata is a no-op", () => {
|
||||
const meta = { title: "View posts", risk: "low" as const };
|
||||
const catalog = mergeCatalogs([
|
||||
{ source: "a.ts", module: defineAuthz({ permissions: { "post:read": meta } }) },
|
||||
{ source: "b.ts", module: defineAuthz({ permissions: { "post:read": { ...meta } } }) },
|
||||
]);
|
||||
expect(catalog.permissions.size).toBe(1);
|
||||
});
|
||||
|
||||
test("conflicting metadata is a boot error naming both files", () => {
|
||||
expect(() =>
|
||||
mergeCatalogs([
|
||||
{ source: "a.ts", module: defineAuthz({ permissions: { "post:read": { risk: "low" } } }) },
|
||||
{ source: "b.ts", module: defineAuthz({ permissions: { "post:read": { risk: "high" } } }) },
|
||||
]),
|
||||
).toThrow(/a\.ts.*b\.ts|b\.ts.*a\.ts/s);
|
||||
});
|
||||
|
||||
test("conflicting role definitions are a boot error", () => {
|
||||
expect(() =>
|
||||
mergeCatalogs([
|
||||
{ source: "a.ts", module: defineAuthz({ roles: { editor: ["post:read"] } }) },
|
||||
{ source: "b.ts", module: defineAuthz({ roles: { editor: ["post:write"] } }) },
|
||||
]),
|
||||
).toThrow(/editor/);
|
||||
});
|
||||
|
||||
test("bindings for the same permission union across modules", () => {
|
||||
const p1 = defineAuthz({
|
||||
permissions: { "post:write": {} },
|
||||
policies: { ownsPost: async () => ({ allowed: true }) },
|
||||
bindings: { "post:write": ["ownsPost"] },
|
||||
});
|
||||
const p2 = defineAuthz({
|
||||
policies: { notLocked: async () => ({ allowed: true }) },
|
||||
bindings: { "post:write": ["notLocked"] },
|
||||
});
|
||||
const catalog = mergeCatalogs([
|
||||
{ source: "a.ts", module: p1 },
|
||||
{ source: "b.ts", module: p2 },
|
||||
]);
|
||||
expect([...catalog.bindings.get("post:write")!].sort()).toEqual(["notLocked", "ownsPost"]);
|
||||
});
|
||||
|
||||
test("a binding referencing a policy no module declares is a boot error", () => {
|
||||
expect(() =>
|
||||
mergeCatalogs([
|
||||
{
|
||||
source: "a.ts",
|
||||
module: { permissions: { "post:write": {} }, bindings: { "post:write": ["ghost"] } },
|
||||
},
|
||||
]),
|
||||
).toThrow(/ghost/);
|
||||
});
|
||||
|
||||
test("the merged catalog is frozen", () => {
|
||||
const catalog = mergeCatalogs([]);
|
||||
expect(() => (catalog.permissions as Map<string, never>).set("x:y", {} as never)).toThrow();
|
||||
});
|
||||
|
||||
test("emptyCatalog has no entries", () => {
|
||||
expect(emptyCatalog().permissions.size).toBe(0);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user