UNPKG

@naturalcycles/db-lib

Version:

Lowest Common Denominator API to supported Databases

308 lines (307 loc) 13.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.runCommonDBTest = runCommonDBTest; const js_lib_1 = require("@naturalcycles/js-lib"); const common_db_1 = require("../common.db"); const dbQuery_1 = require("../query/dbQuery"); const test_model_1 = require("./test.model"); async function runCommonDBTest(db, quirks = {}) { // this is because vitest cannot be "required" from cjs const { test, expect } = await import('vitest'); const { support } = db; const items = (0, test_model_1.createTestItemsDBM)(3); (0, js_lib_1._deepFreeze)(items); const item1 = items[0]; const queryAll = () => dbQuery_1.DBQuery.create(test_model_1.TEST_TABLE); test('ping', async () => { await db.ping(); }); // CREATE TABLE, DROP if (support.createTable) { test('createTable, dropIfExists=true', async () => { await db.createTable(test_model_1.TEST_TABLE, test_model_1.testItemBMJsonSchema, { dropIfExists: true }); }); } if (support.queries) { // DELETE ALL initially test('deleteByIds test items', async () => { const { rows } = await db.runQuery(queryAll().select(['id'])); await db.deleteByQuery(queryAll().filterIn('id', rows.map(i => i.id))); }); // QUERY empty test('runQuery(all), runQueryCount should return empty', async () => { expect((await db.runQuery(queryAll())).rows).toEqual([]); expect(await db.runQueryCount(queryAll())).toBe(0); }); } // GET empty test('getByIds(item1.id) should return empty', async () => { const [item1Loaded] = await db.getByIds(test_model_1.TEST_TABLE, [item1.id]); // console.log(a) expect(item1Loaded).toBeUndefined(); }); test('getByIds([]) should return []', async () => { expect(await db.getByIds(test_model_1.TEST_TABLE, [])).toEqual([]); }); test('getByIds(...) should return empty', async () => { expect(await db.getByIds(test_model_1.TEST_TABLE, ['abc', 'abcd'])).toEqual([]); }); // TimeMachine if (support.timeMachine) { test('getByIds(...) 10 minutes ago should return []', async () => { expect(await db.getByIds(test_model_1.TEST_TABLE, [item1.id, 'abc'], { readAt: js_lib_1.localTime.now().minus(10, 'minute').unix, })).toEqual([]); }); } // SAVE if (support.nullValues) { test('should allow to save and load null values', async () => { const item3 = { ...(0, test_model_1.createTestItemDBM)(3), k2: null, }; (0, js_lib_1._deepFreeze)(item3); await db.saveBatch(test_model_1.TEST_TABLE, [item3]); const item3Loaded = (await db.getByIds(test_model_1.TEST_TABLE, [item3.id]))[0]; expectMatch([item3], [item3Loaded], quirks); expect(item3Loaded.k2).toBeNull(); }); } if (db.dbType === common_db_1.CommonDBType.document) { test('undefined values should not be saved/loaded', async () => { const item3 = { ...(0, test_model_1.createTestItemDBM)(3), k2: undefined, }; (0, js_lib_1._deepFreeze)(item3); const expected = { ...item3 }; delete expected.k2; await db.saveBatch(test_model_1.TEST_TABLE, [item3]); const item3Loaded = (await db.getByIds(test_model_1.TEST_TABLE, [item3.id]))[0]; expectMatch([expected], [item3Loaded], quirks); expect(item3Loaded.k2).toBeUndefined(); expect(Object.keys(item3Loaded)).not.toContain('k2'); }); } if (support.updateSaveMethod) { test('saveBatch UPDATE method should throw', async () => { await expect(db.saveBatch(test_model_1.TEST_TABLE, items, { saveMethod: 'update' })).rejects.toThrow(); }); } test('saveBatch test items', async () => { await db.saveBatch(test_model_1.TEST_TABLE, items); }); test('saveBatch should throw on null id', async () => { await expect(db.saveBatch(test_model_1.TEST_TABLE, [{ ...item1, id: null }])).rejects.toThrow(); }); if (support.insertSaveMethod) { test('saveBatch INSERT method should throw', async () => { await expect(db.saveBatch(test_model_1.TEST_TABLE, items, { saveMethod: 'insert' })).rejects.toThrow(); }); } if (support.updateSaveMethod) { test('saveBatch UPDATE method should pass', async () => { await db.saveBatch(test_model_1.TEST_TABLE, items, { saveMethod: 'update' }); }); } // GET not empty test('getByIds all items', async () => { const rows = await db.getByIds(test_model_1.TEST_TABLE, items.map(i => i.id).concat('abcd')); expectMatch(items, (0, js_lib_1._sortBy)(rows, r => r.id), quirks); }); // QUERY if (support.queries) { test('runQuery(all) should return all items', async () => { let { rows } = await db.runQuery(queryAll()); rows = (0, js_lib_1._sortBy)(rows, r => r.id); // because query doesn't specify order here expectMatch(items, rows, quirks); }); if (support.dbQueryFilter) { test('query even=true', async () => { const q = new dbQuery_1.DBQuery(test_model_1.TEST_TABLE).filter('even', '==', true); let { rows } = await db.runQuery(q); if (!support.dbQueryOrder) rows = (0, js_lib_1._sortBy)(rows, r => r.id); expectMatch(items.filter(i => i.even), rows, quirks); }); } if (support.dbQueryOrder) { test('query order by k1 desc', async () => { const q = new dbQuery_1.DBQuery(test_model_1.TEST_TABLE).order('k1', true); const { rows } = await db.runQuery(q); expectMatch([...items].reverse(), rows, quirks); }); } if (support.dbQuerySelectFields) { test('projection query with only ids', async () => { const q = new dbQuery_1.DBQuery(test_model_1.TEST_TABLE).select(['id']); let { rows } = await db.runQuery(q); rows = (0, js_lib_1._sortBy)(rows, r => r.id); // cause order is not specified expectMatch(items.map(item => (0, js_lib_1._pick)(item, ['id'])), rows, quirks); }); test('projection query without ids', async () => { const q = new dbQuery_1.DBQuery(test_model_1.TEST_TABLE).select(['k1']); let { rows } = await db.runQuery(q); rows = (0, js_lib_1._sortBy)(rows, r => r.k1); // cause order is not specified expectMatch(items.map(item => (0, js_lib_1._pick)(item, ['k1'])), rows, quirks); }); test('projection query empty fields (edge case)', async () => { const q = new dbQuery_1.DBQuery(test_model_1.TEST_TABLE).select([]); const { rows } = await db.runQuery(q); expectMatch(items.map(() => ({})), rows, quirks); }); } test('runQueryCount should return 3', async () => { expect(await db.runQueryCount(new dbQuery_1.DBQuery(test_model_1.TEST_TABLE))).toBe(3); }); } // STREAM if (support.streaming) { test('streamQuery all', async () => { let rows = await db.streamQuery(queryAll()).toArray(); rows = (0, js_lib_1._sortBy)(rows, r => r.id); // cause order is not specified in DBQuery expectMatch(items, rows, quirks); }); } // getTables test('getTables, getTableSchema (if supported)', async () => { const tables = await db.getTables(); // console.log({ tables }) if (support.tableSchemas) { await (0, js_lib_1.pMap)(tables, async (table) => { const schema = await db.getTableSchema(table); // console.log(schema) expect(schema.$id).toBe(`${table}.schema.json`); }); } }); // DELETE BY if (support.queries && support.dbQueryFilter) { test('deleteByQuery even=false', async () => { const q = new dbQuery_1.DBQuery(test_model_1.TEST_TABLE).filter('even', '==', false); const deleted = await db.deleteByQuery(q); expect(deleted).toBe(items.filter(item => !item.even).length); expect(await db.runQueryCount(queryAll())).toBe(1); }); } // BUFFER if (support.bufferValues) { test('buffer values', async () => { const s = 'helloWorld 1'; const b1 = Buffer.from(s); const item = { ...(0, test_model_1.createTestItemDBM)(1), b1, }; await db.saveBatch(test_model_1.TEST_TABLE, [item]); const loaded = (await db.getByIds(test_model_1.TEST_TABLE, [item.id]))[0]; const b1Loaded = loaded.b1; // console.log({ // b11: typeof b1, // b12: typeof b1Loaded, // l1: b1.length, // l2: b1Loaded.length, // b1, // b1Loaded, // }) expect(b1Loaded).toEqual(b1); expect(b1Loaded.toString()).toBe(s); }); } if (support.transactions) { test('transaction happy path', async () => { // cleanup await db.deleteByQuery(queryAll()); // saveBatch [item1, 2, 3] // save item3 with k1: k1_mod // delete item2 // remaining: item1, item3_with_k1_mod await db.runInTransaction(async (tx) => { await tx.saveBatch(test_model_1.TEST_TABLE, items); await tx.saveBatch(test_model_1.TEST_TABLE, [{ ...items[2], k1: 'k1_mod' }]); await tx.deleteByIds(test_model_1.TEST_TABLE, [items[1].id]); }); const { rows } = await db.runQuery(queryAll()); const expected = [items[0], { ...items[2], k1: 'k1_mod' }]; expectMatch(expected, rows, quirks); }); test('transaction rollback', async () => { let err; try { await db.runInTransaction(async (tx) => { await tx.deleteByIds(test_model_1.TEST_TABLE, [items[2].id]); // It should fail on id == null await tx.saveBatch(test_model_1.TEST_TABLE, [{ ...items[0], k1: 5, id: null }]); }); } catch (err_) { err = err_; } expect(err).toBeDefined(); const { rows } = await db.runQuery(queryAll()); const expected = [items[0], { ...items[2], k1: 'k1_mod' }]; expectMatch(expected, rows, quirks); }); } if (support.patchByQuery) { test('patchByQuery', async () => { // cleanup, reset initial data await db.deleteByQuery(queryAll()); await db.saveBatch(test_model_1.TEST_TABLE, items); const patch = { k3: 5, k2: 'abc', }; await db.patchByQuery(dbQuery_1.DBQuery.create(test_model_1.TEST_TABLE).filterEq('even', true), patch); const { rows } = await db.runQuery(queryAll()); const expected = items.map(r => { if (r.even) { return { ...r, ...patch }; } return r; }); expectMatch(expected, rows, quirks); }); } if (support.increment) { test('incrementBatch', async () => { // cleanup, reset initial data await db.deleteByQuery(queryAll()); await db.saveBatch(test_model_1.TEST_TABLE, items); await db.incrementBatch(test_model_1.TEST_TABLE, 'k3', { id1: 1, id2: 2 }); const { rows } = await db.runQuery(queryAll()); const expected = items.map(r => { if (r.id === 'id1') { return { ...r, k3: 2 }; } if (r.id === 'id2') { return { ...r, k3: 4 }; } return r; }); expectMatch(expected, rows, quirks); }); } if (support.queries) { test('cleanup', async () => { // CLEAN UP await db.deleteByQuery(queryAll()); }); } function expectMatch(expected, actual, quirks) { // const expectedSorted = sortObjectDeep(expected) // const actualSorted = sortObjectDeep(actual) if (quirks.allowBooleansAsUndefined) { expected = expected.map(r => { return typeof r !== 'object' ? r : (0, js_lib_1._filterObject)(r, (_k, v) => v !== false); }); } if (quirks.allowExtraPropertiesInResponse) { expect(actual).toMatchObject(expected); } else { expect(actual).toEqual(expected); } } }