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); }); });