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.
This commit is contained in:
2026-08-02 15:09:25 +03:00
parent dc92064b95
commit d90cde394c
14 changed files with 148 additions and 45 deletions

View File

@@ -1,6 +1,6 @@
# End-to-end tests with the official MongoDB Node.js driver
These exercise mongo-light from a real driver over TCP: full CRUD, query
These exercise mongo-lite from a real driver over TCP: full CRUD, query
operators, aggregation, error codes, concurrent clients, and crash recovery.
## Setup
@@ -17,7 +17,7 @@ Start the server, then run the suites against it (defaults to port 27020):
```sh
zig build
zig-out/bin/mongo-light --port 27020 --db /tmp/ml-e2e.log --ttl-sweep-secs 1 &
zig-out/bin/mongo-lite --port 27020 --db /tmp/ml-e2e.log --ttl-sweep-secs 1 &
node tests/e2e/e2e.js # CRUD + operators + aggregate + errors (29 checks)
node tests/e2e/e2e2.js concurrent # 8 clients: 4 writers + 4 readers (2 checks)
@@ -33,7 +33,7 @@ care about the flag.
Rebuild with `zig build` after any change under `src/` before restarting the
server: `zig build test` compiles the test binary only and leaves
`zig-out/bin/mongo-light` stale, so the suites keep running against the old
`zig-out/bin/mongo-lite` stale, so the suites keep running against the old
rules and report failures that the source no longer explains.
`e2e2.js concurrent` is safe to repeat against a running server (it drops its

View File

@@ -1,4 +1,4 @@
// End-to-end test: official MongoDB Node.js driver against mongo-light.
// 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';

View File

@@ -4,7 +4,7 @@
// server must reject with CannotCreateIndex (67).
//
// The server must run with a short sweep interval, e.g.
// zig-out/bin/mongo-light --port 27020 --db /tmp/ml-e2e.log --ttl-sweep-secs 1
// zig-out/bin/mongo-lite --port 27020 --db /tmp/ml-e2e.log --ttl-sweep-secs 1
const { MongoClient } = require('mongodb');
const URL = 'mongodb://127.0.0.1:27020';

75
tests/e2e/e2e5.js Normal file
View File

@@ -0,0 +1,75 @@
// E2E part 5: SDAM monitoring stability, official driver.
// Every other e2e file issues its commands and exits, so all of them pass
// against a server whose *monitoring* is broken. This one just sits on an
// idle connection and watches the driver's heartbeats.
//
// The failure it guards against: advertising `topologyVersion` in the hello
// reply makes the driver monitor with an exhaust hello and expect a stream of
// moreToCome replies. A server that answers once and goes back to reading
// fails the heartbeat ("Server ended moreToCome unexpectedly"), the driver
// drops the connection and clears the pool, and the client — MongoDB Compass,
// say — shows a connect/disconnect loop once per heartbeat.
const { MongoClient } = 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}`);
}
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
// 500ms is the driver's floor for heartbeatFrequencyMS; over WATCH_MS it gives
// ~10 heartbeats, enough that a per-heartbeat failure cannot hide.
const HEARTBEAT_MS = 500;
const WATCH_MS = 5000;
async function main() {
const client = new MongoClient(URL, {
heartbeatFrequencyMS: HEARTBEAT_MS,
serverSelectionTimeoutMS: 5000,
});
let succeeded = 0;
const failures = [];
client.on('serverHeartbeatSucceeded', () => succeeded++);
client.on('serverHeartbeatFailed', (e) => failures.push(e.failure ? e.failure.message : 'unknown'));
// The pool is cleared when SDAM decides the server went away; on a healthy
// idle connection it should never happen.
client.on('connectionPoolCleared', () => failures.push('connectionPoolCleared'));
await client.connect();
await client.db('e2e5').command({ ping: 1 });
await sleep(WATCH_MS);
check('heartbeats actually ran', succeeded >= 5, `${succeeded} succeeded`);
check('no heartbeat failed', failures.length === 0, failures.join(', '));
// A reset pool still recovers, so liveness alone would not catch the loop —
// check it anyway, since a dead connection here means something worse.
let usable = true;
try {
await client.db('e2e5').command({ ping: 1 });
} catch (e) {
usable = false;
check('connection usable after idle period', false, e.message);
}
if (usable) check('connection usable after idle period', true);
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('E2E5_OK');
}
main().catch((e) => {
console.error('E2E5_FAIL', e);
process.exit(1);
});

View File

@@ -1,7 +1,7 @@
{
"name": "e2e",
"version": "1.0.0",
"description": "These exercise mongo-light from a real driver over TCP: full CRUD, query operators, aggregation, error codes, concurrent clients, and crash recovery.",
"description": "These exercise mongo-lite from a real driver over TCP: full CRUD, query operators, aggregation, error codes, concurrent clients, and crash recovery.",
"main": "e2e.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"