@joystick.js/db-canary
Version:
JoystickDB - A minimalist database server for the Joystick framework
302 lines (233 loc) • 10.5 kB
JavaScript
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');
});