Files
MultiforaDB/tests/e2e/e2e.js
Aleksey Shakhmatov d90cde394c commands/e2e: drop topologyVersion from the handshake; rename to mongo-lite
Advertising topologyVersion in the hello reply is what tells a driver the
server speaks the streaming (awaitable) hello protocol — in the Node driver
it is the only condition checked. From the second heartbeat on, the driver
then monitored with an exhaust hello (exhaustAllowed + maxAwaitTimeMS) and
waited for a stream of replies carrying moreToCome. We answered once with
the flag clear and went back to reading, so every heartbeat failed with
"Server ended moreToCome unexpectedly", destroying the connection and
clearing the pool. MongoDB Compass showed this as a connect/disconnect loop
once per heartbeat.

We do not implement streaming hello, so we must not claim to. Omitting the
field keeps monitoring on the polling path, and agrees with the
maxWireVersion 8 we report: streaming hello arrived in wire version 9.

The existing e2e files all passed against the broken server — they issue
their commands and exit before the second heartbeat — so e2e5 watches SDAM
heartbeats on an idle connection instead.

Also renames mongo-light to mongo-lite throughout (binary, log messages,
docs, gitVersion). Unrelated to the fix above, but squashed in at request
rather than left as a commit whose message described only the fix.
2026-08-02 15:09:25 +03:00

133 lines
5.5 KiB
JavaScript

// End-to-end test: official MongoDB Node.js driver against mongo-lite.
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);
});