import { expect, tap } from '@git.zone/tstest/tapbundle'; import * as smartdb from '../ts/index.js'; import { MongoClient, Db } from 'mongodb'; import * as fs from 'fs'; import * as path from 'path'; import * as os from 'os'; // --------------------------------------------------------------------------- // Helpers // --------------------------------------------------------------------------- let tmpDir: string; let server: smartdb.SmartdbServer; let client: MongoClient; let db: Db; function makeTmpDir(): string { return fs.mkdtempSync(path.join(os.tmpdir(), 'smartdb-test-')); } function cleanTmpDir(dir: string): void { if (fs.existsSync(dir)) { fs.rmSync(dir, { recursive: true, force: true }); } } // ============================================================================ // File Storage: Startup // ============================================================================ tap.test('file-storage: should start server with file storage', async () => { tmpDir = makeTmpDir(); server = new smartdb.SmartdbServer({ port: 27118, storage: 'file', storagePath: tmpDir, }); await server.start(); expect(server.running).toBeTrue(); }); tap.test('file-storage: should connect MongoClient', async () => { client = new MongoClient('mongodb://127.0.0.1:27118', { directConnection: true, serverSelectionTimeoutMS: 5000, }); await client.connect(); db = client.db('filetest'); expect(db).toBeTruthy(); }); // ============================================================================ // File Storage: Data files are created on disk // ============================================================================ tap.test('file-storage: inserting creates data files on disk', async () => { const coll = db.collection('diskcheck'); await coll.insertOne({ name: 'disk-test', value: 42 }); // The storage directory should now contain a database directory const dbDir = path.join(tmpDir, 'filetest'); expect(fs.existsSync(dbDir)).toBeTrue(); // Collection directory with data.rdb should exist const collDir = path.join(dbDir, 'diskcheck'); expect(fs.existsSync(collDir)).toBeTrue(); const dataFile = path.join(collDir, 'data.rdb'); expect(fs.existsSync(dataFile)).toBeTrue(); // data.rdb should have the SMARTDB magic header const header = Buffer.alloc(8); const fd = fs.openSync(dataFile, 'r'); fs.readSync(fd, header, 0, 8, 0); fs.closeSync(fd); expect(header.toString('ascii')).toEqual('SMARTDB\0'); }); // ============================================================================ // File Storage: Full CRUD cycle // ============================================================================ tap.test('file-storage: insertOne returns valid id', async () => { const coll = db.collection('crud'); const result = await coll.insertOne({ name: 'Alice', age: 30 }); expect(result.acknowledged).toBeTrue(); expect(result.insertedId).toBeTruthy(); }); tap.test('file-storage: insertMany returns all ids', async () => { const coll = db.collection('crud'); const result = await coll.insertMany([ { name: 'Bob', age: 25 }, { name: 'Charlie', age: 35 }, { name: 'Diana', age: 28 }, { name: 'Eve', age: 32 }, ]); expect(result.insertedCount).toEqual(4); }); tap.test('file-storage: findOne retrieves correct document', async () => { const coll = db.collection('crud'); const doc = await coll.findOne({ name: 'Alice' }); expect(doc).toBeTruthy(); expect(doc!.name).toEqual('Alice'); expect(doc!.age).toEqual(30); }); tap.test('file-storage: find with filter returns correct subset', async () => { const coll = db.collection('crud'); const docs = await coll.find({ age: { $gte: 30 } }).toArray(); expect(docs.length).toEqual(3); // Alice(30), Charlie(35), Eve(32) expect(docs.every(d => d.age >= 30)).toBeTrue(); }); tap.test('file-storage: updateOne modifies document', async () => { const coll = db.collection('crud'); const result = await coll.updateOne( { name: 'Alice' }, { $set: { age: 31, updated: true } } ); expect(result.modifiedCount).toEqual(1); const doc = await coll.findOne({ name: 'Alice' }); expect(doc!.age).toEqual(31); expect(doc!.updated).toBeTrue(); }); tap.test('file-storage: deleteOne removes document', async () => { const coll = db.collection('crud'); const result = await coll.deleteOne({ name: 'Eve' }); expect(result.deletedCount).toEqual(1); const doc = await coll.findOne({ name: 'Eve' }); expect(doc).toBeNull(); }); tap.test('file-storage: count reflects current state', async () => { const coll = db.collection('crud'); const count = await coll.countDocuments(); expect(count).toEqual(4); // 5 inserted - 1 deleted = 4 }); // ============================================================================ // File Storage: Persistence across server restart // ============================================================================ tap.test('file-storage: stop server for restart test', async () => { await client.close(); await server.stop(); expect(server.running).toBeFalse(); }); tap.test('file-storage: restart server with same data path', async () => { server = new smartdb.SmartdbServer({ port: 27118, storage: 'file', storagePath: tmpDir, }); await server.start(); expect(server.running).toBeTrue(); client = new MongoClient('mongodb://127.0.0.1:27118', { directConnection: true, serverSelectionTimeoutMS: 5000, }); await client.connect(); db = client.db('filetest'); }); tap.test('file-storage: data persists after restart', async () => { const coll = db.collection('crud'); // Alice should still be there with updated age const alice = await coll.findOne({ name: 'Alice' }); expect(alice).toBeTruthy(); expect(alice!.age).toEqual(31); expect(alice!.updated).toBeTrue(); // Bob, Charlie, Diana should be there const bob = await coll.findOne({ name: 'Bob' }); expect(bob).toBeTruthy(); expect(bob!.age).toEqual(25); const charlie = await coll.findOne({ name: 'Charlie' }); expect(charlie).toBeTruthy(); const diana = await coll.findOne({ name: 'Diana' }); expect(diana).toBeTruthy(); // Eve should still be deleted const eve = await coll.findOne({ name: 'Eve' }); expect(eve).toBeNull(); }); tap.test('file-storage: count is correct after restart', async () => { const coll = db.collection('crud'); const count = await coll.countDocuments(); expect(count).toEqual(4); }); tap.test('file-storage: can write new data after restart', async () => { const coll = db.collection('crud'); const result = await coll.insertOne({ name: 'Frank', age: 45 }); expect(result.acknowledged).toBeTrue(); const doc = await coll.findOne({ name: 'Frank' }); expect(doc).toBeTruthy(); expect(doc!.age).toEqual(45); const count = await coll.countDocuments(); expect(count).toEqual(5); }); // ============================================================================ // File Storage: Multiple collections in same database // ============================================================================ tap.test('file-storage: multiple collections are independent', async () => { const products = db.collection('products'); const orders = db.collection('orders'); await products.insertMany([ { sku: 'A001', name: 'Widget', price: 9.99 }, { sku: 'A002', name: 'Gadget', price: 19.99 }, ]); await orders.insertMany([ { orderId: 1, sku: 'A001', qty: 3 }, { orderId: 2, sku: 'A002', qty: 1 }, { orderId: 3, sku: 'A001', qty: 2 }, ]); const productCount = await products.countDocuments(); const orderCount = await orders.countDocuments(); expect(productCount).toEqual(2); expect(orderCount).toEqual(3); // Deleting from one collection doesn't affect the other await products.deleteOne({ sku: 'A001' }); expect(await products.countDocuments()).toEqual(1); expect(await orders.countDocuments()).toEqual(3); }); // ============================================================================ // File Storage: Multiple databases // ============================================================================ tap.test('file-storage: multiple databases are independent', async () => { const db2 = client.db('filetest2'); const coll2 = db2.collection('items'); await coll2.insertOne({ name: 'cross-db-test', source: 'db2' }); // db2 has 1 doc const count2 = await coll2.countDocuments(); expect(count2).toEqual(1); // original db is unaffected const crudCount = await db.collection('crud').countDocuments(); expect(crudCount).toEqual(5); await db2.dropDatabase(); }); // ============================================================================ // File Storage: Large batch insert and retrieval // ============================================================================ tap.test('file-storage: bulk insert 1000 documents', async () => { const coll = db.collection('bulk'); const docs = []; for (let i = 0; i < 1000; i++) { docs.push({ index: i, data: `value-${i}`, timestamp: Date.now() }); } const result = await coll.insertMany(docs); expect(result.insertedCount).toEqual(1000); }); tap.test('file-storage: find all 1000 documents', async () => { const coll = db.collection('bulk'); const docs = await coll.find({}).toArray(); expect(docs.length).toEqual(1000); }); tap.test('file-storage: range query on 1000 documents', async () => { const coll = db.collection('bulk'); const docs = await coll.find({ index: { $gte: 500, $lt: 600 } }).toArray(); expect(docs.length).toEqual(100); expect(docs.every(d => d.index >= 500 && d.index < 600)).toBeTrue(); }); tap.test('file-storage: sorted retrieval with limit', async () => { const coll = db.collection('bulk'); const docs = await coll.find({}).sort({ index: -1 }).limit(10).toArray(); expect(docs.length).toEqual(10); expect(docs[0].index).toEqual(999); expect(docs[9].index).toEqual(990); }); // ============================================================================ // File Storage: Update many and verify persistence // ============================================================================ tap.test('file-storage: updateMany on bulk collection', async () => { const coll = db.collection('bulk'); const result = await coll.updateMany( { index: { $lt: 100 } }, { $set: { batch: 'first-hundred' } } ); expect(result.modifiedCount).toEqual(100); const updated = await coll.find({ batch: 'first-hundred' }).toArray(); expect(updated.length).toEqual(100); }); // ============================================================================ // File Storage: Delete many and verify // ============================================================================ tap.test('file-storage: deleteMany removes correct documents', async () => { const coll = db.collection('bulk'); const result = await coll.deleteMany({ index: { $gte: 900 } }); expect(result.deletedCount).toEqual(100); const remaining = await coll.countDocuments(); expect(remaining).toEqual(900); }); // ============================================================================ // File Storage: Persistence of bulk data across restart // ============================================================================ tap.test('file-storage: stop server for bulk restart test', async () => { await client.close(); await server.stop(); expect(server.running).toBeFalse(); }); tap.test('file-storage: restart and verify bulk data', async () => { server = new smartdb.SmartdbServer({ port: 27118, storage: 'file', storagePath: tmpDir, }); await server.start(); client = new MongoClient('mongodb://127.0.0.1:27118', { directConnection: true, serverSelectionTimeoutMS: 5000, }); await client.connect(); db = client.db('filetest'); const coll = db.collection('bulk'); const count = await coll.countDocuments(); expect(count).toEqual(900); // Verify the updateMany persisted const firstHundred = await coll.find({ batch: 'first-hundred' }).toArray(); expect(firstHundred.length).toEqual(100); // Verify deleted docs are gone const over900 = await coll.find({ index: { $gte: 900 } }).toArray(); expect(over900.length).toEqual(0); }); // ============================================================================ // File Storage: Index persistence // ============================================================================ tap.test('file-storage: default indexes.json exists on disk', async () => { // The indexes.json is created when the collection is first created, // containing the default _id_ index spec. const indexFile = path.join(tmpDir, 'filetest', 'crud', 'indexes.json'); expect(fs.existsSync(indexFile)).toBeTrue(); const indexData = JSON.parse(fs.readFileSync(indexFile, 'utf-8')); const names = indexData.map((i: any) => i.name); expect(names).toContain('_id_'); }); // ============================================================================ // Cleanup // ============================================================================ tap.test('file-storage: cleanup', async () => { await client.close(); await server.stop(); expect(server.running).toBeFalse(); cleanTmpDir(tmpDir); }); export default tap.start();