The Node driver sends {field: /re/} as a BSON regex element (type 0x0B),
which the matcher previously only handled via the $regex operator form;
and dot paths with numeric segments (tags.0) were ignored because array
descent only recursed into embedded docs. Both are part of standard
MongoDB query semantics and were caught by the driver e2e suite.
server: unbounded Io async limit so the accept loop never wedges, and
treat header-read failures (client RST on pool teardown) as clean
disconnects. With the default cpu_count-1 limit, groupAsync's eager
fallback ran connection handlers inline on the accept-loop fiber once
that many connections were alive, stalling accept() and timing out
handshakes for further clients.
Add tests/e2e/: official driver CRUD, concurrency, and kill -9 recovery
suites (29 + 2 + 3 checks), plus unit tests for the query fixes.
133 lines
5.5 KiB
JavaScript
133 lines
5.5 KiB
JavaScript
// End-to-end test: official MongoDB Node.js driver against mongo-light.
|
|
const { MongoClient, ObjectId } = require('mongodb');
|
|
|
|
const URL = 'mongodb://127.0.0.1:27020';
|
|
const results = [];
|
|
function check(name, cond, detail = '') {
|
|
results.push({ name, ok: !!cond, detail: String(detail) });
|
|
if (!cond) console.error(` ✗ ${name} ${detail}`);
|
|
}
|
|
async function main() {
|
|
const client = new MongoClient(URL, { serverSelectionTimeoutMS: 5000 });
|
|
await client.connect();
|
|
const db = client.db('e2e');
|
|
const users = db.collection('users');
|
|
await users.drop().catch(() => {});
|
|
|
|
// --- insert ---
|
|
await users.insertOne({ name: 'alice', age: 30, tags: ['a', 'b'] });
|
|
const many = await users.insertMany([
|
|
{ name: 'bob', age: 25, tags: ['b'] },
|
|
{ name: 'carol', age: 35, tags: ['c', 'a'] },
|
|
{ name: 'dave', age: 40, tags: [] },
|
|
]);
|
|
check('insertMany acknowledged', many.acknowledged === true, many);
|
|
check('auto _id assigned', ObjectId.isValid(many.insertedIds[0]));
|
|
|
|
// --- find: filters, operators, sort, skip, limit, projection ---
|
|
const gt = await users.find({ age: { $gt: 28 } }).sort({ age: -1 }).toArray();
|
|
check('find $gt + sort desc', gt.map((d) => d.name).join(',') === 'dave,carol,alice', gt.map((d) => d.name));
|
|
|
|
const inq = await users.find({ name: { $in: ['alice', 'bob'] } }).count();
|
|
check('find $in count', inq === 2, inq);
|
|
|
|
const rgx = await users.find({ name: /^[bc]/ }).toArray();
|
|
check('find $regex', rgx.length === 2, rgx.map((d) => d.name));
|
|
|
|
const exists = await users.find({ tags: { $exists: true } }).count();
|
|
check('find $exists', exists === 4, exists);
|
|
|
|
const lim = await users.find({}).sort({ age: 1 }).skip(1).limit(2).toArray();
|
|
check('find skip+limit+sort', lim.map((d) => d.name).join(',') === 'alice,carol', lim.map((d) => d.name));
|
|
|
|
const proj = await users.findOne({ name: 'alice' }, { projection: { _id: 0, name: 1 } });
|
|
check('projection', proj.name === 'alice' && proj.age === undefined, JSON.stringify(proj));
|
|
|
|
const dot = await users.findOne({ 'tags.0': 'c' });
|
|
check('dot path + array', dot?.name === 'carol');
|
|
|
|
// --- count ---
|
|
check('countDocuments', (await users.countDocuments({})) === 4);
|
|
check('countDocuments with filter', (await users.countDocuments({ age: { $gte: 30 } })) === 3);
|
|
check('estimatedDocumentCount', (await users.estimatedDocumentCount()) === 4);
|
|
|
|
// --- update ---
|
|
const u1 = await users.updateOne({ name: 'alice' }, { $set: { vip: true }, $inc: { age: 1 } });
|
|
check('updateOne nModified', u1.modifiedCount === 1, u1);
|
|
const alice = await users.findOne({ name: 'alice' });
|
|
check('updateOne $set+$inc applied', alice.vip === true && alice.age === 31, JSON.stringify(alice));
|
|
|
|
const um = await users.updateMany({}, { $set: { seen: true } });
|
|
check('updateMany', um.modifiedCount === 4, um);
|
|
|
|
const push = await users.updateOne({ name: 'dave' }, { $push: { tags: 'x' } });
|
|
check('$push', push.modifiedCount === 1);
|
|
check('$push visible', (await users.findOne({ name: 'dave' })).tags.length === 1);
|
|
|
|
const ups = await users.updateOne({ name: 'erin' }, { $set: { age: 28 } }, { upsert: true });
|
|
check('upsert', ups.upsertedCount === 1 && ups.matchedCount === 0, ups);
|
|
check('upsert doc exists', (await users.findOne({ name: 'erin' }))?.age === 28);
|
|
|
|
// --- findOneAndUpdate (findAndModify) ---
|
|
const fam = await users.findOneAndUpdate(
|
|
{ name: 'bob' },
|
|
{ $set: { lucky: true } },
|
|
{ returnDocument: 'after' },
|
|
);
|
|
const famDoc = fam.value ?? fam;
|
|
check('findOneAndUpdate returns new', famDoc.lucky === true, JSON.stringify(fam));
|
|
const famRemove = await users.findOneAndDelete({ name: 'erin' });
|
|
const famDelDoc = famRemove.value ?? famRemove;
|
|
check('findOneAndDelete', famDelDoc?.name === 'erin', JSON.stringify(famRemove));
|
|
|
|
// --- aggregate ---
|
|
const grp = await users
|
|
.aggregate([
|
|
{ $match: { age: { $gte: 25 } } },
|
|
{ $group: { _id: '$tags.length', total: { $sum: '$age' } } },
|
|
{ $sort: { _id: 1 } },
|
|
])
|
|
.toArray();
|
|
check('aggregate $match+$group+$sum+$sort', grp.length >= 1 && grp.some((g) => g.total > 0), JSON.stringify(grp));
|
|
|
|
const cnt = await users.aggregate([{ $match: {} }, { $count: 'n' }]).toArray();
|
|
check('aggregate $count', cnt[0]?.n === 4, JSON.stringify(cnt));
|
|
|
|
// --- duplicate key ---
|
|
let dupErr = null;
|
|
try {
|
|
await users.insertOne({ _id: many.insertedIds[0], name: 'clobber' });
|
|
} catch (e) {
|
|
dupErr = e;
|
|
}
|
|
check('duplicate key rejected', dupErr?.code === 11000, dupErr?.message);
|
|
|
|
// --- listCollections / listDatabases ---
|
|
const colls = await db.listCollections({}, { nameOnly: true }).toArray();
|
|
check('listCollections', colls.some((c) => c.name === 'users'), JSON.stringify(colls));
|
|
const dbs = await client.db('admin').admin().listDatabases();
|
|
check('listDatabases', dbs.databases.some((d) => d.name === 'e2e'), JSON.stringify(dbs.databases.map((d) => d.name)));
|
|
|
|
// --- delete ---
|
|
const del1 = await users.deleteOne({ name: 'dave' });
|
|
check('deleteOne', del1.deletedCount === 1, del1);
|
|
const delMany = await users.deleteMany({});
|
|
check('deleteMany', delMany.deletedCount === 3, delMany);
|
|
check('empty after delete', (await users.countDocuments({})) === 0);
|
|
|
|
await client.close();
|
|
|
|
const failed = results.filter((r) => !r.ok);
|
|
console.log(`\n${results.length - failed.length}/${results.length} checks passed`);
|
|
if (failed.length) {
|
|
console.log('FAILED:', failed.map((f) => f.name).join(', '));
|
|
process.exit(1);
|
|
}
|
|
console.log('E2E_OK');
|
|
}
|
|
|
|
main().catch((e) => {
|
|
console.error('E2E_FAIL', e);
|
|
process.exit(1);
|
|
});
|