pelagia-portal/App/tests/unit/crew-pii.test.ts
Hardik 06ff587024 fix(crewing): mask Aadhaar/PAN document numbers server-side
The crew profile page passed SeafarerDocument.number to the client unmasked for
all roles and all doc types, exposing full Aadhaar/PAN identity numbers to MPO /
Manager / Site staff — contradicting the field's PII annotation and §6 /
Roles-and-Permissions §3 (Aadhaar/PAN are gated to Accounts/SuperUser, same as
the bank account number).

- crew-pii.ts: add documentNumberValue(number, docType, role) — masks AADHAAR /
  PAN for non-privileged roles via the existing canViewFullBankEpf gate +
  maskTail; non-identity docs (passport, CDC, STCW…) pass through; preserves the
  string|null contract.
- crew/[id]/page.tsx: mask the number server-side before it crosses to the client.
- Tests: unit cases for the helper; an integration test that invokes the server
  component and asserts the documents prop is masked for MANAGER/SITE_STAFF/MPO
  and full for ACCOUNTS/SUPERUSER.

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

67 lines
3 KiB
TypeScript

import { describe, it, expect } from "vitest";
import { maskTail, canViewFullBankEpf, canViewSalary, bankEpfValue, documentNumberValue } from "@/lib/crew-pii";
// PII visibility rules for the crew profile (Crewing-Implementation-Spec §6/§8.8).
describe("crew PII masking", () => {
describe("maskTail", () => {
it("keeps the last 4 by default", () => {
expect(maskTail("123456789")).toBe("•••• 6789");
});
it("renders — for empty values", () => {
expect(maskTail(null)).toBe("—");
expect(maskTail("")).toBe("—");
});
it("fully masks values at or under the visible length", () => {
expect(maskTail("12")).toBe("••••");
expect(maskTail("1234")).toBe("••••");
});
});
describe("canViewFullBankEpf", () => {
it("only Accounts and SuperUser see full bank/EPF", () => {
expect(canViewFullBankEpf("ACCOUNTS")).toBe(true);
expect(canViewFullBankEpf("SUPERUSER")).toBe(true);
expect(canViewFullBankEpf("MANAGER")).toBe(false);
expect(canViewFullBankEpf("MANNING")).toBe(false);
expect(canViewFullBankEpf("SITE_STAFF")).toBe(false);
});
});
describe("canViewSalary", () => {
it("hides salary from site staff only", () => {
expect(canViewSalary("SITE_STAFF")).toBe(false);
expect(canViewSalary("MANAGER")).toBe(true);
expect(canViewSalary("ACCOUNTS")).toBe(true);
expect(canViewSalary("MANNING")).toBe(true);
});
});
describe("bankEpfValue", () => {
it("shows full to Accounts, masked to others, — when empty", () => {
expect(bankEpfValue("123456789", "ACCOUNTS")).toBe("123456789");
expect(bankEpfValue("123456789", "MANAGER")).toBe("•••• 6789");
expect(bankEpfValue(null, "ACCOUNTS")).toBe("—");
});
});
describe("documentNumberValue", () => {
it("masks Aadhaar/PAN numbers for non-privileged roles", () => {
expect(documentNumberValue("123456789012", "AADHAAR", "MANAGER")).toBe("•••• 9012");
expect(documentNumberValue("123456789012", "AADHAAR", "MANNING")).toBe("•••• 9012");
expect(documentNumberValue("ABCDE1234F", "PAN", "SITE_STAFF")).toBe("•••• 234F");
});
it("shows Aadhaar/PAN in full to Accounts and SuperUser", () => {
expect(documentNumberValue("123456789012", "AADHAAR", "ACCOUNTS")).toBe("123456789012");
expect(documentNumberValue("ABCDE1234F", "PAN", "SUPERUSER")).toBe("ABCDE1234F");
});
it("does not restrict non-identity documents for any role", () => {
expect(documentNumberValue("P1234567", "PASSPORT", "SITE_STAFF")).toBe("P1234567");
expect(documentNumberValue("CDC-99", "CDC", "MANNING")).toBe("CDC-99");
expect(documentNumberValue("STCW-1", "STCW", "MANAGER")).toBe("STCW-1");
});
it("returns null for an empty number regardless of type/role", () => {
expect(documentNumberValue(null, "AADHAAR", "ACCOUNTS")).toBeNull();
expect(documentNumberValue("", "PASSPORT", "MANAGER")).toBeNull();
});
});
});