Skip to content

Commit 541a8e0

Browse files
fix SDAM unit test behavior
1 parent 151b986 commit 541a8e0

File tree

2 files changed

+108
-33
lines changed

2 files changed

+108
-33
lines changed

test/spec/server-discovery-and-monitoring/errors/error_handling_handshake.json

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -97,14 +97,22 @@
9797
"outcome": {
9898
"servers": {
9999
"a:27017": {
100-
"type": "Unknown",
101-
"topologyVersion": null,
100+
"type": "RSPrimary",
101+
"setName": "rs",
102+
"topologyVersion": {
103+
"processId": {
104+
"$oid": "000000000000000000000001"
105+
},
106+
"counter": {
107+
"$numberLong": "1"
108+
}
109+
},
102110
"pool": {
103-
"generation": 1
111+
"generation": 0
104112
}
105113
}
106114
},
107-
"topologyType": "ReplicaSetNoPrimary",
115+
"topologyType": "ReplicaSetWithPrimary",
108116
"logicalSessionTimeoutMinutes": null,
109117
"setName": "rs"
110118
}

test/unit/assorted/server_discovery_and_monitoring.spec.test.ts

Lines changed: 96 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@ import * as fs from 'fs';
44
import * as path from 'path';
55
import * as sinon from 'sinon';
66

7+
import { Connection } from '../../../src/cmap/connection';
78
import { ConnectionPool } from '../../../src/cmap/connection_pool';
89
import {
910
HEARTBEAT_EVENTS,
11+
LEGACY_HELLO_COMMAND,
1012
SERVER_CLOSED,
1113
SERVER_DESCRIPTION_CHANGED,
1214
SERVER_OPENING,
@@ -37,6 +39,7 @@ import {
3739
import { Server } from '../../../src/sdam/server';
3840
import { ServerDescription, type TopologyVersion } from '../../../src/sdam/server_description';
3941
import { Topology } from '../../../src/sdam/topology';
42+
import { TimeoutContext } from '../../../src/timeout';
4043
import { isRecord, ns, squashError } from '../../../src/utils';
4144
import { ejson, fakeServer } from '../../tools/utils';
4245

@@ -188,7 +191,7 @@ function assertMonitoringOutcome(outcome: any): asserts outcome is MonitoringOut
188191
expect(outcome).to.have.property('events').that.is.an('array');
189192
}
190193

191-
describe('Server Discovery and Monitoring (spec)', function () {
194+
describe.only('Server Discovery and Monitoring (spec)', function () {
192195
let serverConnect: sinon.SinonStub;
193196

194197
before(() => {
@@ -311,6 +314,89 @@ const SDAM_EVENTS = [
311314
...HEARTBEAT_EVENTS
312315
];
313316

317+
function checkoutStubImpl(appError: ApplicationError) {
318+
return async function () {
319+
const connectionPoolGeneration = this.generation;
320+
const fakeConnection = {
321+
generation:
322+
typeof appError.generation === 'number' ? appError.generation : connectionPoolGeneration,
323+
async command(_, __, ___) {
324+
switch (appError.type) {
325+
case 'network':
326+
throw new MongoNetworkError('test generated');
327+
case 'timeout':
328+
throw new MongoNetworkTimeoutError('xxx timed out');
329+
case 'command':
330+
throw new MongoServerError(appError.response);
331+
default:
332+
throw new Error(
333+
// @ts-expect-error `.type` is never, but we want to access it in this unreachable code to
334+
// throw an error message.
335+
`SDAM unit test runner error: unexpected appError.type field: ${appError.type}`
336+
);
337+
}
338+
}
339+
};
340+
return fakeConnection as any as Connection;
341+
};
342+
}
343+
344+
function stubConnectionEstablishment(appError: ApplicationError) {
345+
const stubs = [];
346+
if (appError.when === 'afterHandshakeCompletes') {
347+
const checkOutStub = sinon
348+
.stub(ConnectionPool.prototype, 'checkOut')
349+
.callsFake(checkoutStubImpl(appError));
350+
stubs.push(checkOutStub);
351+
return stubs;
352+
}
353+
354+
// eslint-disable-next-line @typescript-eslint/no-require-imports
355+
const net: typeof import('net') = require('net');
356+
357+
const netStub = sinon.stub(net, 'createConnection');
358+
359+
netStub.callsFake(function createConnectionStub() {
360+
const socket = new net.Socket();
361+
process.nextTick(() => socket.emit('connect'));
362+
return socket;
363+
});
364+
365+
stubs.push(netStub);
366+
367+
class StubbedConnection extends Connection {
368+
override command(
369+
_ns: unknown,
370+
command: Document,
371+
_options?: unknown,
372+
_responseType?: unknown
373+
): Promise<any> {
374+
if (command.hello || command[LEGACY_HELLO_COMMAND]) {
375+
throw new MongoNetworkError(`error executing command`, { beforeHandshake: true });
376+
}
377+
378+
throw new Error('unexpected command: ', command);
379+
}
380+
}
381+
382+
// eslint-disable-next-line @typescript-eslint/no-require-imports
383+
const connectionUtils: typeof import('../../../src/cmap/connect') = require('../../../src/cmap/connect');
384+
385+
const wrapped = sinon.stub(connectionUtils, 'connect').callsFake(async function connect(options) {
386+
const generation =
387+
typeof appError.generation === 'number' ? appError.generation : options.generation;
388+
return wrapped.wrappedMethod({
389+
...options,
390+
generation,
391+
connectionType: StubbedConnection
392+
});
393+
});
394+
395+
stubs.push(wrapped);
396+
397+
return stubs;
398+
}
399+
314400
async function executeSDAMTest(testData: SDAMTest) {
315401
const client = new MongoClient(testData.uri);
316402
// listen for SDAM monitoring events
@@ -337,20 +423,23 @@ async function executeSDAMTest(testData: SDAMTest) {
337423
// phase with applicationErrors simulating error's from network, timeouts, server
338424
for (const appError of phase.applicationErrors) {
339425
// Stub will return appError to SDAM machinery
340-
const checkOutStub = sinon
341-
.stub(ConnectionPool.prototype, 'checkOut')
342-
.callsFake(checkoutStubImpl(appError));
426+
427+
const stubs = stubConnectionEstablishment(appError);
343428

344429
const server = client.topology.s.servers.get(appError.address);
345430

346431
// Run a dummy command to encounter the error
347-
const res = server.command.bind(server)(
348-
new RunCommandOperation(ns('admin.$cmd'), { ping: 1 }, {})
432+
const res = server.command(
433+
new RunCommandOperation(ns('admin.$cmd'), { ping: 1 }, {}),
434+
TimeoutContext.create({
435+
serverSelectionTimeoutMS: 30_000,
436+
waitQueueTimeoutMS: 10_000
437+
})
349438
);
350439
const thrownError = await res.catch(error => error);
351440

352441
// Restore the stub before asserting anything in case of errors
353-
checkOutStub.restore();
442+
stubs.forEach(stub => stub.restore());
354443

355444
const isApplicationError = error => {
356445
// These errors all come from the withConnection stub
@@ -412,28 +501,6 @@ async function executeSDAMTest(testData: SDAMTest) {
412501
}
413502
}
414503

415-
function checkoutStubImpl(appError) {
416-
return async function () {
417-
const connectionPoolGeneration = this.generation;
418-
const fakeConnection = {
419-
generation:
420-
typeof appError.generation === 'number' ? appError.generation : connectionPoolGeneration,
421-
async command(_, __, ___) {
422-
if (appError.type === 'network') {
423-
throw new MongoNetworkError('test generated');
424-
} else if (appError.type === 'timeout') {
425-
throw new MongoNetworkTimeoutError('xxx timed out', {
426-
beforeHandshake: appError.when === 'beforeHandshakeCompletes'
427-
});
428-
} else {
429-
throw new MongoServerError(appError.response);
430-
}
431-
}
432-
};
433-
return fakeConnection;
434-
};
435-
}
436-
437504
function assertTopologyDescriptionOutcomeExpectations(
438505
topology: Topology,
439506
outcome: TopologyDescriptionOutcome

0 commit comments

Comments
 (0)