@@ -4,9 +4,11 @@ import * as fs from 'fs';
44import * as path from 'path' ;
55import * as sinon from 'sinon' ;
66
7+ import { Connection } from '../../../src/cmap/connection' ;
78import { ConnectionPool } from '../../../src/cmap/connection_pool' ;
89import {
910 HEARTBEAT_EVENTS ,
11+ LEGACY_HELLO_COMMAND ,
1012 SERVER_CLOSED ,
1113 SERVER_DESCRIPTION_CHANGED ,
1214 SERVER_OPENING ,
@@ -37,6 +39,7 @@ import {
3739import { Server } from '../../../src/sdam/server' ;
3840import { ServerDescription , type TopologyVersion } from '../../../src/sdam/server_description' ;
3941import { Topology } from '../../../src/sdam/topology' ;
42+ import { TimeoutContext } from '../../../src/timeout' ;
4043import { isRecord , ns , squashError } from '../../../src/utils' ;
4144import { 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+
314400async 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-
437504function assertTopologyDescriptionOutcomeExpectations (
438505 topology : Topology ,
439506 outcome : TopologyDescriptionOutcome
0 commit comments