huridocs/uwazi

View on GitHub
app/api/permissions/specs/entitiesPermissions.spec.ts

Summary

Maintainability
C
1 day
Test Coverage
/* eslint-disable max-lines */
import { testingDB } from 'api/utils/testing_db';
import entities from 'api/entities/entities';
import { entitiesPermissions } from 'api/permissions/entitiesPermissions';
import { AccessLevels, PermissionType, MixedAccess } from 'shared/types/permissionSchema';
import { search } from 'api/search';
import { fixtures, groupA, userA, userB } from 'api/permissions/specs/fixtures';
import { EntitySchema, EntityWithFilesSchema } from 'shared/types/entityType';
import { PermissionsDataSchema } from 'shared/types/permissionType';
import { UserInContextMockFactory } from 'api/utils/testingUserInContext';
import { PUBLIC_PERMISSION } from '../publicPermission';

const publicPermission = {
  ...PUBLIC_PERMISSION,
  level: AccessLevels.READ,
};

const mockCollab = () =>
  new UserInContextMockFactory().mock({
    _id: 'userId',
    role: 'collaborator',
    username: 'username',
    email: 'email',
  });

describe('permissions', () => {
  beforeEach(async () => {
    await testingDB.clearAllAndLoad(fixtures);
  });

  afterAll(async () => {
    await testingDB.disconnect();
  });

  describe('set entities permissions', () => {
    it('should update the specified entities with the passed permissions in all entities languages and make no other changes', async () => {
      const originalEntities = await entities.getUnrestricted({}, 'sharedId +permissions');
      const originalUpdatedEntities = originalEntities.filter(entity =>
        ['shared1', 'shared2'].includes(entity.sharedId!)
      );
      const originalNotUpdatedEntities = originalEntities.filter(
        entity => !['shared1', 'shared2'].includes(entity.sharedId!)
      );

      const permissionsData: PermissionsDataSchema = {
        ids: ['shared1', 'shared2'],
        permissions: [
          { refId: 'user1', type: PermissionType.USER, level: AccessLevels.READ },
          { refId: 'user2', type: PermissionType.USER, level: AccessLevels.WRITE },
          publicPermission,
        ],
      };
      await entitiesPermissions.set(permissionsData);
      mockCollab();
      const storedEntities = await entities.getUnrestricted({}, 'sharedId +permissions');
      const updateEntities = storedEntities.filter(entity =>
        ['shared1', 'shared2'].includes(entity.sharedId!)
      );
      const expectedNewPermissions = permissionsData.permissions.filter(
        p => p.type !== PermissionType.PUBLIC
      );
      updateEntities.forEach((entity, index) => {
        const original = originalUpdatedEntities[index];
        expect(entity).toEqual({
          ...original,
          permissions: expectedNewPermissions,
        });
      });
      const notUpdatedEntities = storedEntities.filter(
        entity => !['shared1', 'shared2'].includes(entity.sharedId!)
      );
      expect(notUpdatedEntities).toEqual(originalNotUpdatedEntities);
    });

    it('should invalidate if permissions are duplicated', async () => {
      const permissionsData: PermissionsDataSchema = {
        ids: ['shared1'],
        permissions: [
          { refId: 'user1', type: 'user', level: 'write' },
          { refId: 'user1', type: 'user', level: 'read' },
        ],
      };
      mockCollab();

      try {
        await entitiesPermissions.set(permissionsData);
        fail('should throw error');
      } catch (e) {
        expect(e.errors[0].keyword).toEqual('duplicatedPermissions');
      }
    });

    it('should preserve permission level if mixed access passed', async () => {
      const permissionsData: PermissionsDataSchema = {
        ids: ['shared2', 'shared4'],
        permissions: [
          {
            refId: userA._id,
            type: PermissionType.USER,
            level: AccessLevels.WRITE,
          },
          {
            refId: groupA._id,
            type: PermissionType.GROUP,
            level: MixedAccess.MIXED,
          },
          { ...publicPermission, level: MixedAccess.MIXED },
        ],
      };

      await entitiesPermissions.set(permissionsData);

      const storedEntities = await entities.getUnrestricted(
        { sharedId: { $in: permissionsData.ids } },
        'sharedId published +permissions'
      );

      expect(storedEntities[0].permissions?.length).toBe(2);
      expect(storedEntities[0].permissions).toEqual(
        expect.arrayContaining([
          expect.objectContaining({
            refId: userA._id,
            level: AccessLevels.WRITE,
          }),
          expect.objectContaining({
            refId: groupA._id,
            level: AccessLevels.READ,
          }),
        ])
      );
      expect(storedEntities[0].published).toBe(true);

      expect(storedEntities[2].permissions?.length).toBe(1);
      expect(storedEntities[2].permissions).toEqual(
        expect.arrayContaining([
          expect.objectContaining({
            refId: userA._id,
            level: AccessLevels.WRITE,
          }),
        ])
      );
      expect(storedEntities[2].published).toBe(false);
    });

    it('should reindex the entities after updating the permissions', async () => {
      const indexEntitiesSpy = jest.spyOn(search, 'indexEntities');

      const permissionsData: PermissionsDataSchema = {
        ids: ['shared1'],
        permissions: [publicPermission],
      };

      await entitiesPermissions.set(permissionsData);
      const storedEntities = (await entities.get({ sharedId: 'shared1' })) as EntitySchema[];
      const ids = storedEntities.map(entity => entity._id);
      expect(indexEntitiesSpy).toHaveBeenCalledWith({ _id: { $in: ids } }, '+fullText');

      indexEntitiesSpy.mockRestore();
    });

    describe('share publicly', () => {
      it('should save the entity with the publish field set to the correct value', async () => {
        const permissionsData: PermissionsDataSchema = {
          ids: ['shared1', 'shared2'],
          permissions: [{ refId: 'user1', type: 'user', level: 'write' }],
        };

        new UserInContextMockFactory().mockEditorUser();

        await entitiesPermissions.set(permissionsData);
        let storedEntities: EntityWithFilesSchema[] = await entities.get(
          { sharedId: 'shared1' },
          '+permissions'
        );

        storedEntities.forEach(entity => {
          expect(entity.permissions).toEqual(permissionsData.permissions);
          expect(entity.published).toBe(false);
        });

        permissionsData.permissions.push({ ...PUBLIC_PERMISSION, level: 'read' });
        await entitiesPermissions.set(permissionsData);
        storedEntities = await entities.get({ sharedId: 'shared1' }, '+permissions');

        storedEntities.forEach(entity => {
          expect(entity.permissions).toEqual([permissionsData.permissions[0]]);
          expect(entity.published).toBe(true);
        });
      });

      it('should throw if non admin/editor changes the publishing status', async () => {
        const permissionsData: PermissionsDataSchema = {
          ids: ['shared1'],
          permissions: [{ refId: 'user1', type: 'user', level: 'write' }],
        };

        mockCollab();

        try {
          await entitiesPermissions.set(permissionsData);
          fail('should throw forbidden exception');
        } catch (e) {
          const storedEntities = await entities.get({ sharedId: 'shared1' });
          expect(storedEntities[0].published).toBe(true);
        }
      });
    });
  });

  describe('get entities permissions', () => {
    it('should return the permissions of the requested entities', async () => {
      const permissions = await entitiesPermissions.get(['shared1', 'shared2']);
      expect(permissions).toEqual([
        {
          refId: groupA._id,
          label: groupA.name,
          level: MixedAccess.MIXED,
          type: PermissionType.GROUP,
        },
        publicPermission,
        {
          refId: userA._id,
          label: userA.username,
          level: AccessLevels.READ,
          type: PermissionType.USER,
        },
        {
          refId: userB._id,
          label: userB.username,
          level: MixedAccess.MIXED,
          type: PermissionType.USER,
        },
      ]);
    });

    it('should return mixed permissions in case one of the entities does not have any', async () => {
      const permissions = await entitiesPermissions.get(['shared1', 'shared3']);
      expect(permissions).toEqual([
        {
          refId: groupA._id,
          label: groupA.name,
          level: MixedAccess.MIXED,
          type: PermissionType.GROUP,
        },
        publicPermission,
        {
          refId: userA._id,
          label: userA.username,
          level: MixedAccess.MIXED,
          type: PermissionType.USER,
        },
        {
          refId: userB._id,
          label: userB.username,
          level: MixedAccess.MIXED,
          type: PermissionType.USER,
        },
      ]);
    });

    describe('publicly shared', () => {
      it('should return a permission of type "public" and level "mixed" if the entities are published and unpublished', async () => {
        const permissions = await entitiesPermissions.get(['shared1', 'shared4']);
        expect(permissions).toEqual(
          expect.arrayContaining([{ ...publicPermission, level: MixedAccess.MIXED }])
        );
      });

      it('should return a permission of type "public" and level "read" if the entity is published', async () => {
        const permissions = await entitiesPermissions.get(['shared1']);
        expect(permissions).toEqual(expect.arrayContaining([publicPermission]));
      });

      it('should NOT return a permission of type "public" if the entity is not published', async () => {
        const permissions = await entitiesPermissions.get(['shared4']);
        expect(permissions).toEqual([]);
      });
    });
  });
});