This commit is contained in:
2026-03-26 16:03:25 +00:00
commit 941e638f8d
49 changed files with 20022 additions and 0 deletions

View File

@@ -0,0 +1,232 @@
import { expect, tap } from '@git.zone/tstest/tapbundle';
import * as smartdb from '../ts/index.js';
const {
calculateCRC32,
calculateCRC32Buffer,
calculateDocumentChecksum,
addChecksum,
verifyChecksum,
removeChecksum,
} = smartdb;
// ============================================================================
// CRC32 String Tests
// ============================================================================
tap.test('checksum: calculateCRC32 should return consistent value for same input', async () => {
const result1 = calculateCRC32('hello world');
const result2 = calculateCRC32('hello world');
expect(result1).toEqual(result2);
});
tap.test('checksum: calculateCRC32 should return different values for different inputs', async () => {
const result1 = calculateCRC32('hello');
const result2 = calculateCRC32('world');
expect(result1).not.toEqual(result2);
});
tap.test('checksum: calculateCRC32 should return a 32-bit unsigned integer', async () => {
const result = calculateCRC32('test string');
expect(result).toBeGreaterThanOrEqual(0);
expect(result).toBeLessThanOrEqual(0xFFFFFFFF);
});
tap.test('checksum: calculateCRC32 should handle empty string', async () => {
const result = calculateCRC32('');
expect(typeof result).toEqual('number');
expect(result).toBeGreaterThanOrEqual(0);
});
tap.test('checksum: calculateCRC32 should handle special characters', async () => {
const result = calculateCRC32('hello\nworld\t!"#$%&\'()');
expect(typeof result).toEqual('number');
expect(result).toBeGreaterThanOrEqual(0);
});
tap.test('checksum: calculateCRC32 should handle unicode characters', async () => {
const result = calculateCRC32('hello 世界 🌍');
expect(typeof result).toEqual('number');
expect(result).toBeGreaterThanOrEqual(0);
});
// ============================================================================
// CRC32 Buffer Tests
// ============================================================================
tap.test('checksum: calculateCRC32Buffer should return consistent value for same input', async () => {
const buffer = Buffer.from('hello world');
const result1 = calculateCRC32Buffer(buffer);
const result2 = calculateCRC32Buffer(buffer);
expect(result1).toEqual(result2);
});
tap.test('checksum: calculateCRC32Buffer should return different values for different inputs', async () => {
const buffer1 = Buffer.from('hello');
const buffer2 = Buffer.from('world');
const result1 = calculateCRC32Buffer(buffer1);
const result2 = calculateCRC32Buffer(buffer2);
expect(result1).not.toEqual(result2);
});
tap.test('checksum: calculateCRC32Buffer should handle empty buffer', async () => {
const result = calculateCRC32Buffer(Buffer.from(''));
expect(typeof result).toEqual('number');
expect(result).toBeGreaterThanOrEqual(0);
});
tap.test('checksum: calculateCRC32Buffer should handle binary data', async () => {
const buffer = Buffer.from([0x00, 0xFF, 0x7F, 0x80, 0x01]);
const result = calculateCRC32Buffer(buffer);
expect(typeof result).toEqual('number');
expect(result).toBeGreaterThanOrEqual(0);
});
// ============================================================================
// Document Checksum Tests
// ============================================================================
tap.test('checksum: calculateDocumentChecksum should return consistent value', async () => {
const doc = { name: 'John', age: 30 };
const result1 = calculateDocumentChecksum(doc);
const result2 = calculateDocumentChecksum(doc);
expect(result1).toEqual(result2);
});
tap.test('checksum: calculateDocumentChecksum should exclude _checksum field', async () => {
const doc1 = { name: 'John', age: 30 };
const doc2 = { name: 'John', age: 30, _checksum: 12345 };
const result1 = calculateDocumentChecksum(doc1);
const result2 = calculateDocumentChecksum(doc2);
expect(result1).toEqual(result2);
});
tap.test('checksum: calculateDocumentChecksum should handle empty document', async () => {
const result = calculateDocumentChecksum({});
expect(typeof result).toEqual('number');
expect(result).toBeGreaterThanOrEqual(0);
});
tap.test('checksum: calculateDocumentChecksum should handle nested objects', async () => {
const doc = {
name: 'John',
address: {
street: '123 Main St',
city: 'Springfield',
zip: {
code: '12345',
plus4: '6789',
},
},
};
const result = calculateDocumentChecksum(doc);
expect(typeof result).toEqual('number');
expect(result).toBeGreaterThanOrEqual(0);
});
tap.test('checksum: calculateDocumentChecksum should handle arrays', async () => {
const doc = {
name: 'John',
tags: ['developer', 'tester', 'admin'],
scores: [95, 87, 92],
};
const result = calculateDocumentChecksum(doc);
expect(typeof result).toEqual('number');
expect(result).toBeGreaterThanOrEqual(0);
});
// ============================================================================
// Add/Verify/Remove Checksum Tests
// ============================================================================
tap.test('checksum: addChecksum should add _checksum field to document', async () => {
const doc = { name: 'John', age: 30 };
const docWithChecksum = addChecksum(doc);
expect('_checksum' in docWithChecksum).toBeTrue();
expect(typeof docWithChecksum._checksum).toEqual('number');
expect(docWithChecksum.name).toEqual('John');
expect(docWithChecksum.age).toEqual(30);
});
tap.test('checksum: addChecksum should not modify the original document', async () => {
const doc = { name: 'John', age: 30 };
addChecksum(doc);
expect('_checksum' in doc).toBeFalse();
});
tap.test('checksum: verifyChecksum should return true for valid checksum', async () => {
const doc = { name: 'John', age: 30 };
const docWithChecksum = addChecksum(doc);
const isValid = verifyChecksum(docWithChecksum);
expect(isValid).toBeTrue();
});
tap.test('checksum: verifyChecksum should return false for tampered document', async () => {
const doc = { name: 'John', age: 30 };
const docWithChecksum = addChecksum(doc);
// Tamper with the document
docWithChecksum.age = 31;
const isValid = verifyChecksum(docWithChecksum);
expect(isValid).toBeFalse();
});
tap.test('checksum: verifyChecksum should return false for wrong checksum', async () => {
const doc = { name: 'John', age: 30, _checksum: 12345 };
const isValid = verifyChecksum(doc);
expect(isValid).toBeFalse();
});
tap.test('checksum: verifyChecksum should return true for document without checksum', async () => {
const doc = { name: 'John', age: 30 };
const isValid = verifyChecksum(doc);
expect(isValid).toBeTrue();
});
tap.test('checksum: removeChecksum should remove _checksum field', async () => {
const doc = { name: 'John', age: 30 };
const docWithChecksum = addChecksum(doc);
const docWithoutChecksum = removeChecksum(docWithChecksum);
expect('_checksum' in docWithoutChecksum).toBeFalse();
expect(docWithoutChecksum.name).toEqual('John');
expect(docWithoutChecksum.age).toEqual(30);
});
tap.test('checksum: removeChecksum should handle document without checksum', async () => {
const doc = { name: 'John', age: 30 };
const result = removeChecksum(doc);
expect('_checksum' in result).toBeFalse();
expect(result.name).toEqual('John');
expect(result.age).toEqual(30);
});
// ============================================================================
// Round-trip Tests
// ============================================================================
tap.test('checksum: full round-trip - add, verify, remove', async () => {
const original = { name: 'Test', value: 42, nested: { a: 1, b: 2 } };
// Add checksum
const withChecksum = addChecksum(original);
expect('_checksum' in withChecksum).toBeTrue();
// Verify checksum
expect(verifyChecksum(withChecksum)).toBeTrue();
// Remove checksum
const restored = removeChecksum(withChecksum);
expect('_checksum' in restored).toBeFalse();
// Original data should be intact
expect(restored.name).toEqual('Test');
expect(restored.value).toEqual(42);
expect(restored.nested.a).toEqual(1);
expect(restored.nested.b).toEqual(2);
});
export default tap.start();

View File

@@ -0,0 +1,417 @@
import { expect, tap } from '@git.zone/tstest/tapbundle';
import * as smartdb from '../ts/index.js';
const { IndexEngine, MemoryStorageAdapter, ObjectId } = smartdb;
let storage: InstanceType<typeof MemoryStorageAdapter>;
let indexEngine: InstanceType<typeof IndexEngine>;
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();

View File

@@ -0,0 +1,273 @@
import { expect, tap } from '@git.zone/tstest/tapbundle';
import * as smartdb from '../ts/index.js';
const { QueryPlanner, IndexEngine, MemoryStorageAdapter, ObjectId } = smartdb;
let storage: InstanceType<typeof MemoryStorageAdapter>;
let indexEngine: InstanceType<typeof IndexEngine>;
let queryPlanner: InstanceType<typeof QueryPlanner>;
const TEST_DB = 'testdb';
const TEST_COLL = 'testcoll';
// ============================================================================
// Setup
// ============================================================================
tap.test('queryplanner: should create QueryPlanner instance', async () => {
storage = new MemoryStorageAdapter();
await storage.initialize();
await storage.createCollection(TEST_DB, TEST_COLL);
indexEngine = new IndexEngine(TEST_DB, TEST_COLL, storage);
queryPlanner = new QueryPlanner(indexEngine);
expect(queryPlanner).toBeTruthy();
});
tap.test('queryplanner: should insert test documents', async () => {
// Insert test documents
const docs = [
{ _id: new ObjectId(), name: 'Alice', age: 25, city: 'NYC', category: 'A' },
{ _id: new ObjectId(), name: 'Bob', age: 30, city: 'LA', category: 'B' },
{ _id: new ObjectId(), name: 'Charlie', age: 35, city: 'NYC', category: 'A' },
{ _id: new ObjectId(), name: 'Diana', age: 28, city: 'Chicago', category: 'C' },
{ _id: new ObjectId(), name: 'Eve', age: 32, city: 'LA', category: 'B' },
];
for (const doc of docs) {
await storage.insertOne(TEST_DB, TEST_COLL, doc);
}
});
// ============================================================================
// Basic Plan Tests
// ============================================================================
tap.test('queryplanner: empty filter should result in COLLSCAN', async () => {
const plan = await queryPlanner.plan({});
expect(plan.type).toEqual('COLLSCAN');
expect(plan.indexCovering).toBeFalse();
expect(plan.selectivity).toEqual(1.0);
expect(plan.explanation).toContain('No filter');
});
tap.test('queryplanner: null filter should result in COLLSCAN', async () => {
const plan = await queryPlanner.plan(null as any);
expect(plan.type).toEqual('COLLSCAN');
});
tap.test('queryplanner: filter with no matching index should result in COLLSCAN', async () => {
const plan = await queryPlanner.plan({ nonExistentField: 'value' });
expect(plan.type).toEqual('COLLSCAN');
expect(plan.explanation).toContain('No suitable index');
});
// ============================================================================
// Index Scan Tests (with indexes)
// ============================================================================
tap.test('queryplanner: should create test indexes', async () => {
await indexEngine.createIndex({ age: 1 }, { name: 'age_1' });
await indexEngine.createIndex({ name: 1 }, { name: 'name_1' });
await indexEngine.createIndex({ city: 1, category: 1 }, { name: 'city_category_1' });
const indexes = await indexEngine.listIndexes();
expect(indexes.length).toBeGreaterThanOrEqual(4); // _id_ + 3 created
});
tap.test('queryplanner: simple equality filter should use IXSCAN', async () => {
const plan = await queryPlanner.plan({ age: 30 });
expect(plan.type).toEqual('IXSCAN');
expect(plan.indexName).toEqual('age_1');
expect(plan.indexFieldsUsed).toContain('age');
expect(plan.usesRange).toBeFalse();
});
tap.test('queryplanner: $eq operator should use IXSCAN', async () => {
const plan = await queryPlanner.plan({ name: { $eq: 'Alice' } });
expect(plan.type).toEqual('IXSCAN');
expect(plan.indexName).toEqual('name_1');
expect(plan.indexFieldsUsed).toContain('name');
});
tap.test('queryplanner: range filter ($gt) should use IXSCAN_RANGE', async () => {
const plan = await queryPlanner.plan({ age: { $gt: 25 } });
expect(plan.type).toEqual('IXSCAN_RANGE');
expect(plan.indexName).toEqual('age_1');
expect(plan.usesRange).toBeTrue();
});
tap.test('queryplanner: range filter ($lt) should use IXSCAN_RANGE', async () => {
const plan = await queryPlanner.plan({ age: { $lt: 35 } });
expect(plan.type).toEqual('IXSCAN_RANGE');
expect(plan.usesRange).toBeTrue();
});
tap.test('queryplanner: range filter ($gte, $lte) should use IXSCAN_RANGE', async () => {
const plan = await queryPlanner.plan({ age: { $gte: 25, $lte: 35 } });
expect(plan.type).toEqual('IXSCAN_RANGE');
expect(plan.usesRange).toBeTrue();
});
tap.test('queryplanner: $in operator should use IXSCAN', async () => {
const plan = await queryPlanner.plan({ age: { $in: [25, 30, 35] } });
expect(plan.type).toEqual('IXSCAN');
expect(plan.indexName).toEqual('age_1');
});
// ============================================================================
// Compound Index Tests
// ============================================================================
tap.test('queryplanner: compound index - first field equality should use index', async () => {
const plan = await queryPlanner.plan({ city: 'NYC' });
expect(plan.type).toEqual('IXSCAN');
expect(plan.indexName).toEqual('city_category_1');
expect(plan.indexFieldsUsed).toContain('city');
});
tap.test('queryplanner: compound index - both fields should use full index', async () => {
const plan = await queryPlanner.plan({ city: 'NYC', category: 'A' });
expect(plan.type).toEqual('IXSCAN');
expect(plan.indexName).toEqual('city_category_1');
expect(plan.indexFieldsUsed).toContain('city');
expect(plan.indexFieldsUsed).toContain('category');
expect(plan.indexFieldsUsed.length).toEqual(2);
});
// ============================================================================
// Selectivity Tests
// ============================================================================
tap.test('queryplanner: equality query should have low selectivity', async () => {
const plan = await queryPlanner.plan({ age: 30 });
expect(plan.selectivity).toBeLessThan(0.1);
});
tap.test('queryplanner: range query should have moderate selectivity', async () => {
const plan = await queryPlanner.plan({ age: { $gt: 25 } });
expect(plan.selectivity).toBeGreaterThan(0);
expect(plan.selectivity).toBeLessThan(1);
});
tap.test('queryplanner: $in query selectivity depends on array size', async () => {
const smallInPlan = await queryPlanner.plan({ age: { $in: [25] } });
const largeInPlan = await queryPlanner.plan({ age: { $in: [25, 26, 27, 28, 29, 30] } });
// Larger $in should have higher selectivity (less selective = more documents)
expect(largeInPlan.selectivity).toBeGreaterThanOrEqual(smallInPlan.selectivity);
});
// ============================================================================
// Index Covering Tests
// ============================================================================
tap.test('queryplanner: query covering all filter fields should be index covering', async () => {
const plan = await queryPlanner.plan({ age: 30 });
// All filter fields are covered by the index
expect(plan.indexCovering).toBeTrue();
});
tap.test('queryplanner: query with residual filter should not be index covering', async () => {
const plan = await queryPlanner.plan({ city: 'NYC', name: 'Alice' });
// 'name' is not in the compound index city_category, so it's residual
expect(plan.indexCovering).toBeFalse();
expect(plan.residualFilter).toBeTruthy();
});
// ============================================================================
// Explain Tests
// ============================================================================
tap.test('queryplanner: explain should return detailed plan info', async () => {
const explanation = await queryPlanner.explain({ age: 30 });
expect(explanation.queryPlanner).toBeTruthy();
expect(explanation.queryPlanner.plannerVersion).toEqual(1);
expect(explanation.queryPlanner.winningPlan).toBeTruthy();
expect(explanation.queryPlanner.rejectedPlans).toBeArray();
});
tap.test('queryplanner: explain should include winning and rejected plans', async () => {
const explanation = await queryPlanner.explain({ age: 30 });
expect(explanation.queryPlanner.winningPlan.type).toBeTruthy();
expect(explanation.queryPlanner.rejectedPlans.length).toBeGreaterThan(0);
});
tap.test('queryplanner: explain winning plan should be the best plan', async () => {
const explanation = await queryPlanner.explain({ age: 30 });
// Winning plan should use an index, not collection scan (if index exists)
expect(explanation.queryPlanner.winningPlan.type).toEqual('IXSCAN');
// There should be a COLLSCAN in rejected plans
const hasCOLLSCAN = explanation.queryPlanner.rejectedPlans.some(p => p.type === 'COLLSCAN');
expect(hasCOLLSCAN).toBeTrue();
});
// ============================================================================
// $and Operator Tests
// ============================================================================
tap.test('queryplanner: $and conditions should be analyzed', async () => {
const plan = await queryPlanner.plan({
$and: [
{ age: { $gte: 25 } },
{ age: { $lte: 35 } },
],
});
expect(plan.type).toEqual('IXSCAN_RANGE');
expect(plan.indexName).toEqual('age_1');
});
// ============================================================================
// Edge Cases
// ============================================================================
tap.test('queryplanner: should handle complex nested operators', async () => {
const plan = await queryPlanner.plan({
age: { $gte: 20, $lte: 40 },
city: 'NYC',
});
expect(plan).toBeTruthy();
expect(plan.type).not.toBeUndefined();
});
tap.test('queryplanner: should handle $exists operator', async () => {
await indexEngine.createIndex({ email: 1 }, { name: 'email_1', sparse: true });
const plan = await queryPlanner.plan({ email: { $exists: true } });
// $exists can use sparse indexes
expect(plan).toBeTruthy();
});
// ============================================================================
// Cleanup
// ============================================================================
tap.test('queryplanner: cleanup', async () => {
await storage.close();
expect(true).toBeTrue();
});
export default tap.start();

View File

@@ -0,0 +1,361 @@
import { expect, tap } from '@git.zone/tstest/tapbundle';
import * as smartdb from '../ts/index.js';
const { SessionEngine } = smartdb;
let sessionEngine: InstanceType<typeof SessionEngine>;
// ============================================================================
// Setup
// ============================================================================
tap.test('session: should create SessionEngine instance', async () => {
sessionEngine = new SessionEngine({
sessionTimeoutMs: 1000, // 1 second for testing
cleanupIntervalMs: 10000, // 10 seconds to avoid cleanup during tests
});
expect(sessionEngine).toBeTruthy();
});
// ============================================================================
// Session Lifecycle Tests
// ============================================================================
tap.test('session: startSession should create session with auto-generated ID', async () => {
const session = sessionEngine.startSession();
expect(session).toBeTruthy();
expect(session.id).toBeTruthy();
expect(session.id.length).toBeGreaterThanOrEqual(32); // UUID hex string (32 or 36 with hyphens)
expect(session.createdAt).toBeGreaterThan(0);
expect(session.lastActivityAt).toBeGreaterThan(0);
expect(session.inTransaction).toBeFalse();
});
tap.test('session: startSession should create session with specified ID', async () => {
const customId = 'custom-session-id-12345';
const session = sessionEngine.startSession(customId);
expect(session.id).toEqual(customId);
});
tap.test('session: startSession should create session with metadata', async () => {
const metadata = { client: 'test-client', version: '1.0' };
const session = sessionEngine.startSession(undefined, metadata);
expect(session.metadata).toBeTruthy();
expect(session.metadata!.client).toEqual('test-client');
expect(session.metadata!.version).toEqual('1.0');
});
tap.test('session: getSession should return session by ID', async () => {
const created = sessionEngine.startSession('get-session-test');
const retrieved = sessionEngine.getSession('get-session-test');
expect(retrieved).toBeTruthy();
expect(retrieved!.id).toEqual('get-session-test');
expect(retrieved!.id).toEqual(created.id);
});
tap.test('session: getSession should return undefined for non-existent session', async () => {
const session = sessionEngine.getSession('non-existent-session-id');
expect(session).toBeUndefined();
});
tap.test('session: touchSession should update lastActivityAt', async () => {
const session = sessionEngine.startSession('touch-test-session');
const originalLastActivity = session.lastActivityAt;
// Wait a bit to ensure time difference
await new Promise(resolve => setTimeout(resolve, 10));
const touched = sessionEngine.touchSession('touch-test-session');
expect(touched).toBeTrue();
const updated = sessionEngine.getSession('touch-test-session');
expect(updated!.lastActivityAt).toBeGreaterThanOrEqual(originalLastActivity);
});
tap.test('session: touchSession should return false for non-existent session', async () => {
const touched = sessionEngine.touchSession('non-existent-touch-session');
expect(touched).toBeFalse();
});
tap.test('session: endSession should remove the session', async () => {
sessionEngine.startSession('end-session-test');
expect(sessionEngine.getSession('end-session-test')).toBeTruthy();
const ended = await sessionEngine.endSession('end-session-test');
expect(ended).toBeTrue();
expect(sessionEngine.getSession('end-session-test')).toBeUndefined();
});
tap.test('session: endSession should return false for non-existent session', async () => {
const ended = await sessionEngine.endSession('non-existent-end-session');
expect(ended).toBeFalse();
});
// ============================================================================
// Session Expiry Tests
// ============================================================================
tap.test('session: isSessionExpired should return false for fresh session', async () => {
const session = sessionEngine.startSession('fresh-session');
const isExpired = sessionEngine.isSessionExpired(session);
expect(isExpired).toBeFalse();
});
tap.test('session: isSessionExpired should return true for old session', async () => {
// Create a session with old lastActivityAt
const session = sessionEngine.startSession('old-session');
// Manually set lastActivityAt to old value (sessionTimeoutMs is 1000ms)
(session as any).lastActivityAt = Date.now() - 2000;
const isExpired = sessionEngine.isSessionExpired(session);
expect(isExpired).toBeTrue();
});
tap.test('session: getSession should return undefined for expired session', async () => {
const session = sessionEngine.startSession('expiring-session');
// Manually expire the session
(session as any).lastActivityAt = Date.now() - 2000;
const retrieved = sessionEngine.getSession('expiring-session');
expect(retrieved).toBeUndefined();
});
// ============================================================================
// Transaction Integration Tests
// ============================================================================
tap.test('session: startTransaction should mark session as in transaction', async () => {
sessionEngine.startSession('txn-session-1');
const started = sessionEngine.startTransaction('txn-session-1', 'txn-id-1', 1);
expect(started).toBeTrue();
const session = sessionEngine.getSession('txn-session-1');
expect(session!.inTransaction).toBeTrue();
expect(session!.txnId).toEqual('txn-id-1');
expect(session!.txnNumber).toEqual(1);
});
tap.test('session: startTransaction should return false for non-existent session', async () => {
const started = sessionEngine.startTransaction('non-existent-txn-session', 'txn-id');
expect(started).toBeFalse();
});
tap.test('session: endTransaction should clear transaction state', async () => {
sessionEngine.startSession('txn-session-2');
sessionEngine.startTransaction('txn-session-2', 'txn-id-2');
const ended = sessionEngine.endTransaction('txn-session-2');
expect(ended).toBeTrue();
const session = sessionEngine.getSession('txn-session-2');
expect(session!.inTransaction).toBeFalse();
expect(session!.txnId).toBeUndefined();
expect(session!.txnNumber).toBeUndefined();
});
tap.test('session: endTransaction should return false for non-existent session', async () => {
const ended = sessionEngine.endTransaction('non-existent-end-txn-session');
expect(ended).toBeFalse();
});
tap.test('session: getTransactionId should return transaction ID', async () => {
sessionEngine.startSession('txn-id-session');
sessionEngine.startTransaction('txn-id-session', 'my-txn-id');
const txnId = sessionEngine.getTransactionId('txn-id-session');
expect(txnId).toEqual('my-txn-id');
});
tap.test('session: getTransactionId should return undefined for session without transaction', async () => {
sessionEngine.startSession('no-txn-session');
const txnId = sessionEngine.getTransactionId('no-txn-session');
expect(txnId).toBeUndefined();
});
tap.test('session: getTransactionId should return undefined for non-existent session', async () => {
const txnId = sessionEngine.getTransactionId('non-existent-txn-id-session');
expect(txnId).toBeUndefined();
});
tap.test('session: isInTransaction should return correct state', async () => {
sessionEngine.startSession('in-txn-check-session');
expect(sessionEngine.isInTransaction('in-txn-check-session')).toBeFalse();
sessionEngine.startTransaction('in-txn-check-session', 'txn-check');
expect(sessionEngine.isInTransaction('in-txn-check-session')).toBeTrue();
sessionEngine.endTransaction('in-txn-check-session');
expect(sessionEngine.isInTransaction('in-txn-check-session')).toBeFalse();
});
tap.test('session: isInTransaction should return false for non-existent session', async () => {
expect(sessionEngine.isInTransaction('non-existent-in-txn-session')).toBeFalse();
});
// ============================================================================
// Session Listing Tests
// ============================================================================
tap.test('session: listSessions should return all active sessions', async () => {
// Close and recreate to have a clean slate
sessionEngine.close();
sessionEngine = new SessionEngine({
sessionTimeoutMs: 10000,
cleanupIntervalMs: 60000,
});
sessionEngine.startSession('list-session-1');
sessionEngine.startSession('list-session-2');
sessionEngine.startSession('list-session-3');
const sessions = sessionEngine.listSessions();
expect(sessions.length).toEqual(3);
});
tap.test('session: listSessions should not include expired sessions', async () => {
const session = sessionEngine.startSession('expired-list-session');
// Expire the session
(session as any).lastActivityAt = Date.now() - 20000;
const sessions = sessionEngine.listSessions();
const found = sessions.find(s => s.id === 'expired-list-session');
expect(found).toBeUndefined();
});
tap.test('session: getSessionCount should return correct count', async () => {
const count = sessionEngine.getSessionCount();
expect(count).toBeGreaterThanOrEqual(3); // We created 3 sessions above
});
tap.test('session: getSessionsWithTransactions should filter correctly', async () => {
// Clean slate
sessionEngine.close();
sessionEngine = new SessionEngine({
sessionTimeoutMs: 10000,
cleanupIntervalMs: 60000,
});
sessionEngine.startSession('no-txn-1');
sessionEngine.startSession('no-txn-2');
sessionEngine.startSession('with-txn-1');
sessionEngine.startSession('with-txn-2');
sessionEngine.startTransaction('with-txn-1', 'txn-a');
sessionEngine.startTransaction('with-txn-2', 'txn-b');
const txnSessions = sessionEngine.getSessionsWithTransactions();
expect(txnSessions.length).toEqual(2);
expect(txnSessions.every(s => s.inTransaction)).toBeTrue();
});
// ============================================================================
// getOrCreateSession Tests
// ============================================================================
tap.test('session: getOrCreateSession should create if missing', async () => {
const session = sessionEngine.getOrCreateSession('get-or-create-new');
expect(session).toBeTruthy();
expect(session.id).toEqual('get-or-create-new');
});
tap.test('session: getOrCreateSession should return existing session', async () => {
const created = sessionEngine.startSession('get-or-create-existing');
const retrieved = sessionEngine.getOrCreateSession('get-or-create-existing');
expect(retrieved.id).toEqual(created.id);
expect(retrieved.createdAt).toEqual(created.createdAt);
});
tap.test('session: getOrCreateSession should touch existing session', async () => {
const session = sessionEngine.startSession('get-or-create-touch');
const originalLastActivity = session.lastActivityAt;
await new Promise(resolve => setTimeout(resolve, 10));
sessionEngine.getOrCreateSession('get-or-create-touch');
const updated = sessionEngine.getSession('get-or-create-touch');
expect(updated!.lastActivityAt).toBeGreaterThanOrEqual(originalLastActivity);
});
// ============================================================================
// extractSessionId Static Method Tests
// ============================================================================
tap.test('session: extractSessionId should handle UUID object', async () => {
const { ObjectId } = smartdb;
const uuid = new smartdb.plugins.bson.UUID();
const lsid = { id: uuid };
const extracted = SessionEngine.extractSessionId(lsid);
expect(extracted).toEqual(uuid.toHexString());
});
tap.test('session: extractSessionId should handle string ID', async () => {
const lsid = { id: 'string-session-id' };
const extracted = SessionEngine.extractSessionId(lsid);
expect(extracted).toEqual('string-session-id');
});
tap.test('session: extractSessionId should handle binary format', async () => {
const binaryData = Buffer.from('test-binary-uuid', 'utf8').toString('base64');
const lsid = { id: { $binary: { base64: binaryData } } };
const extracted = SessionEngine.extractSessionId(lsid);
expect(extracted).toBeTruthy();
expect(typeof extracted).toEqual('string');
});
tap.test('session: extractSessionId should return undefined for null/undefined', async () => {
expect(SessionEngine.extractSessionId(null)).toBeUndefined();
expect(SessionEngine.extractSessionId(undefined)).toBeUndefined();
});
tap.test('session: extractSessionId should return undefined for empty object', async () => {
expect(SessionEngine.extractSessionId({})).toBeUndefined();
});
// ============================================================================
// refreshSession Tests
// ============================================================================
tap.test('session: refreshSession should update lastActivityAt', async () => {
const session = sessionEngine.startSession('refresh-session-test');
const originalLastActivity = session.lastActivityAt;
await new Promise(resolve => setTimeout(resolve, 10));
const refreshed = sessionEngine.refreshSession('refresh-session-test');
expect(refreshed).toBeTrue();
const updated = sessionEngine.getSession('refresh-session-test');
expect(updated!.lastActivityAt).toBeGreaterThanOrEqual(originalLastActivity);
});
tap.test('session: refreshSession should return false for non-existent session', async () => {
const refreshed = sessionEngine.refreshSession('non-existent-refresh-session');
expect(refreshed).toBeFalse();
});
// ============================================================================
// Cleanup
// ============================================================================
tap.test('session: close should clear all sessions', async () => {
sessionEngine.startSession('close-test-session');
expect(sessionEngine.getSessionCount()).toBeGreaterThan(0);
sessionEngine.close();
expect(sessionEngine.getSessionCount()).toEqual(0);
});
export default tap.start();

570
test/test.smartdb.ts Normal file
View File

@@ -0,0 +1,570 @@
import { expect, tap } from '@git.zone/tstest/tapbundle';
import * as smartdb from '../ts/index.js';
import { MongoClient, Db, Collection } from 'mongodb';
let server: smartdb.SmartdbServer;
let client: MongoClient;
let db: Db;
// ============================================================================
// Server Startup
// ============================================================================
tap.test('smartdb: should start the server', async () => {
server = new smartdb.SmartdbServer({ port: 27117 }); // Use non-standard port for tests
await server.start();
expect(server.running).toBeTrue();
});
tap.test('smartdb: should connect with official MongoClient', async () => {
client = new MongoClient('mongodb://127.0.0.1:27117', {
directConnection: true,
serverSelectionTimeoutMS: 5000,
});
await client.connect();
expect(client).toBeTruthy();
});
tap.test('smartdb: should get a database instance', async () => {
db = client.db('testdb');
expect(db).toBeTruthy();
expect(db.databaseName).toEqual('testdb');
});
// ============================================================================
// Basic CRUD Tests
// ============================================================================
tap.test('smartdb: insertOne - should insert a document', async () => {
const collection = db.collection('users');
const result = await collection.insertOne({
name: 'John Doe',
email: 'john@example.com',
age: 30,
});
expect(result.acknowledged).toBeTrue();
expect(result.insertedId).toBeTruthy();
});
tap.test('smartdb: insertMany - should insert multiple documents', async () => {
const collection = db.collection('users');
const result = await collection.insertMany([
{ name: 'Jane Doe', email: 'jane@example.com', age: 25 },
{ name: 'Bob Smith', email: 'bob@example.com', age: 35 },
{ name: 'Alice Johnson', email: 'alice@example.com', age: 28 },
]);
expect(result.acknowledged).toBeTrue();
expect(result.insertedCount).toEqual(3);
expect(Object.keys(result.insertedIds).length).toEqual(3);
});
tap.test('smartdb: findOne - should find a single document', async () => {
const collection = db.collection('users');
const doc = await collection.findOne({ name: 'John Doe' });
expect(doc).toBeTruthy();
expect(doc!.name).toEqual('John Doe');
expect(doc!.email).toEqual('john@example.com');
});
tap.test('smartdb: find - should find multiple documents', async () => {
const collection = db.collection('users');
const docs = await collection.find({ age: { $gte: 28 } }).toArray();
expect(docs.length).toEqual(3);
});
tap.test('smartdb: updateOne - should update a single document', async () => {
const collection = db.collection('users');
const result = await collection.updateOne(
{ name: 'John Doe' },
{ $set: { age: 31 } }
);
expect(result.acknowledged).toBeTrue();
expect(result.matchedCount).toEqual(1);
expect(result.modifiedCount).toEqual(1);
const updated = await collection.findOne({ name: 'John Doe' });
expect(updated!.age).toEqual(31);
});
tap.test('smartdb: updateMany - should update multiple documents', async () => {
const collection = db.collection('users');
const result = await collection.updateMany(
{ age: { $gte: 30 } },
{ $set: { senior: true } }
);
expect(result.acknowledged).toBeTrue();
expect(result.matchedCount).toEqual(2);
expect(result.modifiedCount).toEqual(2);
});
tap.test('smartdb: deleteOne - should delete a single document', async () => {
const collection = db.collection('users');
const result = await collection.deleteOne({ name: 'Bob Smith' });
expect(result.acknowledged).toBeTrue();
expect(result.deletedCount).toEqual(1);
});
tap.test('smartdb: deleteMany - should delete multiple documents', async () => {
const collection = db.collection('users');
// First add some test docs to delete
await collection.insertMany([
{ name: 'Delete1', toDelete: true },
{ name: 'Delete2', toDelete: true },
]);
const result = await collection.deleteMany({ toDelete: true });
expect(result.acknowledged).toBeTrue();
expect(result.deletedCount).toEqual(2);
});
// ============================================================================
// Query Operator Tests
// ============================================================================
tap.test('smartdb: query - $eq operator', async () => {
const collection = db.collection('users');
const docs = await collection.find({ name: { $eq: 'Jane Doe' } }).toArray();
expect(docs.length).toEqual(1);
expect(docs[0].name).toEqual('Jane Doe');
});
tap.test('smartdb: query - $ne operator', async () => {
const collection = db.collection('users');
const docs = await collection.find({ name: { $ne: 'Jane Doe' } }).toArray();
expect(docs.every(d => d.name !== 'Jane Doe')).toBeTrue();
});
tap.test('smartdb: query - $gt and $lt operators', async () => {
const collection = db.collection('users');
const docs = await collection.find({ age: { $gt: 25, $lt: 35 } }).toArray();
expect(docs.every(d => d.age > 25 && d.age < 35)).toBeTrue();
});
tap.test('smartdb: query - $in operator', async () => {
const collection = db.collection('users');
const docs = await collection.find({ name: { $in: ['Jane Doe', 'Alice Johnson'] } }).toArray();
expect(docs.length).toEqual(2);
});
tap.test('smartdb: query - $or operator', async () => {
const collection = db.collection('users');
const docs = await collection.find({
$or: [
{ name: 'Jane Doe' },
{ age: 31 }
]
}).toArray();
expect(docs.length).toBeGreaterThanOrEqual(1);
});
tap.test('smartdb: query - $and operator', async () => {
const collection = db.collection('users');
const docs = await collection.find({
$and: [
{ age: { $gte: 25 } },
{ age: { $lte: 30 } }
]
}).toArray();
expect(docs.every(d => d.age >= 25 && d.age <= 30)).toBeTrue();
});
tap.test('smartdb: query - $exists operator', async () => {
const collection = db.collection('users');
const docs = await collection.find({ senior: { $exists: true } }).toArray();
expect(docs.every(d => 'senior' in d)).toBeTrue();
});
// ============================================================================
// Update Operator Tests
// ============================================================================
tap.test('smartdb: update - $inc operator', async () => {
const collection = db.collection('users');
await collection.updateOne(
{ name: 'Jane Doe' },
{ $inc: { age: 1 } }
);
const updated = await collection.findOne({ name: 'Jane Doe' });
expect(updated!.age).toEqual(26);
});
tap.test('smartdb: update - $unset operator', async () => {
const collection = db.collection('users');
await collection.updateOne(
{ name: 'Jane Doe' },
{ $unset: { senior: '' } }
);
const updated = await collection.findOne({ name: 'Jane Doe' });
expect('senior' in updated!).toBeFalse();
});
tap.test('smartdb: update - $push operator', async () => {
const collection = db.collection('users');
await collection.updateOne(
{ name: 'Jane Doe' },
{ $set: { tags: ['developer'] } }
);
await collection.updateOne(
{ name: 'Jane Doe' },
{ $push: { tags: 'tester' } }
);
const updated = await collection.findOne({ name: 'Jane Doe' });
expect(updated!.tags).toContain('developer');
expect(updated!.tags).toContain('tester');
});
tap.test('smartdb: update - $pull operator', async () => {
const collection = db.collection('users');
await collection.updateOne(
{ name: 'Jane Doe' },
{ $pull: { tags: 'tester' } }
);
const updated = await collection.findOne({ name: 'Jane Doe' });
expect(updated!.tags).not.toContain('tester');
});
tap.test('smartdb: update - upsert creates new document', async () => {
const collection = db.collection('users');
const result = await collection.updateOne(
{ name: 'New User' },
{ $set: { email: 'new@example.com', age: 40 } },
{ upsert: true }
);
expect(result.upsertedCount).toEqual(1);
expect(result.upsertedId).toBeTruthy();
const inserted = await collection.findOne({ name: 'New User' });
expect(inserted).toBeTruthy();
expect(inserted!.email).toEqual('new@example.com');
});
// ============================================================================
// Cursor Tests
// ============================================================================
tap.test('smartdb: cursor - sort', async () => {
const collection = db.collection('users');
const docs = await collection.find({}).sort({ age: -1 }).toArray();
for (let i = 1; i < docs.length; i++) {
if (docs[i-1].age !== undefined && docs[i].age !== undefined) {
expect(docs[i-1].age).toBeGreaterThanOrEqual(docs[i].age);
}
}
});
tap.test('smartdb: cursor - limit', async () => {
const collection = db.collection('users');
const docs = await collection.find({}).limit(2).toArray();
expect(docs.length).toBeLessThanOrEqual(2);
});
tap.test('smartdb: cursor - skip', async () => {
const collection = db.collection('users');
const allDocs = await collection.find({}).toArray();
const skippedDocs = await collection.find({}).skip(1).toArray();
expect(skippedDocs.length).toEqual(Math.max(0, allDocs.length - 1));
});
tap.test('smartdb: cursor - project', async () => {
const collection = db.collection('users');
const docs = await collection.find({}).project({ name: 1, _id: 0 }).toArray();
expect(docs.length).toBeGreaterThan(0);
expect(docs[0].name).toBeTruthy();
expect(docs[0].email).toBeUndefined();
});
// ============================================================================
// FindOneAnd* Tests
// ============================================================================
tap.test('smartdb: findOneAndUpdate - returns updated document', async () => {
const collection = db.collection('users');
const result = await collection.findOneAndUpdate(
{ name: 'Jane Doe' },
{ $set: { status: 'active' } },
{ returnDocument: 'after' }
);
expect(result).toBeTruthy();
expect(result!.status).toEqual('active');
});
tap.test('smartdb: findOneAndDelete - returns deleted document', async () => {
const collection = db.collection('users');
// Insert a temp doc to delete
await collection.insertOne({ name: 'TempUser', temp: true });
const result = await collection.findOneAndDelete({ name: 'TempUser' });
expect(result).toBeTruthy();
expect(result!.name).toEqual('TempUser');
// Verify deleted
const found = await collection.findOne({ name: 'TempUser' });
expect(found).toBeNull();
});
// ============================================================================
// Count and Distinct Tests
// ============================================================================
tap.test('smartdb: countDocuments - counts matching documents', async () => {
const collection = db.collection('users');
const count = await collection.countDocuments({ age: { $gte: 25 } });
expect(count).toBeGreaterThan(0);
});
tap.test('smartdb: estimatedDocumentCount - returns total count', async () => {
const collection = db.collection('users');
const count = await collection.estimatedDocumentCount();
expect(count).toBeGreaterThan(0);
});
tap.test('smartdb: distinct - returns unique values', async () => {
const collection = db.collection('users');
const names = await collection.distinct('name');
expect(names.length).toBeGreaterThan(0);
// All names should be unique
expect(new Set(names).size).toEqual(names.length);
});
// ============================================================================
// Index Tests
// ============================================================================
tap.test('smartdb: createIndex - creates a single index', async () => {
const collection = db.collection('users');
const indexName = await collection.createIndex({ email: 1 });
expect(indexName).toBeTruthy();
expect(indexName).toContain('email');
});
tap.test('smartdb: createIndex - creates compound index', async () => {
const collection = db.collection('users');
const indexName = await collection.createIndex({ name: 1, age: -1 });
expect(indexName).toBeTruthy();
});
tap.test('smartdb: listIndexes - lists all indexes', async () => {
const collection = db.collection('users');
const indexes = await collection.listIndexes().toArray();
expect(indexes.length).toBeGreaterThanOrEqual(1); // At least _id index
expect(indexes.some(i => i.name === '_id_')).toBeTrue();
});
tap.test('smartdb: dropIndex - drops an index', async () => {
const collection = db.collection('users');
const indexName = await collection.createIndex({ toDropField: 1 });
await collection.dropIndex(indexName);
const indexes = await collection.listIndexes().toArray();
expect(indexes.some(i => i.name === indexName)).toBeFalse();
});
// ============================================================================
// Aggregation Tests
// ============================================================================
tap.test('smartdb: aggregate - $match stage', async () => {
const collection = db.collection('users');
const results = await collection.aggregate([
{ $match: { age: { $gte: 25 } } }
]).toArray();
expect(results.length).toBeGreaterThan(0);
expect(results.every(d => d.age >= 25)).toBeTrue();
});
tap.test('smartdb: aggregate - $project stage', async () => {
const collection = db.collection('users');
const results = await collection.aggregate([
{ $project: { name: 1, _id: 0 } }
]).toArray();
expect(results.length).toBeGreaterThan(0);
expect(results[0].name).toBeTruthy();
expect(results[0].email).toBeUndefined();
});
tap.test('smartdb: aggregate - $sort stage', async () => {
const collection = db.collection('users');
const results = await collection.aggregate([
{ $match: { age: { $exists: true } } },
{ $sort: { age: 1 } }
]).toArray();
for (let i = 1; i < results.length; i++) {
expect(results[i].age).toBeGreaterThanOrEqual(results[i-1].age);
}
});
tap.test('smartdb: aggregate - $group stage', async () => {
const collection = db.collection('users');
// Add some categorized data
await collection.insertMany([
{ name: 'GroupTest1', category: 'A', value: 10 },
{ name: 'GroupTest2', category: 'A', value: 20 },
{ name: 'GroupTest3', category: 'B', value: 30 },
]);
const results = await collection.aggregate([
{ $match: { category: { $exists: true } } },
{ $group: { _id: '$category', total: { $sum: '$value' } } }
]).toArray();
expect(results.length).toEqual(2);
const groupA = results.find(r => r._id === 'A');
const groupB = results.find(r => r._id === 'B');
expect(groupA!.total).toEqual(30);
expect(groupB!.total).toEqual(30);
});
tap.test('smartdb: aggregate - $limit and $skip stages', async () => {
const collection = db.collection('users');
const results = await collection.aggregate([
{ $skip: 1 },
{ $limit: 2 }
]).toArray();
expect(results.length).toBeLessThanOrEqual(2);
});
// ============================================================================
// Bulk Operations Tests
// ============================================================================
tap.test('smartdb: bulkWrite - executes multiple operations', async () => {
const collection = db.collection('bulktest');
const result = await collection.bulkWrite([
{ insertOne: { document: { name: 'Bulk1', value: 1 } } },
{ insertOne: { document: { name: 'Bulk2', value: 2 } } },
{ updateOne: { filter: { name: 'Bulk1' }, update: { $set: { updated: true } } } },
]);
expect(result.insertedCount).toEqual(2);
expect(result.modifiedCount).toEqual(1);
});
// ============================================================================
// Database Operations Tests
// ============================================================================
tap.test('smartdb: listCollections - lists all collections', async () => {
const collections = await db.listCollections().toArray();
expect(collections.length).toBeGreaterThan(0);
});
tap.test('smartdb: createCollection - creates a new collection', async () => {
await db.createCollection('newcollection');
const collections = await db.listCollections().toArray();
expect(collections.some(c => c.name === 'newcollection')).toBeTrue();
});
tap.test('smartdb: dropCollection - drops a collection', async () => {
await db.createCollection('todrop');
await db.dropCollection('todrop');
const collections = await db.listCollections().toArray();
expect(collections.some(c => c.name === 'todrop')).toBeFalse();
});
// ============================================================================
// Admin Tests
// ============================================================================
tap.test('smartdb: admin - listDatabases', async () => {
const admin = client.db().admin();
const result = await admin.listDatabases();
expect(result.ok).toEqual(1);
expect(result.databases).toBeArray();
});
tap.test('smartdb: admin - serverStatus', async () => {
const admin = client.db().admin();
const status = await admin.serverStatus();
expect(status.ok).toEqual(1);
});
tap.test('smartdb: admin - ping', async () => {
const admin = client.db().admin();
const result = await admin.ping();
expect(result.ok).toEqual(1);
});
// ============================================================================
// Replace Operations Tests
// ============================================================================
tap.test('smartdb: replaceOne - replaces entire document', async () => {
const collection = db.collection('replacetest');
await collection.insertOne({ name: 'Original', field1: 'value1', field2: 'value2' });
const result = await collection.replaceOne(
{ name: 'Original' },
{ name: 'Replaced', newField: 'newValue' }
);
expect(result.matchedCount).toEqual(1);
expect(result.modifiedCount).toEqual(1);
const replaced = await collection.findOne({ name: 'Replaced' });
expect(replaced).toBeTruthy();
expect(replaced!.newField).toEqual('newValue');
expect(replaced!.field1).toBeUndefined();
expect(replaced!.field2).toBeUndefined();
});
tap.test('smartdb: findOneAndReplace - returns replaced document', async () => {
const collection = db.collection('replacetest');
await collection.insertOne({ name: 'ToReplace', data: 'old' });
const result = await collection.findOneAndReplace(
{ name: 'ToReplace' },
{ name: 'Replaced', data: 'new' },
{ returnDocument: 'after' }
);
expect(result).toBeTruthy();
expect(result!.data).toEqual('new');
});
// ============================================================================
// Cleanup
// ============================================================================
tap.test('smartdb: cleanup - drop database', async () => {
const result = await db.dropDatabase();
expect(result).toBeTrue();
});
tap.test('smartdb: cleanup - close client and server', async () => {
await client.close();
await server.stop();
expect(server.running).toBeFalse();
});
export default tap.start();

411
test/test.smartdb.wal.ts Normal file
View File

@@ -0,0 +1,411 @@
import { expect, tap } from '@git.zone/tstest/tapbundle';
import * as smartdb from '../ts/index.js';
import * as path from 'path';
import * as fs from 'fs/promises';
const { WAL, ObjectId } = smartdb;
let wal: InstanceType<typeof WAL>;
const TEST_WAL_PATH = '/tmp/smartdb-test-wal/test.wal';
// Helper to clean up test files
async function cleanupTestFiles() {
try {
await fs.rm('/tmp/smartdb-test-wal', { recursive: true, force: true });
} catch {
// Ignore if doesn't exist
}
}
// ============================================================================
// Setup
// ============================================================================
tap.test('wal: cleanup before tests', async () => {
await cleanupTestFiles();
});
tap.test('wal: should create WAL instance', async () => {
wal = new WAL(TEST_WAL_PATH, { checkpointInterval: 100 });
expect(wal).toBeTruthy();
});
tap.test('wal: should initialize WAL', async () => {
const result = await wal.initialize();
expect(result).toBeTruthy();
expect(result.recoveredEntries).toBeArray();
expect(result.recoveredEntries.length).toEqual(0);
});
// ============================================================================
// LSN Tests
// ============================================================================
tap.test('wal: getCurrentLsn should return 0 initially', async () => {
const lsn = wal.getCurrentLsn();
expect(lsn).toEqual(0);
});
tap.test('wal: LSN should increment after logging', async () => {
const doc = { _id: new ObjectId(), name: 'Test' };
const lsn = await wal.logInsert('testdb', 'testcoll', doc as any);
expect(lsn).toEqual(1);
expect(wal.getCurrentLsn()).toEqual(1);
});
// ============================================================================
// Insert Logging Tests
// ============================================================================
tap.test('wal: logInsert should create entry with correct structure', async () => {
const doc = { _id: new ObjectId(), name: 'InsertTest', value: 42 };
const lsn = await wal.logInsert('testdb', 'insertcoll', doc as any);
expect(lsn).toBeGreaterThan(0);
const entries = wal.getEntriesAfter(lsn - 1);
const entry = entries.find(e => e.lsn === lsn);
expect(entry).toBeTruthy();
expect(entry!.operation).toEqual('insert');
expect(entry!.dbName).toEqual('testdb');
expect(entry!.collName).toEqual('insertcoll');
expect(entry!.documentId).toEqual(doc._id.toHexString());
expect(entry!.data).toBeTruthy();
expect(entry!.timestamp).toBeGreaterThan(0);
expect(entry!.checksum).toBeGreaterThan(0);
});
tap.test('wal: logInsert with transaction ID', async () => {
const doc = { _id: new ObjectId(), name: 'TxnInsertTest' };
const lsn = await wal.logInsert('testdb', 'insertcoll', doc as any, 'txn-123');
const entries = wal.getEntriesAfter(lsn - 1);
const entry = entries.find(e => e.lsn === lsn);
expect(entry!.txnId).toEqual('txn-123');
});
// ============================================================================
// Update Logging Tests
// ============================================================================
tap.test('wal: logUpdate should store old and new document', async () => {
const oldDoc = { _id: new ObjectId(), name: 'OldName', value: 1 };
const newDoc = { ...oldDoc, name: 'NewName', value: 2 };
const lsn = await wal.logUpdate('testdb', 'updatecoll', oldDoc as any, newDoc as any);
const entries = wal.getEntriesAfter(lsn - 1);
const entry = entries.find(e => e.lsn === lsn);
expect(entry).toBeTruthy();
expect(entry!.operation).toEqual('update');
expect(entry!.data).toBeTruthy();
expect(entry!.previousData).toBeTruthy();
expect(entry!.documentId).toEqual(oldDoc._id.toHexString());
});
// ============================================================================
// Delete Logging Tests
// ============================================================================
tap.test('wal: logDelete should record deleted document', async () => {
const doc = { _id: new ObjectId(), name: 'ToDelete' };
const lsn = await wal.logDelete('testdb', 'deletecoll', doc as any);
const entries = wal.getEntriesAfter(lsn - 1);
const entry = entries.find(e => e.lsn === lsn);
expect(entry).toBeTruthy();
expect(entry!.operation).toEqual('delete');
expect(entry!.previousData).toBeTruthy();
expect(entry!.data).toBeUndefined();
});
// ============================================================================
// Transaction Logging Tests
// ============================================================================
tap.test('wal: logBeginTransaction should create begin entry', async () => {
const lsn = await wal.logBeginTransaction('txn-begin-test');
const entries = wal.getEntriesAfter(lsn - 1);
const entry = entries.find(e => e.lsn === lsn);
expect(entry).toBeTruthy();
expect(entry!.operation).toEqual('begin');
expect(entry!.txnId).toEqual('txn-begin-test');
});
tap.test('wal: logCommitTransaction should create commit entry', async () => {
const lsn = await wal.logCommitTransaction('txn-commit-test');
const entries = wal.getEntriesAfter(lsn - 1);
const entry = entries.find(e => e.lsn === lsn);
expect(entry).toBeTruthy();
expect(entry!.operation).toEqual('commit');
expect(entry!.txnId).toEqual('txn-commit-test');
});
tap.test('wal: logAbortTransaction should create abort entry', async () => {
const lsn = await wal.logAbortTransaction('txn-abort-test');
const entries = wal.getEntriesAfter(lsn - 1);
const entry = entries.find(e => e.lsn === lsn);
expect(entry).toBeTruthy();
expect(entry!.operation).toEqual('abort');
expect(entry!.txnId).toEqual('txn-abort-test');
});
// ============================================================================
// getTransactionEntries Tests
// ============================================================================
tap.test('wal: getTransactionEntries should return entries for transaction', async () => {
// Log a complete transaction
const txnId = 'txn-entries-test';
await wal.logBeginTransaction(txnId);
const doc1 = { _id: new ObjectId(), name: 'TxnDoc1' };
await wal.logInsert('testdb', 'txncoll', doc1 as any, txnId);
const doc2 = { _id: new ObjectId(), name: 'TxnDoc2' };
await wal.logInsert('testdb', 'txncoll', doc2 as any, txnId);
await wal.logCommitTransaction(txnId);
const entries = wal.getTransactionEntries(txnId);
expect(entries.length).toEqual(4); // begin + 2 inserts + commit
expect(entries.every(e => e.txnId === txnId)).toBeTrue();
});
tap.test('wal: getTransactionEntries should return empty for unknown transaction', async () => {
const entries = wal.getTransactionEntries('unknown-txn-id');
expect(entries.length).toEqual(0);
});
// ============================================================================
// getEntriesAfter Tests
// ============================================================================
tap.test('wal: getEntriesAfter should filter by LSN', async () => {
const currentLsn = wal.getCurrentLsn();
// Add more entries
const doc = { _id: new ObjectId(), name: 'AfterTest' };
await wal.logInsert('testdb', 'aftercoll', doc as any);
const entries = wal.getEntriesAfter(currentLsn);
expect(entries.length).toEqual(1);
expect(entries[0].lsn).toBeGreaterThan(currentLsn);
});
tap.test('wal: getEntriesAfter with LSN 0 should return all entries', async () => {
const entries = wal.getEntriesAfter(0);
expect(entries.length).toBeGreaterThan(0);
expect(entries.length).toEqual(wal.getCurrentLsn());
});
// ============================================================================
// Checkpoint Tests
// ============================================================================
tap.test('wal: checkpoint should create checkpoint entry', async () => {
const lsn = await wal.checkpoint();
expect(lsn).toBeGreaterThan(0);
// After checkpoint, getEntriesAfter(checkpoint) should be limited
const entries = wal.getEntriesAfter(0);
expect(entries.some(e => e.operation === 'checkpoint')).toBeTrue();
});
// ============================================================================
// Document Recovery Tests
// ============================================================================
tap.test('wal: recoverDocument should deserialize document from entry', async () => {
const originalDoc = { _id: new ObjectId(), name: 'RecoverTest', nested: { a: 1, b: 2 } };
const lsn = await wal.logInsert('testdb', 'recovercoll', originalDoc as any);
const entries = wal.getEntriesAfter(lsn - 1);
const entry = entries.find(e => e.lsn === lsn);
const recovered = wal.recoverDocument(entry!);
expect(recovered).toBeTruthy();
expect(recovered!.name).toEqual('RecoverTest');
expect(recovered!.nested.a).toEqual(1);
expect(recovered!.nested.b).toEqual(2);
});
tap.test('wal: recoverDocument should return null for entry without data', async () => {
const lsn = await wal.logBeginTransaction('recover-no-data');
const entries = wal.getEntriesAfter(lsn - 1);
const entry = entries.find(e => e.lsn === lsn);
const recovered = wal.recoverDocument(entry!);
expect(recovered).toBeNull();
});
tap.test('wal: recoverPreviousDocument should deserialize previous state', async () => {
const oldDoc = { _id: new ObjectId(), name: 'Old', value: 100 };
const newDoc = { ...oldDoc, name: 'New', value: 200 };
const lsn = await wal.logUpdate('testdb', 'recovercoll', oldDoc as any, newDoc as any);
const entries = wal.getEntriesAfter(lsn - 1);
const entry = entries.find(e => e.lsn === lsn);
const previous = wal.recoverPreviousDocument(entry!);
expect(previous).toBeTruthy();
expect(previous!.name).toEqual('Old');
expect(previous!.value).toEqual(100);
});
tap.test('wal: recoverPreviousDocument should return null for insert entry', async () => {
const doc = { _id: new ObjectId(), name: 'NoPrevious' };
const lsn = await wal.logInsert('testdb', 'recovercoll', doc as any);
const entries = wal.getEntriesAfter(lsn - 1);
const entry = entries.find(e => e.lsn === lsn);
const previous = wal.recoverPreviousDocument(entry!);
expect(previous).toBeNull();
});
// ============================================================================
// WAL Persistence and Recovery Tests
// ============================================================================
tap.test('wal: should persist and recover entries', async () => {
// Close current WAL
await wal.close();
// Create new WAL instance and initialize (should recover)
const wal2 = new WAL(TEST_WAL_PATH, { checkpointInterval: 100 });
const result = await wal2.initialize();
// Should have recovered entries
expect(result.recoveredEntries).toBeArray();
// After checkpoint, there might not be many recoverable entries
// but getCurrentLsn should be preserved or reset
await wal2.close();
});
// ============================================================================
// Entry Checksum Tests
// ============================================================================
tap.test('wal: entries should have valid checksums', async () => {
wal = new WAL(TEST_WAL_PATH + '.checksum', { checkpointInterval: 100 });
await wal.initialize();
const doc = { _id: new ObjectId(), name: 'ChecksumTest' };
const lsn = await wal.logInsert('testdb', 'checksumcoll', doc as any);
const entries = wal.getEntriesAfter(lsn - 1);
const entry = entries.find(e => e.lsn === lsn);
expect(entry!.checksum).toBeGreaterThan(0);
expect(typeof entry!.checksum).toEqual('number');
});
// ============================================================================
// Edge Cases
// ============================================================================
tap.test('wal: should handle special characters in document', async () => {
const doc = {
_id: new ObjectId(),
name: 'Test\nWith\tSpecial\r\nChars',
emoji: '🎉',
unicode: '日本語',
};
const lsn = await wal.logInsert('testdb', 'specialcoll', doc as any);
const entries = wal.getEntriesAfter(lsn - 1);
const entry = entries.find(e => e.lsn === lsn);
const recovered = wal.recoverDocument(entry!);
expect(recovered!.name).toEqual('Test\nWith\tSpecial\r\nChars');
expect(recovered!.emoji).toEqual('🎉');
expect(recovered!.unicode).toEqual('日本語');
});
tap.test('wal: should handle binary data in documents', async () => {
const doc = {
_id: new ObjectId(),
binaryField: Buffer.from([0x00, 0xFF, 0x7F, 0x80]),
};
const lsn = await wal.logInsert('testdb', 'binarycoll', doc as any);
const entries = wal.getEntriesAfter(lsn - 1);
const entry = entries.find(e => e.lsn === lsn);
const recovered = wal.recoverDocument(entry!);
expect(recovered).toBeTruthy();
});
tap.test('wal: should handle nested documents', async () => {
const doc = {
_id: new ObjectId(),
level1: {
level2: {
level3: {
value: 'deep',
},
},
},
};
const lsn = await wal.logInsert('testdb', 'nestedcoll', doc as any);
const entries = wal.getEntriesAfter(lsn - 1);
const entry = entries.find(e => e.lsn === lsn);
const recovered = wal.recoverDocument(entry!);
expect(recovered!.level1.level2.level3.value).toEqual('deep');
});
tap.test('wal: should handle arrays in documents', async () => {
const doc = {
_id: new ObjectId(),
tags: ['a', 'b', 'c'],
numbers: [1, 2, 3],
mixed: [1, 'two', { three: 3 }],
};
const lsn = await wal.logInsert('testdb', 'arraycoll', doc as any);
const entries = wal.getEntriesAfter(lsn - 1);
const entry = entries.find(e => e.lsn === lsn);
const recovered = wal.recoverDocument(entry!);
expect(recovered!.tags).toEqual(['a', 'b', 'c']);
expect(recovered!.numbers).toEqual([1, 2, 3]);
expect(recovered!.mixed[2].three).toEqual(3);
});
// ============================================================================
// Cleanup
// ============================================================================
tap.test('wal: cleanup', async () => {
await wal.close();
await cleanupTestFiles();
expect(true).toBeTrue();
});
export default tap.start();