@naturalcycles/db-lib
Version:
Lowest Common Denominator API to supported Databases
308 lines (307 loc) • 13.2 kB
JavaScript
;
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);
}
}
}