import { expect, tap } from '@git.zone/tstest/tapbundle'; import * as smartmongo from '../ts/index.js'; const { IndexEngine, MemoryStorageAdapter, ObjectId } = smartmongo.tsmdb; let storage: InstanceType; let indexEngine: InstanceType; const TEST_DB = 'testdb'; const TEST_COLL = 'indextest'; // ============================================================================ // Setup // ============================================================================ tap.test('indexengine: should create IndexEngine instance', async () => { storage = new MemoryStorageAdapter(); await storage.initialize(); await storage.createCollection(TEST_DB, TEST_COLL); indexEngine = new IndexEngine(TEST_DB, TEST_COLL, storage); expect(indexEngine).toBeTruthy(); }); // ============================================================================ // Index Creation Tests // ============================================================================ tap.test('indexengine: createIndex should create single-field index', async () => { const indexName = await indexEngine.createIndex({ name: 1 }); expect(indexName).toEqual('name_1'); const exists = await indexEngine.indexExists('name_1'); expect(exists).toBeTrue(); }); tap.test('indexengine: createIndex should create compound index', async () => { const indexName = await indexEngine.createIndex({ city: 1, state: -1 }); expect(indexName).toEqual('city_1_state_-1'); const exists = await indexEngine.indexExists('city_1_state_-1'); expect(exists).toBeTrue(); }); tap.test('indexengine: createIndex should use custom name if provided', async () => { const indexName = await indexEngine.createIndex({ email: 1 }, { name: 'custom_email_index' }); expect(indexName).toEqual('custom_email_index'); const exists = await indexEngine.indexExists('custom_email_index'); expect(exists).toBeTrue(); }); tap.test('indexengine: createIndex should handle unique option', async () => { const indexName = await indexEngine.createIndex({ uniqueField: 1 }, { unique: true }); expect(indexName).toEqual('uniqueField_1'); const indexes = await indexEngine.listIndexes(); const uniqueIndex = indexes.find(i => i.name === 'uniqueField_1'); expect(uniqueIndex!.unique).toBeTrue(); }); tap.test('indexengine: createIndex should handle sparse option', async () => { const indexName = await indexEngine.createIndex({ sparseField: 1 }, { sparse: true }); expect(indexName).toEqual('sparseField_1'); const indexes = await indexEngine.listIndexes(); const sparseIndex = indexes.find(i => i.name === 'sparseField_1'); expect(sparseIndex!.sparse).toBeTrue(); }); tap.test('indexengine: createIndex should return existing index name if already exists', async () => { const indexName1 = await indexEngine.createIndex({ existingField: 1 }, { name: 'existing_idx' }); const indexName2 = await indexEngine.createIndex({ existingField: 1 }, { name: 'existing_idx' }); expect(indexName1).toEqual(indexName2); }); // ============================================================================ // Index Listing and Existence Tests // ============================================================================ tap.test('indexengine: listIndexes should return all indexes', async () => { const indexes = await indexEngine.listIndexes(); expect(indexes.length).toBeGreaterThanOrEqual(5); // _id_ + created indexes expect(indexes.some(i => i.name === '_id_')).toBeTrue(); expect(indexes.some(i => i.name === 'name_1')).toBeTrue(); }); tap.test('indexengine: indexExists should return true for existing index', async () => { const exists = await indexEngine.indexExists('name_1'); expect(exists).toBeTrue(); }); tap.test('indexengine: indexExists should return false for non-existent index', async () => { const exists = await indexEngine.indexExists('nonexistent_index'); expect(exists).toBeFalse(); }); // ============================================================================ // Document Operations and Index Updates // ============================================================================ tap.test('indexengine: should insert documents for index testing', async () => { // Create a fresh index engine for document operations await storage.dropCollection(TEST_DB, TEST_COLL); await storage.createCollection(TEST_DB, TEST_COLL); indexEngine = new IndexEngine(TEST_DB, TEST_COLL, storage); // Create indexes first await indexEngine.createIndex({ age: 1 }); await indexEngine.createIndex({ category: 1 }); // Insert test documents const docs = [ { _id: new ObjectId(), name: 'Alice', age: 25, category: 'A' }, { _id: new ObjectId(), name: 'Bob', age: 30, category: 'B' }, { _id: new ObjectId(), name: 'Charlie', age: 35, category: 'A' }, { _id: new ObjectId(), name: 'Diana', age: 28, category: 'C' }, { _id: new ObjectId(), name: 'Eve', age: 30, category: 'B' }, ]; for (const doc of docs) { const stored = await storage.insertOne(TEST_DB, TEST_COLL, doc); await indexEngine.onInsert(stored); } }); tap.test('indexengine: onInsert should update indexes', async () => { const newDoc = { _id: new ObjectId(), name: 'Frank', age: 40, category: 'D', }; const stored = await storage.insertOne(TEST_DB, TEST_COLL, newDoc); await indexEngine.onInsert(stored); // Find by the indexed field const candidates = await indexEngine.findCandidateIds({ age: 40 }); expect(candidates).toBeTruthy(); expect(candidates!.size).toEqual(1); }); tap.test('indexengine: onUpdate should update indexes correctly', async () => { // Get an existing document const docs = await storage.findAll(TEST_DB, TEST_COLL); const oldDoc = docs.find(d => d.name === 'Alice')!; // Update the document const newDoc = { ...oldDoc, age: 26 }; await storage.updateById(TEST_DB, TEST_COLL, oldDoc._id, newDoc); await indexEngine.onUpdate(oldDoc, newDoc); // Old value should not be in index const oldCandidates = await indexEngine.findCandidateIds({ age: 25 }); expect(oldCandidates).toBeTruthy(); expect(oldCandidates!.has(oldDoc._id.toHexString())).toBeFalse(); // New value should be in index const newCandidates = await indexEngine.findCandidateIds({ age: 26 }); expect(newCandidates).toBeTruthy(); expect(newCandidates!.has(oldDoc._id.toHexString())).toBeTrue(); }); tap.test('indexengine: onDelete should remove from indexes', async () => { const docs = await storage.findAll(TEST_DB, TEST_COLL); const docToDelete = docs.find(d => d.name === 'Frank')!; await storage.deleteById(TEST_DB, TEST_COLL, docToDelete._id); await indexEngine.onDelete(docToDelete); const candidates = await indexEngine.findCandidateIds({ age: 40 }); expect(candidates).toBeTruthy(); expect(candidates!.has(docToDelete._id.toHexString())).toBeFalse(); }); // ============================================================================ // findCandidateIds Tests // ============================================================================ tap.test('indexengine: findCandidateIds with equality filter', async () => { const candidates = await indexEngine.findCandidateIds({ age: 30 }); expect(candidates).toBeTruthy(); expect(candidates!.size).toEqual(2); // Bob and Eve both have age 30 }); tap.test('indexengine: findCandidateIds with $in filter', async () => { const candidates = await indexEngine.findCandidateIds({ age: { $in: [28, 30] } }); expect(candidates).toBeTruthy(); expect(candidates!.size).toEqual(3); // Diana (28), Bob (30), Eve (30) }); tap.test('indexengine: findCandidateIds with no matching index', async () => { const candidates = await indexEngine.findCandidateIds({ nonIndexedField: 'value' }); // Should return null when no index can be used expect(candidates).toBeNull(); }); tap.test('indexengine: findCandidateIds with empty filter', async () => { const candidates = await indexEngine.findCandidateIds({}); // Empty filter = no index can be used expect(candidates).toBeNull(); }); // ============================================================================ // Range Query Tests (B-Tree) // ============================================================================ tap.test('indexengine: findCandidateIds with $gt', async () => { const candidates = await indexEngine.findCandidateIds({ age: { $gt: 30 } }); expect(candidates).toBeTruthy(); // Charlie (35) is > 30 expect(candidates!.size).toBeGreaterThanOrEqual(1); }); tap.test('indexengine: findCandidateIds with $lt', async () => { const candidates = await indexEngine.findCandidateIds({ age: { $lt: 28 } }); expect(candidates).toBeTruthy(); // Alice (26) is < 28 expect(candidates!.size).toBeGreaterThanOrEqual(1); }); tap.test('indexengine: findCandidateIds with $gte', async () => { const candidates = await indexEngine.findCandidateIds({ age: { $gte: 30 } }); expect(candidates).toBeTruthy(); // Bob (30), Eve (30), Charlie (35) expect(candidates!.size).toBeGreaterThanOrEqual(3); }); tap.test('indexengine: findCandidateIds with $lte', async () => { const candidates = await indexEngine.findCandidateIds({ age: { $lte: 28 } }); expect(candidates).toBeTruthy(); // Alice (26), Diana (28) expect(candidates!.size).toBeGreaterThanOrEqual(2); }); tap.test('indexengine: findCandidateIds with range $gt and $lt', async () => { const candidates = await indexEngine.findCandidateIds({ age: { $gt: 26, $lt: 35 } }); expect(candidates).toBeTruthy(); // Diana (28), Bob (30), Eve (30) are between 26 and 35 exclusive expect(candidates!.size).toBeGreaterThanOrEqual(3); }); // ============================================================================ // Index Selection Tests // ============================================================================ tap.test('indexengine: selectIndex should return best index for equality', async () => { const result = indexEngine.selectIndex({ age: 30 }); expect(result).toBeTruthy(); expect(result!.name).toEqual('age_1'); }); tap.test('indexengine: selectIndex should return best index for range query', async () => { const result = indexEngine.selectIndex({ age: { $gt: 25 } }); expect(result).toBeTruthy(); expect(result!.name).toEqual('age_1'); }); tap.test('indexengine: selectIndex should return null for no matching filter', async () => { const result = indexEngine.selectIndex({ nonIndexedField: 'value' }); expect(result).toBeNull(); }); tap.test('indexengine: selectIndex should return null for empty filter', async () => { const result = indexEngine.selectIndex({}); expect(result).toBeNull(); }); tap.test('indexengine: selectIndex should prefer more specific indexes', async () => { // Create a compound index await indexEngine.createIndex({ age: 1, category: 1 }, { name: 'age_category_compound' }); // Query that matches compound index const result = indexEngine.selectIndex({ age: 30, category: 'B' }); expect(result).toBeTruthy(); // Should prefer the compound index since it covers more fields expect(result!.name).toEqual('age_category_compound'); }); // ============================================================================ // Drop Index Tests // ============================================================================ tap.test('indexengine: dropIndex should remove the index', async () => { await indexEngine.createIndex({ dropTest: 1 }, { name: 'drop_test_idx' }); expect(await indexEngine.indexExists('drop_test_idx')).toBeTrue(); await indexEngine.dropIndex('drop_test_idx'); expect(await indexEngine.indexExists('drop_test_idx')).toBeFalse(); }); tap.test('indexengine: dropIndex should throw for _id index', async () => { let threw = false; try { await indexEngine.dropIndex('_id_'); } catch (e) { threw = true; } expect(threw).toBeTrue(); }); tap.test('indexengine: dropIndex should throw for non-existent index', async () => { let threw = false; try { await indexEngine.dropIndex('nonexistent_index'); } catch (e) { threw = true; } expect(threw).toBeTrue(); }); tap.test('indexengine: dropAllIndexes should remove all indexes except _id', async () => { // Create some indexes to drop await indexEngine.createIndex({ toDrop1: 1 }); await indexEngine.createIndex({ toDrop2: 1 }); await indexEngine.dropAllIndexes(); const indexes = await indexEngine.listIndexes(); expect(indexes.length).toEqual(1); expect(indexes[0].name).toEqual('_id_'); }); // ============================================================================ // Unique Index Constraint Tests // ============================================================================ tap.test('indexengine: unique index should prevent duplicate inserts', async () => { // Create fresh collection await storage.dropCollection(TEST_DB, 'uniquetest'); await storage.createCollection(TEST_DB, 'uniquetest'); const uniqueIndexEngine = new IndexEngine(TEST_DB, 'uniquetest', storage); await uniqueIndexEngine.createIndex({ email: 1 }, { unique: true }); // Insert first document const doc1 = { _id: new ObjectId(), email: 'test@example.com', name: 'Test' }; const stored1 = await storage.insertOne(TEST_DB, 'uniquetest', doc1); await uniqueIndexEngine.onInsert(stored1); // Try to insert duplicate const doc2 = { _id: new ObjectId(), email: 'test@example.com', name: 'Test2' }; const stored2 = await storage.insertOne(TEST_DB, 'uniquetest', doc2); let threw = false; try { await uniqueIndexEngine.onInsert(stored2); } catch (e: any) { threw = true; expect(e.message).toContain('duplicate key'); } expect(threw).toBeTrue(); }); // ============================================================================ // Sparse Index Tests // ============================================================================ tap.test('indexengine: sparse index should not include documents without the field', async () => { // Create fresh collection await storage.dropCollection(TEST_DB, 'sparsetest'); await storage.createCollection(TEST_DB, 'sparsetest'); const sparseIndexEngine = new IndexEngine(TEST_DB, 'sparsetest', storage); await sparseIndexEngine.createIndex({ optionalField: 1 }, { sparse: true }); // Insert doc with the field const doc1 = { _id: new ObjectId(), optionalField: 'hasValue', name: 'HasField' }; const stored1 = await storage.insertOne(TEST_DB, 'sparsetest', doc1); await sparseIndexEngine.onInsert(stored1); // Insert doc without the field const doc2 = { _id: new ObjectId(), name: 'NoField' }; const stored2 = await storage.insertOne(TEST_DB, 'sparsetest', doc2); await sparseIndexEngine.onInsert(stored2); // Search for documents with the field const candidates = await sparseIndexEngine.findCandidateIds({ optionalField: 'hasValue' }); expect(candidates).toBeTruthy(); expect(candidates!.size).toEqual(1); expect(candidates!.has(stored1._id.toHexString())).toBeTrue(); }); // ============================================================================ // Cleanup // ============================================================================ tap.test('indexengine: cleanup', async () => { await storage.close(); expect(true).toBeTrue(); }); export default tap.start();