Files
suaveplan/packages/identity/auth/permissions/tests/integration.test.ts
2026-05-27 23:47:53 +01:00

147 lines
5.0 KiB
TypeScript

import { describe, expect, it } from "@suaveplan/testing/runner";
import { MemoryAdapter } from "../src/adapters/memory/memory.js";
import { PermissionCache } from "../src/cache/permission-cache/permission-cache.js";
import { type Ability, buildAbility } from "../src/core/ability/ability.js";
import { PermissionManager } from "../src/core/permission-manager/permission-manager.js";
import { PolicyEngine } from "../src/core/policy-engine/policy-engine.js";
import { RoleManager } from "../src/core/role-manager/role-manager.js";
import { CircularInheritanceError } from "../src/errors/permission-error.js";
import {
createAdminRole,
createViewerRole,
} from "../src/presets/role-presets/role-presets.js";
import type { Permission } from "../src/types/permissions.types.js";
function makePerm(id: string, resource = "posts", action = "read"): Permission {
return { id, resource, action };
}
describe("Full permissions flow", () => {
it("role inheritance transitivity", { timeout: 5000 }, async () => {
const adapter = new MemoryAdapter();
const roleManager = new RoleManager(adapter);
const _manager = new PermissionManager(adapter);
const cPerm = makePerm("p-c", "comments", "read");
const bPerm = makePerm("p-b", "posts", "write");
const aPerm = makePerm("p-a", "posts", "read");
await adapter.createRole({ id: "c", name: "C", permissions: [cPerm] });
await adapter.createRole({
id: "b",
name: "B",
permissions: [bPerm],
inherits: ["c"],
});
await adapter.createRole({
id: "a",
name: "A",
permissions: [aPerm],
inherits: ["b"],
});
const perms = await roleManager.getEffectivePermissions("a");
const permIds = perms.map((p) => p.id);
expect(permIds).toContain("p-a");
expect(permIds).toContain("p-b");
expect(permIds).toContain("p-c");
});
it("policy engine with cache", { timeout: 5000 }, async () => {
const adapter = new MemoryAdapter();
await adapter.createRole({
id: "r1",
name: "R",
permissions: [makePerm("p1")],
});
await adapter.assignRole({
id: "ur1",
userId: "u1",
roleId: "r1",
assignedAt: new Date(),
});
const manager = new PermissionManager(adapter);
const cache = new PermissionCache({ ttl: 60_000 });
const engine = new PolicyEngine(manager, { cache });
const r1 = await engine.evaluate("u1", "posts", "read");
const r2 = await engine.evaluate("u1", "posts", "read");
expect(r1.allowed).toBe(true);
expect(r2.matchedRule).toBe("cached-allow");
// Invalidate cache and re-check
cache.invalidate("u1");
const r3 = await engine.evaluate("u1", "posts", "read");
expect(r3.allowed).toBe(true);
expect(r3.matchedRule).toBe("posts:read");
});
it("circular inheritance detection", { timeout: 5000 }, async () => {
const adapter = new MemoryAdapter();
const roleManager = new RoleManager(adapter);
await adapter.createRole({
id: "x",
name: "X",
permissions: [],
inherits: ["y"],
});
await adapter.createRole({
id: "y",
name: "Y",
permissions: [],
inherits: ["x"],
});
await expect(
roleManager.getRoleInheritanceChain("x")
).rejects.toBeInstanceOf(CircularInheritanceError);
});
it("preset roles work with PermissionManager", {
timeout: 5000,
}, async () => {
const adapter = new MemoryAdapter();
await adapter.createRole(createAdminRole());
await adapter.assignRole({
id: "ur-admin",
userId: "admin-user",
roleId: "admin",
assignedAt: new Date(),
});
await adapter.createRole(createViewerRole());
await adapter.assignRole({
id: "ur-viewer",
userId: "viewer-user",
roleId: "viewer",
assignedAt: new Date(),
});
const manager = new PermissionManager(adapter);
expect(await manager.hasPermission("admin-user", "any", "any")).toBe(
true
);
expect(
await manager.hasPermission("viewer-user", "posts", "read")
).toBe(true);
expect(
await manager.hasPermission("viewer-user", "posts", "delete")
).toBe(false);
});
});
describe("Ability system end-to-end", () => {
it("can/cannot consistency", { timeout: 5000 }, () => {
const ability: Ability = buildAbility((b) => {
b.can("read", "posts");
b.cannot("delete", "posts");
});
expect(ability.can("read", "posts")).toBe(true);
expect(ability.cannot("read", "posts")).toBe(false);
expect(ability.can("delete", "posts")).toBe(false);
expect(ability.cannot("delete", "posts")).toBe(true);
});
});