Home Reference Source Test

test/generic/Snapshot.spec.js

describe('Snapshot', () => {
    /** @type {ObjectStore} */
    let objectStore;
    /** @type {JungleDB} */
    let db;

    const setEqual = function(actual, expected) {
        return expected.equals(actual);
    };

    beforeEach((done) => {
        db = new JungleDB('test', 1);
        objectStore = db.createObjectStore('testStore');
        objectStore.createIndex('test', ['a', 'b'], { keyEncoding: JungleDB.NUMBER_ENCODING });

        (async function () {
            await db.connect();
            // Add 10 objects.
            for (let i=0; i<10; ++i) {
                await objectStore.put(`key${i}`, `value${i}`);
            }
        })().then(done, done.fail);

        jasmine.addCustomEqualityTester(setEqual);
    });

    afterEach((done) => {
        (async function () {
            await db.destroy();
        })().then(done, done.fail);
    });

    it('works on the backend', (done) => {
        (async function () {
            const snap = objectStore.snapshot();
            for (let i=0; i<10; ++i) {
                expect(await snap.get(`key${i}`)).toBe(`value${i}`);
            }

            await objectStore.remove('key0');
            await objectStore.remove('key2');
            await objectStore.put('key1', 'test');
            await objectStore.put('key2', 'test');
            await objectStore.put('something_new', 'nothing');
            await objectStore.put('something_new', 'test');

            expect(await objectStore.get('key0')).toBeUndefined();
            expect(await objectStore.get('key1')).toBe('test');
            expect(await objectStore.get('key2')).toBe('test');
            expect(await objectStore.get('something_new')).toBe('test');

            for (let i=0; i<10; ++i) {
                expect(await snap.get(`key${i}`)).toBe(`value${i}`);
            }
            expect(await snap.get('something_new')).toBe(undefined);

            await snap.abort();
        })().then(done, done.fail);
    });

    it('works on unflushed transaction', (done) => {
        (async function () {
            const tx1 = objectStore.transaction();
            const tx2 = objectStore.transaction();

            await tx1.put('key10', 'value10');
            await tx1.commit();

            const snap = objectStore.snapshot();

            for (let i=0; i<11; ++i) {
                expect(await snap.get(`key${i}`)).toBe(`value${i}`);
            }

            const tx3 = objectStore.transaction();
            await tx3.remove('key0');
            await tx3.remove('key2');
            await tx3.put('key1', 'test');
            await tx3.put('key2', 'test');
            await tx3.commit();

            for (let i=0; i<11; ++i) {
                expect(await snap.get(`key${i}`)).toBe(`value${i}`);
            }

            await tx2.abort();

            expect(await objectStore.get('key0')).toBeUndefined();
            expect(await objectStore.get('key1')).toBe('test');
            expect(await objectStore.get('key2')).toBe('test');

            for (let i=0; i<10; ++i) {
                expect(await snap.get(`key${i}`)).toBe(`value${i}`);
            }

            await snap.abort();
        })().then(done, done.fail);
    });

    it('works on unflushed synchronous transaction', (done) => {
        (async function () {
            const tx1 = objectStore.synchronousTransaction();
            const tx2 = objectStore.transaction();

            await tx1.put('key10', 'value10');
            await tx1.commit();

            const snap = objectStore.snapshot();

            for (let i=0; i<11; ++i) {
                expect(await snap.get(`key${i}`)).toBe(`value${i}`);
            }

            const tx3 = objectStore.transaction();
            await tx3.remove('key0');
            await tx3.remove('key2');
            await tx3.put('key1', 'test');
            await tx3.put('key2', 'test');
            await tx3.commit();

            for (let i=0; i<11; ++i) {
                expect(await snap.get(`key${i}`)).toBe(`value${i}`);
            }

            await tx2.abort();

            expect(await objectStore.get('key0')).toBeUndefined();
            expect(await objectStore.get('key1')).toBe('test');
            expect(await objectStore.get('key2')).toBe('test');

            for (let i=0; i<10; ++i) {
                expect(await snap.get(`key${i}`)).toBe(`value${i}`);
            }

            await snap.abort();
        })().then(done, done.fail);
    });

    it('can handle truncate on the backend', (done) => {
        (async function () {
            const snap = objectStore.snapshot();

            for (let i=0; i<10; ++i) {
                expect(await snap.get(`key${i}`)).toBe(`value${i}`);
            }

            await objectStore.truncate();

            for (let i=0; i<10; ++i) {
                expect(await objectStore.get('key0')).toBeUndefined();
                expect(await snap.get(`key${i}`)).toBe(`value${i}`);
            }

            await snap.abort();
        })().then(done, done.fail);
    });

    it('can inherit transactions', (done) => {
        (async function () {
            const tx1 = objectStore.transaction();

            await tx1.put('key10', 'value10');

            const snap = objectStore.snapshot();
            await snap.inherit(tx1);
            await tx1.abort();

            for (let i=0; i<11; ++i) {
                expect(await snap.get(`key${i}`)).toBe(`value${i}`);
            }

            const tx3 = objectStore.transaction();
            await tx3.remove('key0');
            await tx3.remove('key2');
            await tx3.put('key1', 'test');
            await tx3.put('key2', 'test');
            await tx3.commit();

            for (let i=0; i<11; ++i) {
                expect(await snap.get(`key${i}`)).toBe(`value${i}`);
            }

            await snap.abort();
        })().then(done, done.fail);
    });

    it('can implicitly inherit transactions', (done) => {
        (async function () {
            const tx1 = objectStore.transaction();

            await tx1.put('key10', 'value10');

            const snap = tx1.snapshot();
            await tx1.abort();

            for (let i=0; i<11; ++i) {
                expect(await snap.get(`key${i}`)).toBe(`value${i}`);
            }

            const tx3 = objectStore.transaction();
            await tx3.remove('key0');
            await tx3.remove('key2');
            await tx3.put('key1', 'test');
            await tx3.put('key2', 'test');
            await tx3.commit();

            for (let i=0; i<11; ++i) {
                expect(await snap.get(`key${i}`)).toBe(`value${i}`);
            }

            await snap.abort();
        })().then(done, done.fail);
    });

    it('can implicitly inherit nested transactions', (done) => {
        (async function () {
            const tx1 = objectStore.transaction();
            await tx1.put('key10', 'value10');

            const tx2 = tx1.transaction();
            await tx2.put('key11', 'value11');

            const snap = tx2.snapshot();
            await tx2.abort();
            await tx1.abort();

            for (let i=0; i<12; ++i) {
                expect(await snap.get(`key${i}`)).toBe(`value${i}`);
            }

            const tx3 = objectStore.transaction();
            await tx3.remove('key0');
            await tx3.remove('key2');
            await tx3.put('key1', 'test');
            await tx3.put('key2', 'test');
            await tx3.commit();

            for (let i=0; i<12; ++i) {
                expect(await snap.get(`key${i}`)).toBe(`value${i}`);
            }

            await snap.abort();
        })().then(done, done.fail);
    });

    it('can handle index queries', (done) => {
        (async function () {
            const tx1 = objectStore.transaction();
            await tx1.put('newTest', {
                'key': 'newTest',
                'a': {
                    'b': 123
                }
            });

            await tx1.put('newTest1', {
                'key': 'newTest1',
                'a': {
                    'b': 123
                }
            });

            await tx1.put('newTest2', {
                'a': {
                    'b': 124
                }
            });

            await tx1.commit();

            const snap = objectStore.snapshot();

            await objectStore.remove('newTest1');

            const index = snap.index('test');
            expect(await index.keys(KeyRange.only(123))).toEqual(new Set(['newTest', 'newTest1']));
            expect(await index.keys(KeyRange.lowerBound(123, true))).toEqual(new Set(['newTest2']));

            const values = await snap.values(Query.eq('test', 123));
            const expectedKeys = new Set(['newTest', 'newTest1']);
            for (const value of values) {
                expect(expectedKeys.delete(value['key'])).toBe(true);
            }
            expect(expectedKeys.size).toBe(0);
        })().then(done, done.fail);
    });

    it('throws errors on forbidden methods', (done) => {
        (async function () {
            const snapshot = objectStore.snapshot();
            expect(() => snapshot.truncateSync()).toThrow();
            expect(() => snapshot.putSync()).toThrow();
            expect(() => snapshot.removeSync()).toThrow();
            expect(() => snapshot.transaction()).toThrow();
            expect(() => snapshot.synchronousTransaction()).toThrow();
            expect(() => snapshot.snapshot()).toThrow();

            let threw = false;
            try {
                await snapshot.put();
            } catch (e) {
                threw = true;
            }
            expect(threw).toBe(true);

            threw = false;
            try {
                await snapshot.remove();
            } catch (e) {
                threw = true;
            }
            expect(threw).toBe(true);

            threw = false;
            try {
                await snapshot.commit();
            } catch (e) {
                threw = true;
            }
            expect(threw).toBe(true);

            await snapshot.abort();

            threw = false;
            try {
                await snapshot.abort();
            } catch (e) {
                threw = true;
            }
            expect(threw).toBe(true);
        })().then(done, done.fail);
    });
});