UNPKG

@joystick.js/db-canary

Version:

JoystickDB - A minimalist database server for the Joystick framework

302 lines (233 loc) 10.5 kB
import test from 'ava'; import { initialize_database, get_database, cleanup_database } from '../../../src/server/lib/query_engine.js'; import { initialize_index_database, get_index_database, create_index, drop_index, get_indexes, update_indexes_on_insert, update_indexes_on_update, update_indexes_on_delete, find_documents_by_index, can_use_index } from '../../../src/server/lib/index_manager.js'; import insert_one from '../../../src/server/lib/operations/insert_one.js'; test.beforeEach(() => { initialize_database('./test_data'); initialize_index_database(); const main_db = get_database(); const index_db = get_index_database(); // Clear both databases main_db.clearSync(); index_db.clearSync(); }); test.afterEach(async () => { try { await cleanup_database(); } catch (error) { // Ignore cleanup errors in tests } }); test('create_index - should create an index successfully', async (t) => { const result = await create_index('default', 'users', 'email'); t.is(result.acknowledged, true); const indexes = get_indexes('default', 'users'); t.is(indexes.length, 1); t.is(indexes[0].field, 'email'); t.is(indexes[0].collection, 'users'); }); test('create_index - should create a unique index', async (t) => { const result = await create_index('default', 'users', 'email', { unique: true }); t.is(result.acknowledged, true); const indexes = get_indexes('default', 'users'); t.is(indexes.length, 1); t.is(indexes[0].unique, true); }); test('create_index - should create a sparse index', async (t) => { const result = await create_index('default', 'users', 'email', { sparse: true }); t.is(result.acknowledged, true); const indexes = get_indexes('default', 'users'); t.is(indexes.length, 1); t.is(indexes[0].sparse, true); }); test('create_index - should throw if index already exists', async (t) => { await create_index('default', 'users', 'email'); await t.throwsAsync( () => create_index('default', 'users', 'email'), { message: 'Index on default.users.email already exists' } ); }); test('create_index - should throw if collection name is missing', async (t) => { await t.throwsAsync( () => create_index('default', '', 'email'), { message: 'Collection name is required' } ); }); test('create_index - should throw if field name is missing', async (t) => { await t.throwsAsync( () => create_index('default', 'users', ''), { message: 'Field name is required' } ); }); test('drop_index - should drop an index successfully', async (t) => { await create_index('default', 'users', 'email'); const result = await drop_index('default', 'users', 'email'); t.is(result.acknowledged, true); const indexes = get_indexes('default', 'users'); t.is(indexes.length, 0); }); test('drop_index - should throw if index does not exist', async (t) => { await t.throwsAsync( () => drop_index('default', 'users', 'email'), { message: 'Index on default.users.email does not exist' } ); }); test('get_indexes - should return empty array for collection with no indexes', (t) => { const indexes = get_indexes('default', 'users'); t.is(indexes.length, 0); }); test('get_indexes - should return all indexes for a collection', async (t) => { await create_index('default', 'users', 'email'); await create_index('default', 'users', 'name'); const indexes = get_indexes('default', 'users'); t.is(indexes.length, 2); const fields = indexes.map(index => index.field).sort(); t.deepEqual(fields, ['email', 'name']); }); test('update_indexes_on_insert - should update indexes when document is inserted', async (t) => { await create_index('default', 'users', 'email'); const document = { _id: 'test_id', email: 'test@example.com', name: 'Test User' }; await update_indexes_on_insert('default', 'users', document); const document_ids = find_documents_by_index('default', 'users', 'email', 'eq', 'test@example.com'); t.truthy(document_ids); t.is(document_ids.length, 1); t.is(document_ids[0], 'test_id'); }); test('update_indexes_on_insert - should handle null values with sparse index', async (t) => { await create_index('default', 'users', 'email', { sparse: true }); const document = { _id: 'test_id', name: 'Test User' }; await update_indexes_on_insert('default', 'users', document); const document_ids = find_documents_by_index('default', 'users', 'email', 'eq', null); t.is(document_ids, null); }); test('update_indexes_on_insert - should handle null values with non-sparse index', async (t) => { await create_index('default', 'users', 'email'); const document = { _id: 'test_id', name: 'Test User' }; await update_indexes_on_insert('default', 'users', document); const document_ids = find_documents_by_index('default', 'users', 'email', 'eq', null); t.truthy(document_ids); t.is(document_ids.length, 1); t.is(document_ids[0], 'test_id'); }); test('update_indexes_on_insert - should enforce unique constraint', async (t) => { await create_index('default', 'users', 'email', { unique: true }); const document1 = { _id: 'test_id_1', email: 'test@example.com' }; await update_indexes_on_insert('default', 'users', document1); const document2 = { _id: 'test_id_2', email: 'test@example.com' }; await t.throwsAsync( () => update_indexes_on_insert('default', 'users', document2), { message: 'Duplicate value for unique index on default.users.email: test@example.com' } ); }); test('update_indexes_on_update - should update indexes when document is updated', async (t) => { await create_index('default', 'users', 'email'); const old_document = { _id: 'test_id', email: 'old@example.com' }; const new_document = { _id: 'test_id', email: 'new@example.com' }; await update_indexes_on_insert('default', 'users', old_document); await update_indexes_on_update('default', 'users', old_document, new_document); const old_ids = find_documents_by_index('default', 'users', 'email', 'eq', 'old@example.com'); t.is(old_ids, null); const new_ids = find_documents_by_index('default', 'users', 'email', 'eq', 'new@example.com'); t.truthy(new_ids); t.is(new_ids.length, 1); t.is(new_ids[0], 'test_id'); }); test('update_indexes_on_delete - should update indexes when document is deleted', async (t) => { await create_index('default', 'users', 'email'); const document = { _id: 'test_id', email: 'test@example.com' }; await update_indexes_on_insert('default', 'users', document); await update_indexes_on_delete('default', 'users', document); const document_ids = find_documents_by_index('default', 'users', 'email', 'eq', 'test@example.com'); t.is(document_ids, null); }); test('find_documents_by_index - should find documents by exact match', async (t) => { await create_index('default', 'users', 'email'); const document = { _id: 'test_id', email: 'test@example.com' }; await update_indexes_on_insert('default', 'users', document); const document_ids = find_documents_by_index('default', 'users', 'email', '$eq', 'test@example.com'); t.truthy(document_ids); t.is(document_ids.length, 1); t.is(document_ids[0], 'test_id'); }); test('find_documents_by_index - should find documents by $in operator', async (t) => { await create_index('default', 'users', 'status'); const doc1 = { _id: 'id1', status: 'active' }; const doc2 = { _id: 'id2', status: 'inactive' }; const doc3 = { _id: 'id3', status: 'pending' }; await update_indexes_on_insert('default', 'users', doc1); await update_indexes_on_insert('default', 'users', doc2); await update_indexes_on_insert('default', 'users', doc3); const document_ids = find_documents_by_index('default', 'users', 'status', '$in', ['active', 'pending']); t.truthy(document_ids); t.is(document_ids.length, 2); t.true(document_ids.includes('id1')); t.true(document_ids.includes('id3')); }); test('find_documents_by_index - should find documents by $exists operator', async (t) => { await create_index('default', 'users', 'email'); const doc1 = { _id: 'id1', email: 'test@example.com' }; const doc2 = { _id: 'id2', name: 'Test User' }; await update_indexes_on_insert('default', 'users', doc1); await update_indexes_on_insert('default', 'users', doc2); const with_email = find_documents_by_index('default', 'users', 'email', '$exists', true); t.truthy(with_email); t.is(with_email.length, 1); t.is(with_email[0], 'id1'); const without_email = find_documents_by_index('default', 'users', 'email', '$exists', false); t.truthy(without_email); t.is(without_email.length, 1); t.is(without_email[0], 'id2'); }); test('can_use_index - should detect when index can be used', async (t) => { await create_index('default', 'users', 'email'); const filter1 = { email: 'test@example.com' }; const result1 = can_use_index('default', 'users', filter1); t.truthy(result1); t.is(result1.field, 'email'); t.deepEqual(result1.operators, ['eq']); const filter2 = { email: { $eq: 'test@example.com' } }; const result2 = can_use_index('default', 'users', filter2); t.truthy(result2); t.is(result2.field, 'email'); t.deepEqual(result2.operators, ['$eq']); const filter3 = { email: { $in: ['test1@example.com', 'test2@example.com'] } }; const result3 = can_use_index('default', 'users', filter3); t.truthy(result3); t.is(result3.field, 'email'); t.deepEqual(result3.operators, ['$in']); }); test('can_use_index - should return null when no index can be used', async (t) => { await create_index('default', 'users', 'email'); const filter1 = { name: 'Test User' }; const result1 = can_use_index('default', 'users', filter1); t.is(result1, null); const filter2 = { email: { $regex: 'test.*' } }; const result2 = can_use_index('default', 'users', filter2); t.is(result2, null); }); test('nested field indexing - should handle nested object fields', async (t) => { await create_index('default', 'users', 'profile.email'); const document = { _id: 'test_id', profile: { email: 'test@example.com', name: 'Test User' } }; await update_indexes_on_insert('default', 'users', document); const document_ids = find_documents_by_index('default', 'users', 'profile.email', 'eq', 'test@example.com'); t.truthy(document_ids); t.is(document_ids.length, 1); t.is(document_ids[0], 'test_id'); });