add e2e test

This commit is contained in:
Marshal
2026-07-22 23:44:32 +00:00
parent b0f561a935
commit 2481f43f1f
32 changed files with 4702 additions and 125 deletions

View File

@@ -0,0 +1,51 @@
import {
assertCanApproveContractStep,
canEditContractStep,
} 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 steps 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,
);
});
});

View File

@@ -201,6 +201,34 @@ export function assertCanApproveContractStep(
);
}
/**
* Strict "is it exactly this caller's turn?" test — mirrors the backoffice
* `canApproveContractStep`. Same passes as {@link assertCanApproveContractStep}
* EXCEPT the blanket "holds any contract-approve permission" fallback is
* dropped: a line-staff holding `approveLineStaff` must NOT read as the director
* for a director step. Used to gate contract-document editing so approval hands
* edit rights to the NEXT approver only — a previous approver who already acted
* (but still holds an approve permission) loses the edit button, as required.
*
* (Kept separate from the approve/reject gate, which keeps the blanket fallback
* so delegates whose token omits a position type can still action their step.)
*/
export function canEditContractStep(
user: TCurrentUser | MeLikeUser | null | undefined,
requiredRole: string,
): boolean {
if (isFreightApprovalAdmin(user)) return true;
const positionTypes = collectPositionTypeKeys(user);
if (positionTypes.includes(requiredRole)) return true;
const aliases = LEGACY_ROLE_POSITION_TYPES[requiredRole] ?? [];
if (aliases.some((alias) => positionTypes.includes(alias))) return true;
const legacyPermission = CONTRACT_APPROVE_ROLE_PERMISSION[requiredRole];
return Boolean(legacyPermission && hasFreightPermission(user, legacyPermission));
}
export function assertCanApproveBookingStep(
user: TCurrentUser | MeLikeUser | null | undefined,
requiredRole: string,