UNPKG

@joystick.js/db-canary

Version:

JoystickDB - A minimalist database server for the Joystick framework

247 lines (189 loc) 7.46 kB
import test from 'ava'; import { initialize_database, get_database, cleanup_database } from '../../../src/server/lib/query_engine.js'; import { get_write_queue, shutdown_write_queue } from '../../../src/server/lib/write_queue.js'; import insert_one from '../../../src/server/lib/operations/insert_one.js'; import update_one from '../../../src/server/lib/operations/update_one.js'; import delete_one from '../../../src/server/lib/operations/delete_one.js'; import bulk_write from '../../../src/server/lib/operations/bulk_write.js'; import find_one from '../../../src/server/lib/operations/find_one.js'; test.before(async () => { // Set test environment process.env.NODE_ENV = 'test'; // Initialize database once initialize_database('./test_data'); }); test.after.always(async () => { // Final cleanup - shutdown write queue first try { await shutdown_write_queue(); } catch (error) { // Ignore cleanup errors } // Proper database cleanup including index databases try { await cleanup_database(); } catch (error) { // Ignore cleanup errors } // Force garbage collection if available if (global.gc) { global.gc(); } }); const cleanup_test_data = async () => { try { await shutdown_write_queue(); } catch (error) { // Ignore shutdown errors } const db = get_database(); try { await db.transaction(() => { const range = db.getRange(); const keys_to_remove = []; for (const { key } of range) { if (typeof key === 'string' && key.includes(':')) { keys_to_remove.push(key); } } for (const key of keys_to_remove) { try { db.remove(key); } catch (error) { // Ignore individual key removal errors } } }); } catch (error) { // Ignore cleanup errors - database might be in inconsistent state } }; test('concurrent write operations should be serialized', async (t) => { await cleanup_test_data(); const collection = 'test_serialization'; const promises = [ insert_one('default', collection, { name: 'test1', value: 1 }), update_one('default', collection, { name: 'test1' }, { $set: { value: 2 } }), delete_one('default', collection, { name: 'test1' }) ]; const results = await Promise.all(promises); t.is(results[0].acknowledged, true); t.is(results[1].acknowledged, true); t.is(results[2].acknowledged, true); const write_queue = get_write_queue(); const stats = write_queue.get_stats(); t.is(stats.total_operations, 3); t.is(stats.completed_operations, 3); t.is(stats.failed_operations, 0); }); test('read operations should not be blocked by write queue', async (t) => { await cleanup_test_data(); const collection = 'test_read_write'; await insert_one('default', collection, { name: 'test', value: 1 }); const slow_write_promise = insert_one('default', collection, { name: 'slow', value: 2 }); const read_start = Date.now(); const read_result = await find_one('default', collection, { name: 'test' }); const read_duration = Date.now() - read_start; t.true(read_duration < 100); t.is(read_result.name, 'test'); t.is(read_result.value, 1); await slow_write_promise; }); test('bulk write operations should be atomic', async (t) => { await cleanup_test_data(); const collection = 'test_bulk_atomic'; const operations = [ { insertOne: { document: { name: 'doc1', value: 1 } } }, { insertOne: { document: { name: 'doc2', value: 2 } } }, { insertOne: { document: { name: 'doc3', value: 3 } } } ]; const result = await bulk_write('default', collection, operations); t.is(result.acknowledged, true); t.is(result.inserted_count, 3); const doc1 = await find_one('default', collection, { name: 'doc1' }); const doc2 = await find_one('default', collection, { name: 'doc2' }); const doc3 = await find_one('default', collection, { name: 'doc3' }); t.is(doc1.value, 1); t.is(doc2.value, 2); t.is(doc3.value, 3); }); test('write queue should handle high concurrency', async (t) => { await cleanup_test_data(); const collection = 'test_high_concurrency'; const operation_count = 50; const promises = []; for (let i = 0; i < operation_count; i++) { promises.push( insert_one('default', collection, { name: `doc${i}`, value: i }) ); } const results = await Promise.all(promises); t.is(results.length, operation_count); for (let i = 0; i < operation_count; i++) { t.is(results[i].acknowledged, true); t.truthy(results[i].inserted_id); } const write_queue = get_write_queue(); const stats = write_queue.get_stats(); t.is(stats.total_operations, operation_count); t.is(stats.completed_operations, operation_count); t.is(stats.failed_operations, 0); t.is(stats.current_queue_depth, 0); }); test('write queue should maintain operation ordering', async (t) => { await cleanup_test_data(); const collection = 'test_ordering'; const document_id = `test_doc_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; await insert_one('default', collection, { _id: document_id, counter: 0 }); const update_promises = []; for (let i = 1; i <= 10; i++) { update_promises.push( update_one('default', collection, { _id: document_id }, { $inc: { counter: 1 } }) ); } await Promise.all(update_promises); const final_doc = await find_one('default', collection, { _id: document_id }); t.is(final_doc.counter, 10); }); test('write queue should handle mixed operation types', async (t) => { await cleanup_test_data(); const collection = 'test_mixed_ops'; const promises = [ insert_one('default', collection, { name: 'doc1', value: 1 }), insert_one('default', collection, { name: 'doc2', value: 2 }), update_one('default', collection, { name: 'doc1' }, { $set: { value: 10 } }), insert_one('default', collection, { name: 'doc3', value: 3 }), delete_one('default', collection, { name: 'doc2' }), bulk_write('default', collection, [ { insertOne: { document: { name: 'doc4', value: 4 } } }, { updateOne: { filter: { name: 'doc3' }, update: { $set: { value: 30 } } } } ]) ]; const results = await Promise.all(promises); t.is(results.length, 6); for (const result of results) { t.is(result.acknowledged, true); } const doc1 = await find_one('default', collection, { name: 'doc1' }); const doc2 = await find_one('default', collection, { name: 'doc2' }); const doc3 = await find_one('default', collection, { name: 'doc3' }); const doc4 = await find_one('default', collection, { name: 'doc4' }); t.is(doc1.value, 10); t.is(doc2, null); t.is(doc3.value, 30); t.is(doc4.value, 4); }); test('write queue statistics should be included in admin response', async (t) => { await cleanup_test_data(); const collection = 'test_admin_stats'; await insert_one('default', collection, { name: 'test', value: 1 }); await update_one('default', collection, { name: 'test' }, { $set: { value: 2 } }); const write_queue = get_write_queue(); const stats = write_queue.get_stats(); t.is(stats.total_operations, 2); t.is(stats.completed_operations, 2); t.is(stats.failed_operations, 0); t.true(stats.avg_wait_time_ms >= 0); t.true(stats.avg_processing_time_ms >= 0); t.is(stats.success_rate, 100); });