pelagia-portal/App/tests/unit/permissions.test.ts
Hardik da2d856b73 feat(po): submitter view-all of POs + History + export (feature-flagged)
Gated behind NEXT_PUBLIC_SUBMITTER_VIEW_ALL_ENABLED (opt-in, "true").
When on, submitter roles (TECHNICAL/MANNING) get read-only access to every
PO: the History page + report export, any other user's PO detail page, and
the per-PO Export PDF/XLSX buttons. No approval/payment/edit rights are added.

- lib/feature-flags.ts: SUBMITTER_VIEW_ALL_ENABLED flag
- lib/permissions.ts: isSubmitterRole / submitterCanViewAll / canViewAllPos
- po/[id] page + export route: gate via canViewAllPos
- history page + reports/export route: OR submitterCanViewAll into export_reports
- sidebar: show History to submitters when flag on
- tests: permission helpers, both flag states
- docs: .env.example, CLAUDE.md (wiki updated separately)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 04:57:11 +05:30

179 lines
6.6 KiB
TypeScript

import { describe, it, expect, vi, afterEach } from "vitest";
import {
hasPermission,
requirePermission,
isSubmitterRole,
submitterCanViewAll,
canViewAllPos,
} from "@/lib/permissions";
describe("Permissions", () => {
describe("hasPermission", () => {
// ── Original cases ─────────────────────────────────────────────────────
it("TECHNICAL can create POs", () => {
expect(hasPermission("TECHNICAL", "create_po")).toBe(true);
});
it("TECHNICAL cannot approve POs", () => {
expect(hasPermission("TECHNICAL", "approve_po")).toBe(false);
});
it("MANAGER can approve POs", () => {
expect(hasPermission("MANAGER", "approve_po")).toBe(true);
});
// MANAGER was intentionally granted process_payment in commit e1340b9
// ("chore(perm): manager permissions fix 2").
it("MANAGER can process payment", () => {
expect(hasPermission("MANAGER", "process_payment")).toBe(true);
});
it("ACCOUNTS can process payment", () => {
expect(hasPermission("ACCOUNTS", "process_payment")).toBe(true);
});
it("SUPERUSER has all operational permissions", () => {
expect(hasPermission("SUPERUSER", "create_po")).toBe(true);
expect(hasPermission("SUPERUSER", "approve_po")).toBe(true);
expect(hasPermission("SUPERUSER", "process_payment")).toBe(true);
expect(hasPermission("SUPERUSER", "confirm_receipt")).toBe(true);
});
it("ADMIN can manage users", () => {
expect(hasPermission("ADMIN", "manage_users")).toBe(true);
});
it("AUDITOR has read-only access", () => {
expect(hasPermission("AUDITOR", "view_all_pos")).toBe(true);
expect(hasPermission("AUDITOR", "approve_po")).toBe(false);
expect(hasPermission("AUDITOR", "create_po")).toBe(false);
});
// ── New permissions: MANAGER and ACCOUNTS expansions ──────────────────
it("MANAGER can create POs", () => {
expect(hasPermission("MANAGER", "create_po")).toBe(true);
});
it("MANAGER can submit POs", () => {
expect(hasPermission("MANAGER", "submit_po")).toBe(true);
});
it("MANAGER can manage vendors", () => {
expect(hasPermission("MANAGER", "manage_vendors")).toBe(true);
});
it("ACCOUNTS can manage vendors", () => {
expect(hasPermission("ACCOUNTS", "manage_vendors")).toBe(true);
});
it("ACCOUNTS cannot create POs", () => {
expect(hasPermission("ACCOUNTS", "create_po")).toBe(false);
});
it("ACCOUNTS cannot approve POs", () => {
expect(hasPermission("ACCOUNTS", "approve_po")).toBe(false);
});
it("TECHNICAL cannot manage vendors", () => {
expect(hasPermission("TECHNICAL", "manage_vendors")).toBe(false);
});
it("MANNING cannot manage vendors", () => {
expect(hasPermission("MANNING", "manage_vendors")).toBe(false);
});
it("AUDITOR cannot create, submit, or approve POs", () => {
expect(hasPermission("AUDITOR", "create_po")).toBe(false);
expect(hasPermission("AUDITOR", "submit_po")).toBe(false);
expect(hasPermission("AUDITOR", "approve_po")).toBe(false);
});
it("AUDITOR cannot manage vendors or products", () => {
expect(hasPermission("AUDITOR", "manage_vendors")).toBe(false);
expect(hasPermission("AUDITOR", "manage_products")).toBe(false);
});
it("ADMIN cannot approve or process payments", () => {
expect(hasPermission("ADMIN", "approve_po")).toBe(false);
expect(hasPermission("ADMIN", "process_payment")).toBe(false);
});
it("SUPERUSER does not have manage_vendors (admin-only permission)", () => {
expect(hasPermission("SUPERUSER", "manage_vendors")).toBe(false);
});
});
// ── Submitter view-all (feature-flagged) ──────────────────────────────────
describe("isSubmitterRole", () => {
it("is true for the two submitter roles", () => {
expect(isSubmitterRole("TECHNICAL")).toBe(true);
expect(isSubmitterRole("MANNING")).toBe(true);
});
it("is false for every other role", () => {
for (const role of ["ACCOUNTS", "MANAGER", "SUPERUSER", "AUDITOR", "ADMIN"] as const) {
expect(isSubmitterRole(role)).toBe(false);
}
});
});
describe("canViewAllPos / submitterCanViewAll — flag OFF (default)", () => {
it("submitters cannot view all POs", () => {
expect(canViewAllPos("TECHNICAL")).toBe(false);
expect(canViewAllPos("MANNING")).toBe(false);
expect(submitterCanViewAll("TECHNICAL")).toBe(false);
});
it("view_all_pos holders can still view all POs", () => {
for (const role of ["ACCOUNTS", "MANAGER", "SUPERUSER", "AUDITOR", "ADMIN"] as const) {
expect(canViewAllPos(role)).toBe(true);
}
});
});
describe("canViewAllPos / submitterCanViewAll — flag ON", () => {
afterEach(() => {
vi.unstubAllEnvs();
vi.resetModules();
});
it("submitters gain view-all when NEXT_PUBLIC_SUBMITTER_VIEW_ALL_ENABLED=true", async () => {
vi.resetModules();
vi.stubEnv("NEXT_PUBLIC_SUBMITTER_VIEW_ALL_ENABLED", "true");
const perms = await import("@/lib/permissions");
expect(perms.submitterCanViewAll("TECHNICAL")).toBe(true);
expect(perms.submitterCanViewAll("MANNING")).toBe(true);
expect(perms.canViewAllPos("TECHNICAL")).toBe(true);
expect(perms.canViewAllPos("MANNING")).toBe(true);
});
it("does not widen non-submitter roles, and is read-only (no approve/edit)", async () => {
vi.resetModules();
vi.stubEnv("NEXT_PUBLIC_SUBMITTER_VIEW_ALL_ENABLED", "true");
const perms = await import("@/lib/permissions");
expect(perms.submitterCanViewAll("MANAGER")).toBe(false);
expect(perms.canViewAllPos("ACCOUNTS")).toBe(true); // unchanged
// The flag grants read access only — no approval or edit rights.
expect(perms.hasPermission("TECHNICAL", "approve_po")).toBe(false);
expect(perms.hasPermission("TECHNICAL", "view_all_pos")).toBe(false);
});
});
describe("requirePermission", () => {
it("does not throw when permission is granted", () => {
expect(() => requirePermission("MANAGER", "approve_po")).not.toThrow();
});
it("throws when permission is denied", () => {
expect(() => requirePermission("TECHNICAL", "approve_po")).toThrow();
});
it("throws with a message containing the role name", () => {
expect(() => requirePermission("ACCOUNTS", "approve_po")).toThrow(/ACCOUNTS/);
});
});
});