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