test/generic/InMemoryBackend.spec.js
describe('InMemoryBackend', () => {
let objectStore;
const setEqual = function(actual, expected) {
return expected.equals(actual);
};
beforeEach((done) => {
objectStore = JungleDB.createVolatileObjectStore();
(async function () {
// Add 10 objects.
for (let i=0; i<10; ++i) {
await objectStore.put(`key${i}`, `value${i}`);
}
})().then(done, done.fail);
jasmine.addCustomEqualityTester(setEqual);
});
it('can open a transaction and commit it', (done) => {
(async function () {
const tx = objectStore.transaction();
await tx.remove('key0');
await tx.put('newKey', 'test');
expect(await tx.commit()).toBe(true);
expect(tx.state).toBe(Transaction.STATE.COMMITTED);
expect(await objectStore.get('key0')).toBe(undefined);
expect(await objectStore.get('newKey')).toBe('test');
})().then(done, done.fail);
});
it('can only commit one transaction and ensures read isolation', (done) => {
(async function () {
// Create two transactions on the main state.
const tx1 = objectStore.transaction();
// Remove a key in one of those.
await tx1.remove('key0');
await tx1.put('test', 'success');
await tx1.put('key1', 'someval');
const tx2 = objectStore.transaction();
// Ensure read isolation.
expect(await tx1.get('key0')).toBe(undefined);
expect(await tx2.get('key0')).toBe('value0');
expect(await tx1.get('test')).toBe('success');
expect(await tx2.get('test')).toBe(undefined);
expect(await tx1.get('key1')).toBe('someval');
expect(await tx2.get('key1')).toBe('value1');
// Commit one transaction.
expect(await tx1.commit()).toBe(true);
expect(tx1.state).toBe(Transaction.STATE.COMMITTED);
// Still ensure read isolation.
expect(await tx1.get('key0')).toBe(undefined);
expect(await tx2.get('key0')).toBe('value0');
expect(await tx1.get('test')).toBe('success');
expect(await tx2.get('test')).toBe(undefined);
expect(await tx1.get('key1')).toBe('someval');
expect(await tx2.get('key1')).toBe('value1');
// Create a third transaction, which should be based on tx1.
const tx3 = objectStore.transaction();
expect(await tx3.get('key0')).toBe(undefined);
expect(await tx3.get('test')).toBe('success');
expect(await tx3.get('key1')).toBe('someval');
// More changes
await tx3.remove('key2');
await tx3.put('test', 'success2');
await tx3.put('key0', 'someval');
// Still ensure read isolation.
expect(await tx1.get('key0')).toBe(undefined);
expect(await tx3.get('key0')).toBe('someval');
expect(await tx1.get('test')).toBe('success');
expect(await tx3.get('test')).toBe('success2');
expect(await tx1.get('key1')).toBe('someval');
expect(await tx3.get('key1')).toBe('someval');
expect(await tx1.get('key2')).toBe('value2');
expect(await tx3.get('key2')).toBe(undefined);
// Commit third transaction.
expect(await tx3.commit()).toBe(true);
expect(tx3.state).toBe(Transaction.STATE.COMMITTED);
// Create a fourth transaction, which should be based on tx3.
const tx4 = objectStore.transaction();
expect(await tx4.get('key0')).toBe('someval');
expect(await tx4.get('test')).toBe('success2');
expect(await tx4.get('key1')).toBe('someval');
expect(await tx4.get('key2')).toBe(undefined);
// Abort second transaction and commit empty fourth transaction.
expect(await tx2.abort()).toBe(true);
expect(await tx4.commit()).toBe(true);
expect(tx4.state).toBe(Transaction.STATE.COMMITTED);
// Now everything should be in the backend.
expect(await objectStore.get('key0')).toBe('someval');
expect(await objectStore.get('test')).toBe('success2');
expect(await objectStore.get('key1')).toBe('someval');
expect(await objectStore.get('key2')).toBe(undefined);
// Create a fifth transaction, which should be based on the new state.
const tx5 = objectStore.transaction();
expect(await tx5.get('key0')).toBe('someval');
expect(await tx5.get('test')).toBe('success2');
expect(await tx5.get('key1')).toBe('someval');
expect(await tx5.get('key2')).toBe(undefined);
await tx5.abort();
})().then(done, done.fail);
});
it('can correctly handle multi-layered transactions', (done) => {
(async function () {
// Create two transactions on the main state.
const tx1 = objectStore.transaction();
const tx2 = objectStore.transaction();
// Remove a key in one of those.
await tx1.remove('key0');
// Ensure read isolation.
expect(await tx1.get('key0')).toBe(undefined);
expect(await tx2.get('key0')).toBe('value0');
// Commit one transaction.
expect(await tx1.commit()).toBe(true);
expect(tx1.state).toBe(Transaction.STATE.COMMITTED);
// Still ensure read isolation.
expect(await tx1.get('key0')).toBe(undefined);
expect(await tx2.get('key0')).toBe('value0');
// Create a third transaction, which should be based on tx1.
const tx3 = objectStore.transaction();
expect(await tx3.get('key0')).toBe(undefined);
// Should not be able to commit tx2.
expect(await tx2.commit()).toBe(false);
expect(tx2.state).toBe(Transaction.STATE.CONFLICTED);
// Abort third transaction.
expect(await tx3.abort()).toBe(true);
// Now tx1 should be in the backend.
expect(await objectStore.get('key0')).toBe(undefined);
// Create a fourth transaction, which should be based on the new state.
const tx4 = objectStore.transaction();
expect(await tx4.get('key0')).toBe(undefined);
await tx4.abort();
})().then(done, done.fail);
});
it('correctly constructs key streams', (done) => {
(async function () {
let i = 0;
await objectStore.keyStream(key => {
expect(key).toBe(`key${i}`);
++i;
return true;
});
expect(i).toBe(10);
--i;
await objectStore.keyStream(key => {
expect(key).toBe(`key${i}`);
--i;
return true;
}, false);
expect(i).toBe(-1);
i = 4;
await objectStore.keyStream(key => {
expect(key).toBe(`key${i}`);
--i;
return true;
}, false, KeyRange.bound('key1', 'key4'));
expect(i).toBe(0);
i = 4;
await objectStore.keyStream(key => {
expect(key).toBe(`key${i}`);
++i;
return i < 5;
}, true, KeyRange.lowerBound('key3', true));
expect(i).toBe(5);
})().then(done, done.fail);
});
it('correctly constructs value streams', (done) => {
(async function () {
let i = 0;
await objectStore.valueStream((value, key) => {
expect(value).toBe(`value${i}`);
expect(key).toBe(`key${i}`);
++i;
return true;
});
expect(i).toBe(10);
--i;
await objectStore.valueStream((value, key) => {
expect(value).toBe(`value${i}`);
expect(key).toBe(`key${i}`);
--i;
return true;
}, false);
expect(i).toBe(-1);
i = 4;
await objectStore.valueStream((value, key) => {
expect(value).toBe(`value${i}`);
expect(key).toBe(`key${i}`);
--i;
return true;
}, false, KeyRange.bound('key1', 'key4'));
expect(i).toBe(0);
i = 4;
await objectStore.valueStream((value, key) => {
expect(value).toBe(`value${i}`);
expect(key).toBe(`key${i}`);
++i;
return i < 5;
}, true, KeyRange.lowerBound('key3', true));
expect(i).toBe(5);
})().then(done, done.fail);
});
});