Files
edr-platform/apps/edr-freight-api/src/modules/rule-engine/services/yard-scope.service.spec.ts
2026-08-18 12:55:11 +00:00

140 lines
4.8 KiB
TypeScript

import { ForbiddenException } from '@nestjs/common';
import { YardScopeService } from './yard-scope.service';
/**
* The resolver answers "which yards", never "may they act at all" — that stays
* with the permission guard. So a mapped desk is narrowed to its yards, and an
* unmapped one keeps the reach its permissions already gave it.
*/
describe('YardScopeService', () => {
const yardIdsForPositions = jest.fn();
const service = () =>
new YardScopeService({ yardIdsForPositions } as never);
const staff = (positionId: string, permissions: string[] = []) => ({
roles: [{ key: 'staff' }],
permissions: permissions.map((key) => ({ key })),
employee: { position: { id: positionId, permissions: [] } },
});
beforeEach(() => {
jest.clearAllMocks();
delete process.env.YARD_SCOPE_ENFORCE;
});
it('resolves a mapped position to its yards', async () => {
yardIdsForPositions.mockResolvedValue(['yard-kality', 'yard-mojo']);
const scope = await service().getScopedYardIds(staff('pos-officer'));
expect(scope).toEqual(['yard-kality', 'yard-mojo']);
expect(yardIdsForPositions).toHaveBeenCalledWith(['pos-officer']);
});
it('leaves an unmapped position unrestricted — permissions still gate the action', async () => {
yardIdsForPositions.mockResolvedValue([]);
expect(await service().getScopedYardIds(staff('pos-unmapped'))).toBeNull();
});
it('leaves a caller with no resolvable position unrestricted', async () => {
const noPosition = { roles: [{ key: 'staff' }], employee: { position: {} } };
expect(await service().getScopedYardIds(noPosition)).toBeNull();
expect(yardIdsForPositions).not.toHaveBeenCalled();
});
it('narrows nothing for an anonymous caller but grants nothing either', async () => {
expect(await service().getScopedYardIds(null)).toEqual([]);
});
it('returns unrestricted only for super admins and view_all holders', async () => {
const superAdmin = { roles: [{ key: 'super_admin' }] };
const hqDesk = staff('pos-occ', ['edr_freight_app:yards:view_all']);
expect(await service().getScopedYardIds(superAdmin)).toBeNull();
expect(await service().getScopedYardIds(hqDesk)).toBeNull();
expect(yardIdsForPositions).not.toHaveBeenCalled();
});
it('includes delegated positions — standing in must not lose the yard', async () => {
yardIdsForPositions.mockResolvedValue(['yard-kality']);
await service().getScopedYardIds({
roles: [{ key: 'staff' }],
employee: {
position: { id: 'pos-own' },
delegatedPositions: [{ id: 'pos-gelan-director' }],
},
});
expect(yardIdsForPositions).toHaveBeenCalledWith([
'pos-own',
'pos-gelan-director',
]);
});
describe('listFilterYardIds', () => {
it('narrows nothing while shadow-logging', async () => {
yardIdsForPositions.mockResolvedValue(['yard-kality']);
expect(
await service().listFilterYardIds(staff('pos-officer'), undefined, 'list'),
).toBeNull();
});
it('narrows to the mapped yards once enforcing', async () => {
process.env.YARD_SCOPE_ENFORCE = 'true';
yardIdsForPositions.mockResolvedValue(['yard-kality', 'yard-mojo']);
expect(
await service().listFilterYardIds(staff('pos-officer'), undefined, 'list'),
).toEqual(['yard-kality', 'yard-mojo']);
});
it('keeps an in-scope yard filter as the caller asked', async () => {
process.env.YARD_SCOPE_ENFORCE = 'true';
yardIdsForPositions.mockResolvedValue(['yard-kality', 'yard-mojo']);
expect(
await service().listFilterYardIds(staff('pos-officer'), 'yard-mojo', 'list'),
).toEqual(['yard-mojo']);
});
it('returns an empty set — not everything — for an out-of-scope yard filter', async () => {
process.env.YARD_SCOPE_ENFORCE = 'true';
yardIdsForPositions.mockResolvedValue(['yard-kality']);
expect(
await service().listFilterYardIds(staff('pos-officer'), 'yard-djibouti', 'list'),
).toEqual([]);
});
it('never narrows an unmapped desk', async () => {
process.env.YARD_SCOPE_ENFORCE = 'true';
yardIdsForPositions.mockResolvedValue([]);
expect(
await service().listFilterYardIds(staff('pos-unmapped'), undefined, 'list'),
).toBeNull();
});
});
it('only logs an out-of-scope yard until YARD_SCOPE_ENFORCE is set', async () => {
yardIdsForPositions.mockResolvedValue(['yard-kality']);
const shadow = service();
await expect(
shadow.assertYardInScope(staff('pos-officer'), 'yard-mojo', 'test'),
).resolves.toBeUndefined();
process.env.YARD_SCOPE_ENFORCE = 'true';
const enforcing = service();
await expect(
enforcing.assertYardInScope(staff('pos-officer'), 'yard-mojo', 'test'),
).rejects.toBeInstanceOf(ForbiddenException);
});
});