mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
140 lines
4.8 KiB
TypeScript
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);
|
|
});
|
|
});
|