feat(app): M5.2 — customer-area route shells + role-gated nav
10 route shells under /[slug]/, role-filtered Nav, backstage stub at /__backstage__, dashboard reads session.products to render tiles. src/lib/session.ts is the canonical role × surface matrix; canSee() is the only RBAC primitive in the portal (real enforcement remains at the API layer). 24 vitest tests; 100% src/lib coverage. Refs: M5.2
This commit was merged in pull request #7.
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
import { describe, expect, test } from "vitest";
|
||||
import { canSee, hasAnyOrgRole, hasOrgRole, hasProduct } from "./session";
|
||||
import type { SessionWithExtras } from "./session";
|
||||
|
||||
function s(roles: SessionWithExtras["org_roles"], products: string[] = []): SessionWithExtras {
|
||||
return {
|
||||
user: { name: "Test", email: "t@x.test" },
|
||||
expires: "2099-01-01T00:00:00Z",
|
||||
org_roles: roles,
|
||||
products,
|
||||
};
|
||||
}
|
||||
|
||||
describe("hasOrgRole", () => {
|
||||
test("null session has no roles", () => {
|
||||
expect(hasOrgRole(null, "IT_ADMIN")).toBe(false);
|
||||
});
|
||||
test("matches single role", () => {
|
||||
expect(hasOrgRole(s(["CXO"]), "CXO")).toBe(true);
|
||||
expect(hasOrgRole(s(["CXO"]), "IT_ADMIN")).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("hasAnyOrgRole", () => {
|
||||
test("any match wins", () => {
|
||||
expect(hasAnyOrgRole(s(["LEGAL"]), ["IT_ADMIN", "LEGAL"])).toBe(true);
|
||||
expect(hasAnyOrgRole(s(["USER"]), ["IT_ADMIN", "CXO"])).toBe(false);
|
||||
});
|
||||
test("empty roles", () => {
|
||||
expect(hasAnyOrgRole(s(undefined), ["IT_ADMIN"])).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("hasProduct", () => {
|
||||
test("checks products array", () => {
|
||||
expect(hasProduct(s(["USER"], ["certifai"]), "certifai")).toBe(true);
|
||||
expect(hasProduct(s(["USER"], ["certifai"]), "compliance")).toBe(false);
|
||||
expect(hasProduct(null, "certifai")).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("canSee", () => {
|
||||
test("IT_ADMIN sees settings, USER does not", () => {
|
||||
expect(canSee(s(["IT_ADMIN"]), "settings")).toBe(true);
|
||||
expect(canSee(s(["USER"]), "settings")).toBe(false);
|
||||
});
|
||||
test("CXO can see billing", () => {
|
||||
expect(canSee(s(["CXO"]), "billing")).toBe(true);
|
||||
});
|
||||
test("LEGAL can see audit but not settings", () => {
|
||||
expect(canSee(s(["LEGAL"]), "audit")).toBe(true);
|
||||
expect(canSee(s(["LEGAL"]), "settings")).toBe(false);
|
||||
});
|
||||
test("FINANCE sees billing but not settings", () => {
|
||||
expect(canSee(s(["FINANCE"]), "billing")).toBe(true);
|
||||
expect(canSee(s(["FINANCE"]), "settings")).toBe(false);
|
||||
});
|
||||
test("dashboard visible to everyone with any role", () => {
|
||||
expect(canSee(s(["USER"]), "dashboard")).toBe(true);
|
||||
expect(canSee(s(["LEGAL"]), "dashboard")).toBe(true);
|
||||
});
|
||||
test("null session sees nothing", () => {
|
||||
expect(canSee(null, "dashboard")).toBe(false);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user