UNPKG

forest-express

Version:

Official package for all Forest Express Lianas

511 lines (434 loc) 16.7 kB
/* eslint-disable jest/no-hooks */ import ActionAuthorizationService from '../../../src/services/authorization/action-authorization'; import CustomActionTriggerForbiddenError from '../../../src/services/authorization/errors/custom-action-trigger-forbidden-error'; import InvalidActionConditionError from '../../../src/services/authorization/errors/invalid-action-condition-error'; import UnsupportedConditionalsError from '../../../src/services/authorization/errors/unsupported-conditional-error'; const mockRecordsCounterCount = jest.fn(); jest.mock('../../../src/services/exposed/records-counter', () => ({ __esModule: true, default: () => ({ count: mockRecordsCounterCount }), })); describe('actionAuthorizationService', () => { beforeEach(() => { jest.resetAllMocks(); }); const forestAdminClient = { permissionService: { canOnCollection: jest.fn(), canExecuteCustomAction: jest.fn(), canExecuteChart: jest.fn(), canExecuteSegmentQuery: jest.fn(), canApproveCustomAction: jest.fn(), canTriggerCustomAction: jest.fn(), doesTriggerCustomActionRequiresApproval: jest.fn(), getConditionalTriggerCondition: jest.fn(), getConditionalRequiresApprovalCondition: jest.fn(), getConditionalApproveCondition: jest.fn(), getConditionalApproveConditions: jest.fn(), getRoleIdsAllowedToApproveWithoutConditions: jest.fn(), }, verifySignedActionParameters: jest.fn(), }; const user = { id: 1, renderingId: 42, email: 'user@email.com', tags: {}, }; const recordsCounterParams = { user, model: Symbol('model') as never, timezone: 'Europe/Paris' }; const filterForCaller = { field: 'id', operator: 'in', value: [1, 2, 3] }; const customActionName = 'customActionName'; const collectionName = 'collectionName'; const condition = { value: 'someName', field: 'name', operator: 'equal', source: 'data', }; function makeActionAuthorizationService() { return new ActionAuthorizationService( { forestAdminClient, } as unknown as ConstructorParameters<typeof ActionAuthorizationService>[0], ); } describe('assertCanTriggerCustomAction', () => { describe('trigger does not require approval', () => { beforeEach(() => { forestAdminClient.permissionService .doesTriggerCustomActionRequiresApproval.mockResolvedValue(false); }); it('should do nothing if the user can trigger a custom action', async () => { forestAdminClient.permissionService .canTriggerCustomAction.mockResolvedValue(true); const authorization = makeActionAuthorizationService(); await expect( authorization.assertCanTriggerCustomAction({ user, collectionName, customActionName, recordsCounterParams, filterForCaller, }), ).resolves.toBeUndefined(); expect(forestAdminClient.permissionService.canTriggerCustomAction).toHaveBeenCalledWith({ userId: 1, customActionName, collectionName, }); }); it('should throw an error if the user cannot trigger', async () => { (forestAdminClient.permissionService.canTriggerCustomAction).mockResolvedValue( false, ); const authorization = makeActionAuthorizationService(); await expect( authorization.assertCanTriggerCustomAction({ user, collectionName, customActionName, recordsCounterParams, filterForCaller, }), ).rejects.toThrow(CustomActionTriggerForbiddenError); }); describe('with "Trigger" condition (conditional use case)', () => { beforeEach(() => { // user can trigger from permission ( forestAdminClient.permissionService.canTriggerCustomAction ).mockResolvedValue(true); ( forestAdminClient.permissionService.getConditionalTriggerCondition ).mockResolvedValue(condition); }); it('should do nothing if the user can perform conditional trigger', async () => { // All aggregate count returns the same results user can perform conditional trigger (mockRecordsCounterCount).mockResolvedValue(16); const authorization = makeActionAuthorizationService(); await expect( authorization.assertCanTriggerCustomAction({ user, collectionName, customActionName, recordsCounterParams, filterForCaller, }), ).resolves.toBeUndefined(); expect( forestAdminClient.permissionService.getConditionalTriggerCondition, ).toHaveBeenCalledWith({ userId: 1, collectionName, customActionName, }); expect(mockRecordsCounterCount).toHaveBeenCalledTimes(2); }); it('should throw an error if cannot perform conditional trigger', async () => { // Aggregate returns different results user cannot perform conditional trigger (mockRecordsCounterCount) .mockResolvedValueOnce(16) .mockResolvedValueOnce(2); const authorization = makeActionAuthorizationService(); await expect( authorization.assertCanTriggerCustomAction({ user, collectionName, customActionName, recordsCounterParams, filterForCaller, }), ).rejects.toThrow(CustomActionTriggerForbiddenError); expect(mockRecordsCounterCount).toHaveBeenCalledTimes(2); }); describe('when the collection does not support conditionals (smart collection use case)', () => { it('should throw an error UnsupportedConditionalsError', async () => { const authorization = makeActionAuthorizationService(); await expect( authorization.assertCanTriggerCustomAction({ user, collectionName, customActionName, // Basically executing a SmartAction on a SmartCollection use case recordsCounterParams: { model: undefined, } as never, filterForCaller, }), ).rejects.toThrow(UnsupportedConditionalsError); expect(mockRecordsCounterCount).not.toHaveBeenCalled(); }); }); }); }); describe('trigger does require approval', () => { beforeEach(() => { ( forestAdminClient.permissionService.doesTriggerCustomActionRequiresApproval ).mockResolvedValue(true); // We test the require approval so yes the user can trigger (forestAdminClient.permissionService.canTriggerCustomAction).mockResolvedValue( true, ); // No Approve conditions for any roles for this action ( forestAdminClient.permissionService.getConditionalApproveConditions ).mockResolvedValue(undefined); ( forestAdminClient.permissionService .getRoleIdsAllowedToApproveWithoutConditions ).mockResolvedValue([1, 16]); }); it( 'should throw an error CustomActionRequiresApprovalError with the ' + 'RoleIdsAllowedToApprove', async () => { ( forestAdminClient.permissionService.getConditionalRequiresApprovalCondition ).mockResolvedValue(null); const authorization = makeActionAuthorizationService(); await expect( authorization.assertCanTriggerCustomAction({ user, collectionName, customActionName, recordsCounterParams, filterForCaller, }), ).rejects.toMatchObject({ name: 'CustomActionRequiresApprovalError', message: 'This action requires to be approved.', data: { roleIdsAllowedToApprove: [1, 16], }, }); }, ); describe('with "RequiresApproval" condition (conditional use case)', () => { beforeEach(() => { ( forestAdminClient.permissionService.getConditionalRequiresApprovalCondition ).mockResolvedValue(condition); }); it('should do nothing if no records match the "RequiresApproval" condition', async () => { // No records matching condition approval not required (mockRecordsCounterCount).mockResolvedValue(0); const authorization = makeActionAuthorizationService(); await expect( authorization.assertCanTriggerCustomAction({ user, collectionName, customActionName, recordsCounterParams, filterForCaller, }), ).resolves.toBeUndefined(); expect( forestAdminClient.permissionService.getConditionalRequiresApprovalCondition, ).toHaveBeenCalledWith({ userId: 1, customActionName, collectionName, }); // One time during doesTriggerCustomActionRequiresApproval // Not called during roleIdsAllowedToApprove computation (No Approve condition) expect(mockRecordsCounterCount).toHaveBeenCalledTimes(1); }); it( 'should throw an error CustomActionRequiresApprovalError ' + 'if some records on which the CustomAction is executed match condition', async () => { (mockRecordsCounterCount).mockResolvedValueOnce(3); const authorization = makeActionAuthorizationService(); await expect( authorization.assertCanTriggerCustomAction({ user, collectionName, customActionName, recordsCounterParams, filterForCaller, }), ).rejects.toMatchObject({ name: 'CustomActionRequiresApprovalError', message: 'This action requires to be approved.', data: { roleIdsAllowedToApprove: [1, 16], }, }); // One time during doesTriggerCustomActionRequiresApproval // Not called during roleIdsAllowedToApprove computation (No Approve condition) expect(mockRecordsCounterCount).toHaveBeenCalledTimes(1); }, ); it( 'should throw an error InvalidActionConditionError when we cannot compute the ' + ' aggregate count', async () => { (mockRecordsCounterCount).mockRejectedValue( new Error('Some internal driver error'), ); const authorization = makeActionAuthorizationService(); await expect( authorization.assertCanTriggerCustomAction({ user, collectionName, customActionName, recordsCounterParams, filterForCaller, }), ).rejects.toThrow(new InvalidActionConditionError()); }, ); }); }); }); describe('assertCanApproveCustomAction', () => { describe('without "Approval" condition (basic use case)', () => { beforeEach(() => { // No Approve conditions for any roles for this action ( forestAdminClient.permissionService.getConditionalApproveConditions ).mockResolvedValue(undefined); ( forestAdminClient.permissionService .getRoleIdsAllowedToApproveWithoutConditions ).mockResolvedValue([1, 16]); }); it('should do nothing if the user can approve a custom action', async () => { (forestAdminClient.permissionService.canApproveCustomAction).mockResolvedValue( true, ); ( forestAdminClient.permissionService.getConditionalApproveCondition ).mockResolvedValue(null); const authorization = makeActionAuthorizationService(); await expect( authorization.assertCanApproveCustomAction({ user, collectionName, customActionName, recordsCounterParams, filterForCaller, requesterId: 30, }), ).resolves.toBeUndefined(); expect(forestAdminClient.permissionService.canApproveCustomAction).toHaveBeenCalledWith({ userId: 1, customActionName, collectionName, requesterId: 30, }); expect( forestAdminClient.permissionService.getConditionalApproveCondition, ).toHaveBeenCalledWith({ userId: 1, customActionName, collectionName, }); expect(mockRecordsCounterCount).not.toHaveBeenCalled(); }); it('should throw an error if the user cannot approve', async () => { (forestAdminClient.permissionService.canApproveCustomAction).mockResolvedValue( false, ); const authorization = makeActionAuthorizationService(); await expect( authorization.assertCanApproveCustomAction({ user, collectionName, customActionName, recordsCounterParams, filterForCaller, requesterId: 30, }), ).rejects.toMatchObject({ name: 'ApprovalNotAllowedError', message: "You don't have permission to approve this action.", data: { roleIdsAllowedToApprove: [1, 16], }, }); expect(mockRecordsCounterCount).not.toHaveBeenCalled(); }); }); describe('with "Approval" condition (conditional use case)', () => { beforeEach(() => { (forestAdminClient.permissionService.canApproveCustomAction).mockResolvedValue( true, ); ( forestAdminClient.permissionService.getConditionalApproveCondition ).mockResolvedValue(condition); ( forestAdminClient.permissionService.getConditionalApproveConditions ).mockResolvedValue( new Map([ [ 10, { value: 'some', field: 'name', operator: 'Equal', source: 'data', }, ], [ 20, { value: 'some', field: 'name', operator: 'Equal', source: 'data', }, ], ]), ); ( forestAdminClient.permissionService .getRoleIdsAllowedToApproveWithoutConditions ).mockResolvedValue([1, 16]); }); it('should do nothing if the user can approve a custom action', async () => { (mockRecordsCounterCount).mockResolvedValue(3); const authorization = makeActionAuthorizationService(); await expect( authorization.assertCanApproveCustomAction({ user, collectionName, customActionName, recordsCounterParams, filterForCaller, requesterId: 30, }), ).resolves.toBeUndefined(); expect(mockRecordsCounterCount).toHaveBeenCalledTimes(2); }); it('should throw an error ApprovalNotAllowedError', async () => { (mockRecordsCounterCount).mockResolvedValueOnce(3); const authorization = makeActionAuthorizationService(); await expect( authorization.assertCanApproveCustomAction({ user, collectionName, customActionName, recordsCounterParams, filterForCaller, requesterId: 30, }), ).rejects.toMatchObject({ name: 'ApprovalNotAllowedError', message: "You don't have permission to approve this action.", data: { roleIdsAllowedToApprove: [1, 16, 10, 20], }, }); // Two time during canApproveCustomAction // Two time during roleIdsAllowedToApprove computation // Even if there is two approves conditions they have equivalent condition hashes // so it's only computed once for both conditions // (We group roleId by condition hash to gain performances) expect(mockRecordsCounterCount).toHaveBeenCalledTimes(4); }); }); }); });