UNPKG

@joystick.js/db-canary

Version:

JoystickDB - A minimalist database server for the Joystick framework

256 lines (197 loc) 7.33 kB
import test from 'ava'; import { get_write_queue, shutdown_write_queue } from '../../../src/server/lib/write_queue.js'; test.beforeEach(async (t) => { await shutdown_write_queue(); t.context.write_queue = get_write_queue(); }); test.afterEach(async (t) => { await shutdown_write_queue(); }); test('write queue should process operations sequentially', async (t) => { const write_queue = t.context.write_queue; const execution_order = []; const create_operation = (id, delay = 10) => { return async () => { await new Promise(resolve => setTimeout(resolve, delay)); execution_order.push(id); return { id }; }; }; const promises = [ write_queue.enqueue_write_operation(create_operation('op1'), { test: 'op1' }), write_queue.enqueue_write_operation(create_operation('op2'), { test: 'op2' }), write_queue.enqueue_write_operation(create_operation('op3'), { test: 'op3' }) ]; const results = await Promise.all(promises); t.deepEqual(execution_order, ['op1', 'op2', 'op3']); t.is(results.length, 3); t.is(results[0].id, 'op1'); t.is(results[1].id, 'op2'); t.is(results[2].id, 'op3'); }); test('write queue should handle operation failures', async (t) => { const write_queue = t.context.write_queue; const failing_operation = async () => { throw new Error('Operation failed'); }; const successful_operation = async () => { return { success: true }; }; const error = await t.throwsAsync( write_queue.enqueue_write_operation(failing_operation, { test: 'failing' }) ); t.is(error.message, 'Operation failed'); const result = await write_queue.enqueue_write_operation( successful_operation, { test: 'successful' } ); t.deepEqual(result, { success: true }); }); test('write queue should retry retryable errors', async (t) => { const write_queue = t.context.write_queue; let attempt_count = 0; const retryable_operation = async () => { attempt_count++; if (attempt_count < 3) { const error = new Error('MDB_MAP_FULL: Database map is full'); throw error; } return { success: true, attempts: attempt_count }; }; const result = await write_queue.enqueue_write_operation( retryable_operation, { test: 'retryable' } ); t.is(attempt_count, 3); t.deepEqual(result, { success: true, attempts: 3 }); }); test('write queue should not retry non-retryable errors', async (t) => { const write_queue = t.context.write_queue; let attempt_count = 0; const non_retryable_operation = async () => { attempt_count++; throw new Error('Invalid document format'); }; const error = await t.throwsAsync( write_queue.enqueue_write_operation(non_retryable_operation, { test: 'non_retryable' }) ); t.is(attempt_count, 1); t.is(error.message, 'Invalid document format'); }); test('write queue should track statistics correctly', async (t) => { const write_queue = t.context.write_queue; const successful_operation = async () => { await new Promise(resolve => setTimeout(resolve, 50)); return { success: true }; }; const failing_operation = async () => { throw new Error('Operation failed'); }; await write_queue.enqueue_write_operation(successful_operation, { test: 'success1' }); await write_queue.enqueue_write_operation(successful_operation, { test: 'success2' }); await t.throwsAsync( write_queue.enqueue_write_operation(failing_operation, { test: 'failure' }) ); const stats = write_queue.get_stats(); t.is(stats.total_operations, 3); t.is(stats.completed_operations, 2); t.is(stats.failed_operations, 1); t.is(stats.current_queue_depth, 0); t.true(stats.avg_wait_time_ms >= 0); t.true(stats.avg_processing_time_ms >= 0); t.is(stats.success_rate, 67); }); test('write queue should handle concurrent enqueuing', async (t) => { const write_queue = t.context.write_queue; const execution_order = []; const create_operation = (id) => { return async () => { execution_order.push(id); return { id }; }; }; const promises = []; for (let i = 0; i < 10; i++) { promises.push( write_queue.enqueue_write_operation( create_operation(`op${i}`), { test: `op${i}` } ) ); } const results = await Promise.all(promises); t.is(results.length, 10); t.is(execution_order.length, 10); for (let i = 0; i < 10; i++) { t.is(execution_order[i], `op${i}`); t.is(results[i].id, `op${i}`); } }); test('write queue should clear statistics', async (t) => { const write_queue = t.context.write_queue; const operation = async () => ({ success: true }); await write_queue.enqueue_write_operation(operation, { test: 'clear_stats' }); let stats = write_queue.get_stats(); t.is(stats.total_operations, 1); t.is(stats.completed_operations, 1); write_queue.clear_stats(); stats = write_queue.get_stats(); t.is(stats.total_operations, 0); t.is(stats.completed_operations, 0); t.is(stats.failed_operations, 0); }); test('write queue should handle shutdown gracefully', async (t) => { const write_queue = t.context.write_queue; const quick_operation = async () => { return { success: true }; }; const promise1 = write_queue.enqueue_write_operation(quick_operation, { test: 'shutdown1' }); const result1 = await promise1; t.deepEqual(result1, { success: true }); write_queue.shutdown(); const promise2 = write_queue.enqueue_write_operation(quick_operation, { test: 'shutdown2' }); const error = await t.throwsAsync(promise2); t.is(error.message, 'Server shutting down'); }); test('write queue should calculate backoff delay correctly', async (t) => { const write_queue = t.context.write_queue; const delay1 = write_queue.calculate_backoff_delay(1); const delay2 = write_queue.calculate_backoff_delay(2); const delay3 = write_queue.calculate_backoff_delay(3); t.true(delay1 >= 100 && delay1 <= 200); t.true(delay2 >= 200 && delay2 <= 400); t.true(delay3 >= 400 && delay3 <= 800); const delay_max = write_queue.calculate_backoff_delay(10); t.true(delay_max <= 5000); }); test('write queue should identify retryable errors correctly', async (t) => { const write_queue = t.context.write_queue; const retryable_errors = [ new Error('MDB_MAP_FULL: Database map is full'), new Error('MDB_TXN_FULL: Transaction is full'), new Error('MDB_READERS_FULL: Too many readers'), { code: 'EAGAIN', message: 'Resource temporarily unavailable' }, { code: 'EBUSY', message: 'Resource busy' } ]; const non_retryable_errors = [ new Error('Invalid document format'), new Error('Collection not found'), { code: 'ENOENT', message: 'File not found' } ]; for (const error of retryable_errors) { t.true(write_queue.is_retryable_error(error)); } for (const error of non_retryable_errors) { t.false(write_queue.is_retryable_error(error)); } }); test('write queue should generate unique operation IDs', async (t) => { const write_queue = t.context.write_queue; const ids = new Set(); for (let i = 0; i < 100; i++) { const id = write_queue.generate_operation_id(); t.false(ids.has(id)); ids.add(id); } t.is(ids.size, 100); });