mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
124 lines
4.1 KiB
TypeScript
124 lines
4.1 KiB
TypeScript
import {
|
||
assertCanApproveContractStep,
|
||
canEditContractStep,
|
||
collectPermissionKeys,
|
||
hasFreightPermission,
|
||
setPositionTypePermissionResolver,
|
||
} from './freight-permission.util';
|
||
import { FREIGHT_PERMS } from '../seed/freight-permissions.registry';
|
||
|
||
// The document-edit gate (canEditContractStep) must be STRICT: only the approver
|
||
// whose turn it is may edit. This is the fix for a previous approver keeping the
|
||
// "Edit contract articles" button after acting, because the approve gate lets
|
||
// through anyone holding any contract-approve permission.
|
||
describe('canEditContractStep (strict per-step edit gate)', () => {
|
||
const director = {
|
||
employee: { position: { positionType: { key: '-marketing-director-' } } },
|
||
};
|
||
// A line staff who already approved their own step but still holds a
|
||
// contract-approve permission — the exact actor that leaked edit rights.
|
||
const officerWithApprovePerm = {
|
||
employee: {
|
||
position: {
|
||
positionType: { key: '-marketing-officer-' },
|
||
permissions: [{ key: FREIGHT_PERMS.contracts.approveLineStaff }],
|
||
},
|
||
},
|
||
};
|
||
const superAdmin = { roles: [{ key: 'super_admin' }] };
|
||
|
||
it('lets the step’s own approver edit', () => {
|
||
expect(canEditContractStep(director, '-marketing-director-')).toBe(true);
|
||
});
|
||
|
||
it('lets an approval admin edit any step', () => {
|
||
expect(canEditContractStep(superAdmin, '-marketing-director-')).toBe(true);
|
||
});
|
||
|
||
it('does NOT let a different approver edit just because they hold an approve permission', () => {
|
||
expect(canEditContractStep(officerWithApprovePerm, '-marketing-director-')).toBe(
|
||
false,
|
||
);
|
||
});
|
||
|
||
it('stays intentionally stricter than the approve gate (which keeps the blanket fallback)', () => {
|
||
// The approve gate passes the officer via the any-permission blanket…
|
||
expect(() =>
|
||
assertCanApproveContractStep(officerWithApprovePerm, '-marketing-director-'),
|
||
).not.toThrow();
|
||
// …but the edit gate does not — that divergence IS the fix.
|
||
expect(canEditContractStep(officerWithApprovePerm, '-marketing-director-')).toBe(
|
||
false,
|
||
);
|
||
});
|
||
});
|
||
|
||
/**
|
||
* The GL lockout regression: positions created through the admin UI keep their
|
||
* grants on the position TYPE, and the JWT only ever snapshots DIRECT position
|
||
* permissions. Without the type resolver those staff resolved to zero
|
||
* permissions, so every gated route rejected them — which is what kept GL
|
||
* officers out of their own clearance detail pages.
|
||
*/
|
||
describe('collectPermissionKeys — position-type grants', () => {
|
||
const CLEARANCE = FREIGHT_PERMS.contracts.clearanceReview;
|
||
|
||
afterEach(() => {
|
||
setPositionTypePermissionResolver(() => []);
|
||
});
|
||
|
||
const glOfficer = {
|
||
roles: [],
|
||
permissions: [],
|
||
employee: {
|
||
position: {
|
||
permissions: [], // admin-created position carries NO direct grants
|
||
positionType: { key: 'commercial-global-logistics-(et)-officer' },
|
||
},
|
||
},
|
||
};
|
||
|
||
it('resolves permissions carried by the position type', () => {
|
||
setPositionTypePermissionResolver((key) =>
|
||
key === 'commercial-global-logistics-(et)-officer' ? [CLEARANCE] : [],
|
||
);
|
||
|
||
expect(collectPermissionKeys(glOfficer)).toContain(CLEARANCE);
|
||
expect(hasFreightPermission(glOfficer, CLEARANCE)).toBe(true);
|
||
});
|
||
|
||
it('handles the array-shaped employee payload too', () => {
|
||
setPositionTypePermissionResolver(() => [CLEARANCE]);
|
||
|
||
const arrayShaped = {
|
||
roles: [],
|
||
permissions: [],
|
||
employee: [
|
||
{
|
||
positions: [
|
||
{ permissions: [], positionType: { key: 'djibouti-gl-officer' } },
|
||
],
|
||
},
|
||
],
|
||
};
|
||
|
||
expect(hasFreightPermission(arrayShaped, CLEARANCE)).toBe(true);
|
||
});
|
||
|
||
it('still rejects when neither the position nor its type grants it', () => {
|
||
setPositionTypePermissionResolver(() => []);
|
||
|
||
expect(hasFreightPermission(glOfficer, CLEARANCE)).toBe(false);
|
||
});
|
||
|
||
it('keeps direct position permissions working with no resolver installed', () => {
|
||
const direct = {
|
||
roles: [],
|
||
permissions: [],
|
||
employee: { position: { permissions: [{ key: CLEARANCE }] } },
|
||
};
|
||
|
||
expect(hasFreightPermission(direct, CLEARANCE)).toBe(true);
|
||
});
|
||
});
|