huridocs/uwazi

View on GitHub
app/api/common.v2/database/specs/MongoResultSet.spec.ts

Summary

Maintainability
B
5 hrs
Test Coverage
import { ObjectId } from 'mongodb';

import { testingEnvironment } from 'api/utils/testingEnvironment';
import testingDB from 'api/utils/testing_db';

import { MongoResultSet } from '../MongoResultSet';

const testDocuments = [
  { _id: new ObjectId(), name: 'doc1' },
  { _id: new ObjectId(), name: 'doc2' },
  { _id: new ObjectId(), name: 'doc3' },
  { _id: new ObjectId(), name: 'doc4' },
  { _id: new ObjectId(), name: 'doc5' },
  { _id: new ObjectId(), name: 'doc6' },
];

const buildCursor = (query?: any) =>
  testingDB.mongodb?.collection<(typeof testDocuments)[number]>('testDocuments').find(query || {});

const buildAggregationCursor = () =>
  testingDB.mongodb
    ?.collection<(typeof testDocuments)[number]>('testDocuments')
    .aggregate([{ $match: {} }]);

beforeEach(async () => {
  await testingEnvironment.setUp({ testDocuments });
});

afterAll(async () => {
  await testingEnvironment.tearDown();
});

describe('when built from a $type cursor', () => {
  describe('next()', () => {
    it('should provide the next item as a normal mongo cursor', async () => {
      const cursor = buildCursor();
      const resultSet = new MongoResultSet(cursor!, MongoResultSet.NoOpMapper);
      let index = 0;
      // eslint-disable-next-line no-await-in-loop
      while (await resultSet.hasNext()) {
        // eslint-disable-next-line no-await-in-loop
        const item = await resultSet.next();
        expect(item).toEqual(testDocuments[index]);
        index += 1;
      }
    });
  });

  describe('nextBatch()', () => {
    it('should provide the next batch', async () => {
      const cursor = buildCursor();
      const resultSet = new MongoResultSet(cursor!, MongoResultSet.NoOpMapper);
      const visited: (typeof testDocuments)[] = [];
      // eslint-disable-next-line no-await-in-loop
      while (await resultSet.hasNext()) {
        // eslint-disable-next-line no-await-in-loop
        const batch = await resultSet.nextBatch(4);
        visited.push(batch);
      }
      expect(visited).toEqual([
        [testDocuments[0], testDocuments[1], testDocuments[2], testDocuments[3]],
        [testDocuments[4], testDocuments[5]],
      ]);
      expect(cursor?.closed).toBe(true);
    });
  });

  describe('all()', () => {
    it('should return all the results and close the cursor', async () => {
      const cursor = buildCursor();
      const resultSet = new MongoResultSet(cursor!, MongoResultSet.NoOpMapper);
      expect(await resultSet.all()).toEqual(testDocuments);
      expect(cursor?.closed).toBe(true);
    });

    it('should use the mapper function', async () => {
      const cursor = buildCursor();
      const resultSet = new MongoResultSet(cursor!, elem => elem.name);
      expect(await resultSet.all()).toEqual(testDocuments.map(elem => elem.name));
    });
  });

  describe('using indexed(...)', () => {
    it('should create an index/map of the whole result set', async () => {
      const cursor = buildCursor();
      const resultSet = new MongoResultSet(cursor!, elem => elem);

      const index = await resultSet.indexed(elem => elem.name);

      for (let i = 1; i <= 6; i += 1) {
        expect(index[`doc${i}`]).toEqual(testDocuments.find(d => d.name === `doc${i}`));
      }

      expect(Object.keys(index).length).toBe(6);
    });
  });

  it.each([
    { page: 1, start: 0, end: 4 },
    { page: 2, start: 4, end: 6 },
  ])('should return results page $page and close the cursor', async ({ page, start, end }) => {
    const cursor = buildCursor();
    const resultSet = new MongoResultSet(cursor!, elem => elem.name);
    const result = await resultSet.page(page, 4);
    expect(result).toEqual(testDocuments.slice(start, end).map(elem => elem.name));
    expect(cursor?.closed).toBe(true);
  });

  describe('using find(...)', () => {
    it('should return the first result that matches the query', async () => {
      const cursor = buildCursor();
      const resultSet = new MongoResultSet(cursor!, elem => elem.name);
      expect(await resultSet.find(item => item.startsWith('doc2'))).toBe('doc2');
      expect(cursor?.closed).toBe(true);
    });

    it('should return null if no item matches the query', async () => {
      const cursor = buildCursor();
      const resultSet = new MongoResultSet(cursor!, elem => elem.name);
      expect(await resultSet.find(item => item.startsWith('notDoc'))).toBe(null);
      expect(cursor?.closed).toBe(true);
    });
  });

  describe('using every(...) to check a predicate against every item', () => {
    it('should return true if it is true for every item', async () => {
      const cursor = buildCursor();
      const resultSet = new MongoResultSet(cursor!, elem => elem.name);
      expect(await resultSet.every(item => item.startsWith('doc'))).toBe(true);
      expect(cursor?.closed).toBe(true);
    });

    it('should return true if it is true for every item', async () => {
      const cursor = buildCursor();
      const resultSet = new MongoResultSet(cursor!, elem => elem.name);
      expect(await resultSet.every(item => item.startsWith('doc1'))).toBe(false);
      expect(cursor?.closed).toBe(true);
    });

    it('should return true if there are no items', async () => {
      const cursor = buildCursor({ name: 'non-existing' });
      const resultSet = new MongoResultSet(cursor!, elem => elem.name);
      expect(await resultSet.every(item => item.startsWith('doc'))).toBe(true);
      expect(cursor?.closed).toBe(true);
    });
  });

  describe('using some(...) to check a predicate against every item', () => {
    it('should return true if it is true for at least one item', async () => {
      const cursor = buildCursor();
      const resultSet = new MongoResultSet(cursor!, elem => elem.name);
      expect(await resultSet.some(item => item === 'doc3')).toBe(true);
      expect(cursor?.closed).toBe(true);
    });

    it('should return false if it is false for every item', async () => {
      const cursor = buildCursor();
      const resultSet = new MongoResultSet(cursor!, elem => elem.name);
      expect(await resultSet.some(item => item.startsWith('notDoc'))).toBe(false);
      expect(cursor?.closed).toBe(true);
    });

    it('should return false if there are no items', async () => {
      const cursor = buildCursor({ name: 'non-existing' });
      const resultSet = new MongoResultSet(cursor!, elem => elem.name);
      expect(await resultSet.some(item => item.startsWith('doc'))).toBe(false);
      expect(cursor?.closed).toBe(true);
    });
  });

  describe('using forEach(...)', () => {
    it('should execute the sync callback for every item', async () => {
      const cursor = buildCursor();
      const resultSet = new MongoResultSet(cursor!, elem => elem.name);
      const visited: string[] = [];
      await resultSet.forEach(item => {
        visited.push(item);
      });
      expect(visited).toEqual(['doc1', 'doc2', 'doc3', 'doc4', 'doc5', 'doc6']);
      expect(cursor?.closed).toBe(true);
    });

    it('should execute the async callback for every item', async () => {
      const cursor = buildCursor();
      const resultSet = new MongoResultSet(cursor!, elem => elem.name);
      const visited: string[] = [];
      await resultSet.forEach(async item => {
        visited.push(item);
        await new Promise(resolve => {
          setTimeout(resolve, 2);
        });
        visited.push(item);
      });
      expect(visited).toEqual([
        'doc1',
        'doc1',
        'doc2',
        'doc2',
        'doc3',
        'doc3',
        'doc4',
        'doc4',
        'doc5',
        'doc5',
        'doc6',
        'doc6',
      ]);
      expect(cursor?.closed).toBe(true);
    });

    it('should allow breaking, when the callback returns false', async () => {
      const cursor = buildCursor();
      const resultSet = new MongoResultSet(cursor!, elem => elem.name);
      const visited: string[] = [];
      await resultSet.forEach(item => {
        visited.push(item);
        if (item === 'doc2') {
          return false;
        }
      });
      expect(visited).toEqual(['doc1', 'doc2']);
      expect(cursor?.closed).toBe(true);
    });
  });

  describe('using forEachBatch(...)', () => {
    it('should execute the sync callback for every batch', async () => {
      const cursor = buildCursor();
      const resultSet = new MongoResultSet(cursor!, elem => elem.name);
      const visited: string[][] = [];
      await resultSet.forEachBatch(4, batch => {
        visited.push(batch);
      });
      expect(visited).toEqual([
        ['doc1', 'doc2', 'doc3', 'doc4'],
        ['doc5', 'doc6'],
      ]);
      expect(cursor?.closed).toBe(true);
    });

    it('should execute the async callback for every batch', async () => {
      const cursor = buildCursor();
      const resultSet = new MongoResultSet(cursor!, elem => elem.name);
      const visited: string[][] = [];
      await resultSet.forEachBatch(4, async batch => {
        visited.push(batch);
        await new Promise(resolve => {
          setTimeout(resolve, 2);
        });
        visited.push(batch);
      });
      expect(visited).toEqual([
        ['doc1', 'doc2', 'doc3', 'doc4'],
        ['doc1', 'doc2', 'doc3', 'doc4'],
        ['doc5', 'doc6'],
        ['doc5', 'doc6'],
      ]);
      expect(cursor?.closed).toBe(true);
    });

    it('should allow breaking, when the callback returns false', async () => {
      const cursor = buildCursor();
      const resultSet = new MongoResultSet(cursor!, elem => elem.name);
      const visited: string[][] = [];
      await resultSet.forEachBatch(2, batch => {
        visited.push(batch);
        if (batch[0] === 'doc3') {
          return false;
        }
      });
      expect(visited).toEqual([
        ['doc1', 'doc2'],
        ['doc3', 'doc4'],
      ]);
      expect(cursor?.closed).toBe(true);
    });
  });

  describe('when calling first()', () => {
    it('should return the first result and close the cursors', async () => {
      const cursor = buildCursor();
      const result = new MongoResultSet(cursor!, elem => elem.name);
      expect(await result.first()).toEqual('doc1');
      expect(cursor?.closed).toBe(true);
    });
  });
});

describe('when built from an aggregation cursor', () => {
  it('should correctly count the result and use the mapper', async () => {
    const cursor = buildAggregationCursor();
    const resultSet = new MongoResultSet(cursor!, elem => elem.name);
    const result = await resultSet.page(1, 4);
    expect(result).toEqual(testDocuments.slice(0, 4).map(elem => elem.name));
    expect(cursor?.closed).toBe(true);
  });
});