import { beforeEach, describe, expect, test } from "bun:test"; import type { PermissionStore } from "../src/store.ts"; /** * Every PermissionStore adapter must pass this suite, so the memory and db * implementations cannot drift apart. */ export function runStoreConformance(name: string, makeStore: () => Promise): void { describe(`PermissionStore conformance: ${name}`, () => { let store: PermissionStore; beforeEach(async () => { store = await makeStore(); }); test("an unknown subject has empty assignments", async () => { expect(await store.assignmentsFor("nobody")).toEqual({ roles: [], grants: [], denies: [], }); }); test("assignRole then assignmentsFor round-trips", async () => { await store.assignRole("u1", "editor"); expect((await store.assignmentsFor("u1")).roles).toEqual(["editor"]); }); test("assignRole is idempotent", async () => { await store.assignRole("u1", "editor"); await store.assignRole("u1", "editor"); expect((await store.assignmentsFor("u1")).roles).toEqual(["editor"]); }); test("revokeRole removes only that role", async () => { await store.assignRole("u1", "editor"); await store.assignRole("u1", "admin"); await store.revokeRole("u1", "editor"); expect((await store.assignmentsFor("u1")).roles).toEqual(["admin"]); }); test("revoking a role that was never assigned is a no-op", async () => { await store.revokeRole("u1", "ghost"); expect((await store.assignmentsFor("u1")).roles).toEqual([]); }); test("scoped assignments do not leak across tenants", async () => { await store.assignRole("u1", "editor", { tenantId: "t1" }); expect((await store.assignmentsFor("u1", { tenantId: "t1" })).roles).toEqual(["editor"]); expect((await store.assignmentsFor("u1", { tenantId: "t2" })).roles).toEqual([]); }); test("a global assignment is visible inside every tenant", async () => { await store.assignRole("u1", "superadmin"); expect((await store.assignmentsFor("u1", { tenantId: "t1" })).roles).toEqual(["superadmin"]); }); test("global and scoped roles union within a tenant", async () => { await store.assignRole("u1", "viewer"); await store.assignRole("u1", "editor", { tenantId: "t1" }); expect((await store.assignmentsFor("u1", { tenantId: "t1" })).roles.sort()).toEqual([ "editor", "viewer", ]); }); test("grant with allow and deny land in the right buckets", async () => { await store.grant("u1", "post:write", "allow"); await store.grant("u1", "post:delete", "deny"); const assignments = await store.assignmentsFor("u1"); expect(assignments.grants).toEqual(["post:write"]); expect(assignments.denies).toEqual(["post:delete"]); }); test("re-granting the same permission replaces its effect", async () => { await store.grant("u1", "post:write", "allow"); await store.grant("u1", "post:write", "deny"); const assignments = await store.assignmentsFor("u1"); expect(assignments.grants).toEqual([]); expect(assignments.denies).toEqual(["post:write"]); }); test("revokeGrant removes the permission entirely", async () => { await store.grant("u1", "post:write", "allow"); await store.revokeGrant("u1", "post:write"); expect((await store.assignmentsFor("u1")).grants).toEqual([]); }); test("a tenant-scoped grant does not leak into another tenant", async () => { await store.grant("u1", "post:write", "allow", { tenantId: "t1" }); expect((await store.assignmentsFor("u1", { tenantId: "t1" })).grants).toEqual(["post:write"]); expect((await store.assignmentsFor("u1", { tenantId: "t2" })).grants).toEqual([]); }); test("a tenant-scoped deny does not leak into another tenant", async () => { await store.grant("u1", "post:delete", "deny", { tenantId: "t1" }); expect((await store.assignmentsFor("u1", { tenantId: "t1" })).denies).toEqual([ "post:delete", ]); expect((await store.assignmentsFor("u1", { tenantId: "t2" })).denies).toEqual([]); }); test("a global grant is visible inside every tenant", async () => { await store.grant("u1", "post:publish", "allow"); expect((await store.assignmentsFor("u1", { tenantId: "t1" })).grants).toEqual([ "post:publish", ]); }); test("revokeGrant is scope-isolated: revoking a tenant-scoped grant leaves the global grant intact", async () => { await store.grant("u1", "post:write", "allow"); await store.grant("u1", "post:write", "allow", { tenantId: "t1" }); await store.revokeGrant("u1", "post:write", { tenantId: "t1" }); expect((await store.assignmentsFor("u1")).grants).toEqual(["post:write"]); expect((await store.assignmentsFor("u1", { tenantId: "t1" })).grants).toEqual(["post:write"]); }); test("revokeRole is scope-isolated: revoking a tenant-scoped role leaves the global role intact", async () => { await store.assignRole("u1", "editor"); await store.assignRole("u1", "editor", { tenantId: "t1" }); await store.revokeRole("u1", "editor", { tenantId: "t1" }); expect((await store.assignmentsFor("u1")).roles).toEqual(["editor"]); expect((await store.assignmentsFor("u1", { tenantId: "t1" })).roles).toEqual(["editor"]); }); test("listSubjects returns everyone with an assignment in scope", async () => { await store.assignRole("u1", "editor", { tenantId: "t1" }); await store.assignRole("u2", "editor", { tenantId: "t1" }); await store.assignRole("u3", "editor", { tenantId: "t2" }); expect((await store.listSubjects({ tenantId: "t1" })).sort()).toEqual(["u1", "u2"]); }); test("listSubjects with no scope returns global assignees only", async () => { await store.assignRole("g1", "viewer"); await store.assignRole("s1", "editor", { tenantId: "t1" }); expect(await store.listSubjects()).toEqual(["g1"]); }); test("listSubjects credits grant-only subjects", async () => { await store.grant("g1", "post:write", "allow", { tenantId: "t1" }); expect((await store.listSubjects({ tenantId: "t1" })).sort()).toEqual(["g1"]); }); }); }