BREAKING CHANGE(tsmdb): rename CongoDB to TsmDB and relocate/rename wire-protocol server implementation and public exports
This commit is contained in:
572
test/test.tsmdb.ts
Normal file
572
test/test.tsmdb.ts
Normal file
@@ -0,0 +1,572 @@
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import * as smartmongo from '../ts/index.js';
|
||||
import { MongoClient, Db, Collection } from 'mongodb';
|
||||
|
||||
const { tsmdb } = smartmongo;
|
||||
|
||||
let server: smartmongo.tsmdb.TsmdbServer;
|
||||
let client: MongoClient;
|
||||
let db: Db;
|
||||
|
||||
// ============================================================================
|
||||
// Server Startup
|
||||
// ============================================================================
|
||||
|
||||
tap.test('tsmdb: should start the server', async () => {
|
||||
server = new tsmdb.TsmdbServer({ port: 27117 }); // Use non-standard port for tests
|
||||
await server.start();
|
||||
expect(server.running).toBeTrue();
|
||||
});
|
||||
|
||||
tap.test('tsmdb: 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('tsmdb: should get a database instance', async () => {
|
||||
db = client.db('testdb');
|
||||
expect(db).toBeTruthy();
|
||||
expect(db.databaseName).toEqual('testdb');
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// Basic CRUD Tests
|
||||
// ============================================================================
|
||||
|
||||
tap.test('tsmdb: 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('tsmdb: 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('tsmdb: 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('tsmdb: 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('tsmdb: 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('tsmdb: 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('tsmdb: 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('tsmdb: 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('tsmdb: 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('tsmdb: 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('tsmdb: 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('tsmdb: 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('tsmdb: 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('tsmdb: 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('tsmdb: 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('tsmdb: 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('tsmdb: 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('tsmdb: 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('tsmdb: 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('tsmdb: 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('tsmdb: 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('tsmdb: cursor - limit', async () => {
|
||||
const collection = db.collection('users');
|
||||
const docs = await collection.find({}).limit(2).toArray();
|
||||
expect(docs.length).toBeLessThanOrEqual(2);
|
||||
});
|
||||
|
||||
tap.test('tsmdb: 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('tsmdb: 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('tsmdb: 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('tsmdb: 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('tsmdb: countDocuments - counts matching documents', async () => {
|
||||
const collection = db.collection('users');
|
||||
const count = await collection.countDocuments({ age: { $gte: 25 } });
|
||||
expect(count).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
tap.test('tsmdb: estimatedDocumentCount - returns total count', async () => {
|
||||
const collection = db.collection('users');
|
||||
const count = await collection.estimatedDocumentCount();
|
||||
expect(count).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
tap.test('tsmdb: 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('tsmdb: 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('tsmdb: createIndex - creates compound index', async () => {
|
||||
const collection = db.collection('users');
|
||||
const indexName = await collection.createIndex({ name: 1, age: -1 });
|
||||
|
||||
expect(indexName).toBeTruthy();
|
||||
});
|
||||
|
||||
tap.test('tsmdb: 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('tsmdb: 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('tsmdb: 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('tsmdb: 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('tsmdb: 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('tsmdb: 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('tsmdb: 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('tsmdb: 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('tsmdb: listCollections - lists all collections', async () => {
|
||||
const collections = await db.listCollections().toArray();
|
||||
expect(collections.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
tap.test('tsmdb: 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('tsmdb: 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('tsmdb: admin - listDatabases', async () => {
|
||||
const admin = client.db().admin();
|
||||
const result = await admin.listDatabases();
|
||||
expect(result.ok).toEqual(1);
|
||||
expect(result.databases).toBeArray();
|
||||
});
|
||||
|
||||
tap.test('tsmdb: admin - serverStatus', async () => {
|
||||
const admin = client.db().admin();
|
||||
const status = await admin.serverStatus();
|
||||
expect(status.ok).toEqual(1);
|
||||
});
|
||||
|
||||
tap.test('tsmdb: admin - ping', async () => {
|
||||
const admin = client.db().admin();
|
||||
const result = await admin.ping();
|
||||
expect(result.ok).toEqual(1);
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// Replace Operations Tests
|
||||
// ============================================================================
|
||||
|
||||
tap.test('tsmdb: 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('tsmdb: 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('tsmdb: cleanup - drop database', async () => {
|
||||
const result = await db.dropDatabase();
|
||||
expect(result).toBeTrue();
|
||||
});
|
||||
|
||||
tap.test('tsmdb: cleanup - close client and server', async () => {
|
||||
await client.close();
|
||||
await server.stop();
|
||||
expect(server.running).toBeFalse();
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
Reference in New Issue
Block a user