dce-mango
Version:
Harvard DCE's Non-relational DB Wrapper.
446 lines (346 loc) • 13.4 kB
text/typescript
/* 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<TestDocument>(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<TestDocument>(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<TestDocument>(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<TestDocument>(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<TestDocument>(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<typeof testObjDoc>(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<typeof testArrDoc>(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<typeof testArrDoc>(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<TestDocument>(collectionName, options);
// Insert test document
await testCollection.insert(TEST_DOCUMENT);
// Get lock collection
const lockCollectionName = getLockCollectionName(collectionName);
const lockOpts = getLockCollectionOpts();
const locks = new Collection<Lock>(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<TestDocument>(collectionName, options);
// Insert test document
await testCollection.insert(TEST_DOCUMENT);
// Get lock collection
const lockCollectionName = getLockCollectionName(collectionName);
const lockOpts = getLockCollectionOpts();
const locks = new Collection<Lock>(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<TestDocument>('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<TestDocument>(collectionName, options);
// Insert test document
await testCollection.insert(TEST_DOCUMENT);
// Get lock collection
const lockCollectionName = getLockCollectionName(collectionName);
const lockOpts = getLockCollectionOpts();
const locks = new Collection<Lock>(lockCollectionName, lockOpts);
const idToLock = TEST_DOCUMENT.id;
await testCollection.runAtomicProcedure({
idOrIdsToLock: idToLock,
procedure: async (collection: Collection<TestDocument>) => {
// 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 });
},
);