/* eslint-disable @typescript-eslint/dot-notation */ // ^^ For testing private methods ^^ // Import dotenv import * as dotenv from 'dotenv'; // Import Mango import { initMango, Collection, closeMango } from '../src'; // Import types import Lock from './types/Lock'; import TestDocument from './types/TestDocument'; import FullTestDocument from './types/FullTestDocument'; // Import constants import TEST_DOCUMENT from './constants/TEST_DOCUMENT'; import SECOND_TEST_DOCUMENT from './constants/SECOND_TEST_DOCUMENT'; import COLLECTION_NAMES from './constants/COLLECTION_NAMES'; import COLLECTION_OPTS from './constants/COLLECTION_OPTS'; // Import helpers import cleanupTestCollections from './helpers/cleanupTestCollections'; import getLockCollectionName from './helpers/getLockCollectionName'; import getLockCollectionOpts from './helpers/getLockCollectionOpts'; /*------------------------------------------------------------------------*/ /* Setup */ /*------------------------------------------------------------------------*/ beforeAll(() => { dotenv.config(); if (!process.env.MONGO_URL) { throw new Error('Set a MONGO_URL in .env for testing!'); } // Initialize database // NOTE: bump schemaVersion if changes are made to test collections initMango({ schemaVersion: Date.now() }); }); /*------------------------------------------------------------------------*/ /* Teardown */ /*------------------------------------------------------------------------*/ afterAll(async () => { // Cleanup await cleanupTestCollections(); // Close connection await closeMango(); }); /*------------------------------------------------------------------------*/ /* Tests */ /*------------------------------------------------------------------------*/ test( 'Basic Mango test: init, insert, find, delete, fail-to-find', async () => { const collectionName = COLLECTION_NAMES.test; const options = COLLECTION_OPTS[collectionName]; const testCollection = new Collection(collectionName, options); // Test insert: await testCollection.insert(TEST_DOCUMENT); // Test find: const foundDocuments = await testCollection.find({ id: TEST_DOCUMENT.id }); const expectedFound: FullTestDocument[] = [{ ...TEST_DOCUMENT, mongoTimestamp: undefined }]; expect(foundDocuments).toStrictEqual(expectedFound); // Test delete: await testCollection.delete({ id: TEST_DOCUMENT.id }); const findNone = await testCollection.find({ id: TEST_DOCUMENT.id }); expect(findNone).toStrictEqual([]); }, ); test( 'deleteAll works', async () => { const collectionName = COLLECTION_NAMES.test; const options = COLLECTION_OPTS[collectionName]; const testCollection = new Collection(collectionName, options); // Insert initial test documents: await testCollection.insert(TEST_DOCUMENT); await testCollection.insert(SECOND_TEST_DOCUMENT); // Delete all: await testCollection.deleteAll({}); // Find and compare const foundDocuments = await testCollection.find({}); expect(foundDocuments).toStrictEqual([]); }, ); test( 'count works', async () => { const collectionName = COLLECTION_NAMES.test; const options = COLLECTION_OPTS[collectionName]; const testCollection = new Collection(collectionName, options); // Insert initial test documents: await testCollection.insert(TEST_DOCUMENT); await testCollection.insert(SECOND_TEST_DOCUMENT); // Count all: const count = await testCollection.count({}); expect(count).toBe(2); // Count only first document: const countFirst = await testCollection.count({ id: TEST_DOCUMENT.id }); expect(countFirst).toBe(1); // Cleanup await testCollection.delete({ id: TEST_DOCUMENT.id }); await testCollection.delete({ id: SECOND_TEST_DOCUMENT.id }); }, ); test( 'findAndExtractProp works', async () => { const collectionName = COLLECTION_NAMES.test; const options = COLLECTION_OPTS[collectionName]; const testCollection = new Collection(collectionName, options); // Insert initial test document: await testCollection.insert(TEST_DOCUMENT); await testCollection.insert(SECOND_TEST_DOCUMENT); // Find and extract message const messages = await testCollection.findAndExtractProp( {}, 'msg', ); expect(messages).toStrictEqual([ TEST_DOCUMENT.msg, SECOND_TEST_DOCUMENT.msg, ]); // Find and extract rating (exclude falsy) const ratings = await testCollection.findAndExtractProp( {}, 'rating', true, ); expect(ratings).toStrictEqual( [TEST_DOCUMENT.rating, SECOND_TEST_DOCUMENT.rating] .filter((rating) => { return rating; }), ); // Cleanup await testCollection.delete({ id: TEST_DOCUMENT.id }); await testCollection.delete({ id: SECOND_TEST_DOCUMENT.id }); }, ); test( 'Testing the increment method', async () => { const collectionName = COLLECTION_NAMES.test; const options = COLLECTION_OPTS[collectionName]; const testCollection = new Collection(collectionName, options); // Insert initial test document: await testCollection.insert({ ...TEST_DOCUMENT, rating: 1 }); // Increment the rating await testCollection.increment(TEST_DOCUMENT.id, 'rating'); // Find and compare const incrementedDocument = (await testCollection.find({ id: TEST_DOCUMENT.id }))[0]; expect(incrementedDocument.rating).toBe(2); // Cleanup await testCollection.delete({ id: TEST_DOCUMENT.id }); }, ); test( 'Testing updatePropValues', async () => { const collectionName = COLLECTION_NAMES.objTest; const options = COLLECTION_OPTS[collectionName]; const testDocId = 777; const testObjDoc = { id: testDocId, messageMap: { hello: 'world', outer: 'space', mother: 'earth', }, }; const testCollection = new Collection(collectionName, options); // Insert initial test document: await testCollection.insert(testObjDoc); // Update hello to 'moon' await testCollection.updatePropValues({ id: testDocId }, { 'messageMap.hello': 'moon' }); // Find and compare const updatedDocument = (await testCollection.find({ id: testDocId }))[0]; expect(updatedDocument.messageMap.hello).toBe('moon'); // Cleanup await testCollection.delete({ id: testDocId }); }, ); test( 'Collection methods for array documents', async () => { const collectionName = COLLECTION_NAMES.arrayTest; const options = COLLECTION_OPTS[collectionName]; const testDocId = 888; const testArrDoc = { id: testDocId, messages: [ 'First', 'Second', 'Third', ], }; const testCollection = new Collection(collectionName, options); // Insert initial test document: await testCollection.insert(testArrDoc); /* --------- Test Pushing Element --------- */ // Push new array member await testCollection.push(testDocId, 'messages', 'Fourth'); // Find and compare const pushedDocument = await testCollection.find({ id: testDocId }).then(docs => docs.shift()); expect(pushedDocument.messages).toStrictEqual(['First', 'Second', 'Third', 'Fourth']); /* ---------- Test Filtering Out ---------- */ // Filter out 'goodbye' await testCollection.filterOut({ id: testDocId, arrayProp: 'messages', compareValue: 'Fourth', }); // Compare to original const filteredDocument = await testCollection.find({ id: testDocId }).then(docs => docs.shift()); expect(filteredDocument).toStrictEqual({ ...testArrDoc, mongoTimestamp: undefined }); // Cleanup await testCollection.delete({ id: testDocId }); }, ); test( 'Test deep filtering an array of objects.', async () => { const collectionName = COLLECTION_NAMES.arrayTest; const options = COLLECTION_OPTS[collectionName]; const testDocId = 999; const testArrDoc = { id: testDocId, messages: [ { msg: 'Hello' }, { msg: 'World' }, { msg: '!!!' }, ], }; const testCollection = new Collection(collectionName, options); // Insert initial test document: await testCollection.insert(testArrDoc); // Filter out '!!!' await testCollection.filterOut({ id: testDocId, arrayProp: 'messages', compareProp: 'msg', compareValue: '!!!', }); // Compare to original const filteredDocument = await testCollection.find({ id: testDocId }).then(docs => docs.shift()); testArrDoc.messages.pop(); expect(filteredDocument).toStrictEqual({ ...testArrDoc, mongoTimestamp: undefined }); // Cleanup await testCollection.delete({ id: testDocId }); }, ); test( 'Basic Coconut lock/unlock test', async () => { const collectionName = COLLECTION_NAMES.coconut; // Get collection const options = COLLECTION_OPTS[collectionName]; const testCollection = new Collection(collectionName, options); // Insert test document await testCollection.insert(TEST_DOCUMENT); // Get lock collection const lockCollectionName = getLockCollectionName(collectionName); const lockOpts = getLockCollectionOpts(); const locks = new Collection(lockCollectionName, lockOpts); // Test lock await testCollection['lock'](TEST_DOCUMENT.id); const foundLocks = await locks.find({ id: TEST_DOCUMENT.id }); expect(foundLocks[0].id).toBe(TEST_DOCUMENT.id); // Test unlock await testCollection['unlock'](TEST_DOCUMENT.id); const noLocks = await locks.find({ id: TEST_DOCUMENT.id }); expect(noLocks).toStrictEqual([]); // Cleanup await testCollection.delete({ id: TEST_DOCUMENT.id }); }, ); test( 'Coconut: testing that lock actually blocks', async () => { const collectionName = COLLECTION_NAMES.coconut; // Get collection const options = COLLECTION_OPTS[collectionName]; const testCollection = new Collection(collectionName, options); // Insert test document await testCollection.insert(TEST_DOCUMENT); // Get lock collection const lockCollectionName = getLockCollectionName(collectionName); const lockOpts = getLockCollectionOpts(); const locks = new Collection(lockCollectionName, lockOpts); // Test lock await testCollection['lock'](TEST_DOCUMENT.id); const foundLocks = await locks.find({ id: TEST_DOCUMENT.id }); expect(foundLocks[0].id).toBe(TEST_DOCUMENT.id); // Test that other lock will not overlap const lockStatus = { isLocked: true }; const otherCollection = new Collection('test_coconut_collection', options); const lockPromise = otherCollection['lock'](TEST_DOCUMENT.id) .then(() => { return lockStatus.isLocked; }); // Unlock from the old collection await testCollection['unlock'](TEST_DOCUMENT.id); lockStatus.isLocked = false; // Checking the lock from before const resolvedLockPromise = await lockPromise; expect(resolvedLockPromise).toBe(false); await otherCollection['unlock'](TEST_DOCUMENT.id); const noLocks = await locks.find({ id: TEST_DOCUMENT.id }); expect(noLocks).toStrictEqual([]); // Cleanup await testCollection.delete({ id: TEST_DOCUMENT.id }); }, ); test( 'Coconut: testing runAtomicProcedure', async () => { const collectionName = COLLECTION_NAMES.coconut; // Get collection const options = COLLECTION_OPTS[collectionName]; const testCollection = new Collection(collectionName, options); // Insert test document await testCollection.insert(TEST_DOCUMENT); // Get lock collection const lockCollectionName = getLockCollectionName(collectionName); const lockOpts = getLockCollectionOpts(); const locks = new Collection(lockCollectionName, lockOpts); const idToLock = TEST_DOCUMENT.id; await testCollection.runAtomicProcedure({ idOrIdsToLock: idToLock, procedure: async (collection: Collection) => { // Check that runAtomicProcedure properly locks the list of items const allLocks = await locks.find({}); expect(allLocks.length).toBe(1); allLocks.forEach((lock) => { expect(lock.id).toBe(TEST_DOCUMENT.id); }); // Try some operations await collection.updatePropValues( { id: TEST_DOCUMENT.id }, { msg: 'Goodbye World' }, ); }, }); // Check that the changes have been pushed to the testCollection const changedTestDocs = await testCollection.find({ id: TEST_DOCUMENT.id }); expect(changedTestDocs[0].msg).toBe('Goodbye World'); // Test that items are no longer locked const noLocks = await locks.find({ id: TEST_DOCUMENT.id }); expect(noLocks).toStrictEqual([]); // Cleanup await testCollection.delete({ id: TEST_DOCUMENT.id }); }, );