feat: add housekeeping capability context

This commit is contained in:
Simo committed 2026-08-25 19:16:29 +02:00
1 parent 7d62a04f0e
commit c63c9830b2
4 files changed
+180

No files matched your search

@@ -0,0 +1,89 @@
import { describe, expect, it, vi } from "vitest";
import type { PermissionSet } from "@/types/admin";
import {
createHousekeepingCapabilityContext,
satisfiesCapability,
} from "./capability-context";
import {
allCapabilities,
anyCapability,
type HousekeepingActor,
} from "./contracts";
const actor: HousekeepingActor = {
id: 42,
username: "operator",
rank: 7,
};
const permissionSet = (
slugs: readonly string[],
isSuperAdmin = false,
): PermissionSet => {
const granted = new Set(slugs);
const has = (slug: string) => isSuperAdmin || granted.has(slug);
return {
isSuperAdmin,
has,
hasAny: (...requested) => requested.some(has),
hasAll: (...requested) => requested.every(has),
};
};
describe("housekeeping capability context", () => {
it("evaluates any and all requirements from effective permission methods", () => {
const context = createHousekeepingCapabilityContext(
actor,
permissionSet(["admin.users.view", "admin.tickets.view"]),
);
expect(
satisfiesCapability(
context,
anyCapability("admin.users.view", "admin.logs.view"),
),
).toBe(true);
expect(
satisfiesCapability(
context,
allCapabilities("admin.users.view", "admin.logs.view"),
),
).toBe(false);
});
it("keeps the super administrator bypass explicit", () => {
const context = createHousekeepingCapabilityContext(
actor,
permissionSet([], true),
);
expect(context.isSuperAdmin).toBe(true);
expect(context.has("unknown.future.slug")).toBe(true);
expect(
satisfiesCapability(context, allCapabilities("unknown.future.slug")),
).toBe(true);
});
it("delegates capability checks without applying rank logic", () => {
const permissions: PermissionSet = {
isSuperAdmin: false,
has: vi.fn(() => false),
hasAny: vi.fn(() => true),
hasAll: vi.fn(() => false),
};
const context = createHousekeepingCapabilityContext(
{ ...actor, rank: 0 },
permissions,
);
expect(
satisfiesCapability(context, anyCapability("admin.users.view")),
).toBe(true);
expect(
satisfiesCapability(context, allCapabilities("admin.users.view")),
).toBe(false);
expect(permissions.hasAny).toHaveBeenCalledWith("admin.users.view");
expect(permissions.hasAll).toHaveBeenCalledWith("admin.users.view");
});
});
@@ -0,0 +1,30 @@
import type { PermissionSet } from "@/lib/permissions";
import type {
CapabilityRequirement,
HousekeepingActor,
HousekeepingCapabilityContext,
} from "./contracts";
export function createHousekeepingCapabilityContext(
actor: HousekeepingActor,
permissions: PermissionSet,
): HousekeepingCapabilityContext {
return {
actor,
isSuperAdmin: permissions.isSuperAdmin,
has: (slug) => permissions.has(slug),
hasAny: (...slugs) => permissions.hasAny(...slugs),
hasAll: (...slugs) => permissions.hasAll(...slugs),
};
}
export function satisfiesCapability(
context: HousekeepingCapabilityContext,
requirement: CapabilityRequirement,
): boolean {
if (context.isSuperAdmin) return true;
return requirement.mode === "any"
? context.hasAny(...requirement.slugs)
: context.hasAll(...requirement.slugs);
}
@@ -0,0 +1,45 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
const { getAdminContext } = vi.hoisted(() => ({ getAdminContext: vi.fn() }));
vi.mock("react", () => ({
cache: <T extends (...args: never[]) => unknown>(callback: T) => callback,
}));
vi.mock("@/lib/permissions", () => ({ getAdminContext }));
import { getHousekeepingCapabilityContext } from "./server-capability-context";
describe("getHousekeepingCapabilityContext", () => {
beforeEach(() => {
vi.clearAllMocks();
getAdminContext.mockResolvedValue({
session: {
user: {
id: 42,
username: "operator",
rank: 7,
},
},
permissions: {
isSuperAdmin: false,
has: (slug: string) => slug === "admin.users.view",
hasAny: (...slugs: string[]) => slugs.includes("admin.users.view"),
hasAll: (...slugs: string[]) =>
slugs.every((slug) => slug === "admin.users.view"),
},
});
});
it("uses the refreshed administrator actor and forwarded permissions", async () => {
const context = await getHousekeepingCapabilityContext();
expect(context).toMatchObject({
actor: { id: 42, username: "operator", rank: 7 },
isSuperAdmin: false,
});
expect(context.has("admin.users.view")).toBe(true);
expect(context.has("admin.logs.view")).toBe(false);
expect(getAdminContext).toHaveBeenCalledTimes(1);
});
});
@@ -0,0 +1,16 @@
import { cache } from "react";
import { getAdminContext } from "@/lib/permissions";
import { createHousekeepingCapabilityContext } from "./capability-context";
export const getHousekeepingCapabilityContext = cache(async () => {
const { session, permissions } = await getAdminContext();
return createHousekeepingCapabilityContext(
{
id: session.user.id,
username: session.user.username,
rank: session.user.rank,
},
permissions,
);
});