@@ -10,6 +10,7 @@ import {
10
10
isRecord ,
11
11
MongoClient ,
12
12
MongoCompatibilityError ,
13
+ MongoError ,
13
14
MongoNetworkError ,
14
15
MongoNetworkTimeoutError ,
15
16
MongoServerError ,
@@ -25,6 +26,7 @@ import {
25
26
ServerHeartbeatStartedEvent ,
26
27
ServerHeartbeatSucceededEvent ,
27
28
ServerOpeningEvent ,
29
+ squashError ,
28
30
Topology ,
29
31
TOPOLOGY_CLOSED ,
30
32
TOPOLOGY_DESCRIPTION_CHANGED ,
@@ -108,6 +110,7 @@ interface MonitoringOutcome {
108
110
interface OutcomeServerDescription {
109
111
type ?: string ;
110
112
setName ?: string ;
113
+ error ?: { message : string } ;
111
114
setVersion ?: number ;
112
115
electionId ?: ObjectId | null ;
113
116
logicalSessionTimeoutMinutes ?: number ;
@@ -268,6 +271,7 @@ function normalizeServerDescription(serverDescription) {
268
271
// it as `Unknown`.
269
272
serverDescription . type = 'Unknown' ;
270
273
}
274
+ serverDescription . error == serverDescription ?. error ?. message ;
271
275
272
276
return serverDescription ;
273
277
}
@@ -322,84 +326,88 @@ async function executeSDAMTest(testData: SDAMTest) {
322
326
// connect the topology
323
327
await client . connect ( ) ;
324
328
325
- for ( const phase of testData . phases ) {
326
- // Determine which of the two kinds of phases we're running
327
- if ( 'responses' in phase && phase . responses != null ) {
328
- // phase with responses for hello simulations
329
- for ( const [ address , hello ] of phase . responses ) {
330
- client . topology . serverUpdateHandler ( new ServerDescription ( address , hello ) ) ;
331
- }
332
- } else if ( 'applicationErrors' in phase && phase . applicationErrors ) {
333
- // phase with applicationErrors simulating error's from network, timeouts, server
334
- for ( const appError of phase . applicationErrors ) {
335
- // Stub will return appError to SDAM machinery
336
- const checkOutStub = sinon
337
- . stub ( ConnectionPool . prototype , 'checkOut' )
338
- . callsFake ( checkoutStubImpl ( appError ) ) ;
339
-
340
- const server = client . topology . s . servers . get ( appError . address ) ;
341
-
342
- // Run a dummy command to encounter the error
343
- const res = server . command . bind ( server ) ( ns ( 'admin.$cmd' ) , { ping : 1 } , { } ) ;
344
- const thrownError = await res . catch ( error => error ) ;
345
-
346
- // Restore the stub before asserting anything in case of errors
347
- checkOutStub . restore ( ) ;
348
-
349
- const isApplicationError = error => {
350
- // These errors all come from the withConnection stub
351
- return (
352
- error instanceof MongoNetworkError ||
353
- error instanceof MongoNetworkTimeoutError ||
354
- error instanceof MongoServerError
355
- ) ;
356
- } ;
357
- expect (
358
- thrownError ,
359
- `expected the error thrown to be one of MongoNetworkError, MongoNetworkTimeoutError or MongoServerError (referred to in the spec as an "Application Error") got ${ thrownError . name } ${ thrownError . stack } `
360
- ) . to . satisfy ( isApplicationError ) ;
329
+ try {
330
+ for ( const phase of testData . phases ) {
331
+ // Determine which of the two kinds of phases we're running
332
+ if ( 'responses' in phase && phase . responses != null ) {
333
+ // phase with responses for hello simulations
334
+ for ( const [ address , hello ] of phase . responses ) {
335
+ client . topology . serverUpdateHandler ( new ServerDescription ( address , hello ) ) ;
336
+ }
337
+ } else if ( 'applicationErrors' in phase && phase . applicationErrors ) {
338
+ // phase with applicationErrors simulating error's from network, timeouts, server
339
+ for ( const appError of phase . applicationErrors ) {
340
+ // Stub will return appError to SDAM machinery
341
+ const checkOutStub = sinon
342
+ . stub ( ConnectionPool . prototype , 'checkOut' )
343
+ . callsFake ( checkoutStubImpl ( appError ) ) ;
344
+
345
+ const server = client . topology . s . servers . get ( appError . address ) ;
346
+
347
+ // Run a dummy command to encounter the error
348
+ const res = server . command . bind ( server ) ( ns ( 'admin.$cmd' ) , { ping : 1 } , { } ) ;
349
+ const thrownError = await res . catch ( error => error ) ;
350
+
351
+ // Restore the stub before asserting anything in case of errors
352
+ checkOutStub . restore ( ) ;
353
+
354
+ const isApplicationError = error => {
355
+ // These errors all come from the withConnection stub
356
+ return (
357
+ error instanceof MongoNetworkError ||
358
+ error instanceof MongoNetworkTimeoutError ||
359
+ error instanceof MongoServerError
360
+ ) ;
361
+ } ;
362
+ expect (
363
+ thrownError ,
364
+ `expected the error thrown to be one of MongoNetworkError, MongoNetworkTimeoutError or MongoServerError (referred to in the spec as an "Application Error") got ${ thrownError . name } ${ thrownError . stack } `
365
+ ) . to . satisfy ( isApplicationError ) ;
366
+ }
367
+ } else if ( phase . outcome != null && Object . keys ( phase ) . length === 1 ) {
368
+ // Load Balancer SDAM tests have no "work" to be done for the phase
369
+ } else {
370
+ expect . fail ( ejson `Unknown phase shape - ${ phase } ` ) ;
361
371
}
362
- } else if ( phase . outcome != null && Object . keys ( phase ) . length === 1 ) {
363
- // Load Balancer SDAM tests have no "work" to be done for the phase
364
- } else {
365
- expect . fail ( ejson `Unknown phase shape - ${ phase } ` ) ;
366
- }
367
372
368
- if ( 'outcome' in phase && phase . outcome != null ) {
369
- if ( isMonitoringOutcome ( phase . outcome ) ) {
370
- // Test for monitoring events
371
- const expectedEvents = convertOutcomeEvents ( phase . outcome . events ) ;
373
+ if ( 'outcome' in phase && phase . outcome != null ) {
374
+ if ( isMonitoringOutcome ( phase . outcome ) ) {
375
+ // Test for monitoring events
376
+ const expectedEvents = convertOutcomeEvents ( phase . outcome . events ) ;
372
377
373
- expect ( events ) . to . have . length ( expectedEvents . length ) ;
374
- for ( const [ i , actualEvent ] of Object . entries ( events ) ) {
375
- const actualEventClone = cloneForCompare ( actualEvent ) ;
376
- expect ( actualEventClone ) . to . matchMongoSpec ( expectedEvents [ i ] ) ;
377
- }
378
- } else if ( isTopologyDescriptionOutcome ( phase . outcome ) ) {
379
- // Test for SDAM machinery correctly changing the topology type among other properties
380
- assertTopologyDescriptionOutcomeExpectations ( client . topology , phase . outcome ) ;
381
- if ( phase . outcome . compatible === false ) {
382
- // driver specific error throwing
383
- if ( testData . description === 'Multiple mongoses with large minWireVersion' ) {
384
- // TODO(DRIVERS-2250): There is test bug that causes two errors
385
- // this will start failing when the test is synced and fixed
386
- expect ( errorsThrown ) . to . have . lengthOf ( 2 ) ;
378
+ expect ( events ) . to . have . length ( expectedEvents . length ) ;
379
+ for ( const [ i , actualEvent ] of Object . entries ( events ) ) {
380
+ const actualEventClone = cloneForCompare ( actualEvent ) ;
381
+ expect ( actualEventClone ) . to . matchMongoSpec ( expectedEvents [ i ] ) ;
382
+ }
383
+ } else if ( isTopologyDescriptionOutcome ( phase . outcome ) ) {
384
+ // Test for SDAM machinery correctly changing the topology type among other properties
385
+ assertTopologyDescriptionOutcomeExpectations ( client . topology , phase . outcome ) ;
386
+ if ( phase . outcome . compatible === false ) {
387
+ // driver specific error throwing
388
+ if ( testData . description === 'Multiple mongoses with large minWireVersion' ) {
389
+ // TODO(DRIVERS-2250): There is test bug that causes two errors
390
+ // this will start failing when the test is synced and fixed
391
+ expect ( errorsThrown ) . to . have . lengthOf ( 2 ) ;
392
+ } else {
393
+ expect ( errorsThrown ) . to . have . lengthOf ( 1 ) ;
394
+ }
395
+ expect ( errorsThrown [ 0 ] ) . to . be . instanceOf ( MongoCompatibilityError ) ;
396
+ expect ( errorsThrown [ 0 ] . message ) . to . match ( / b u t t h i s v e r s i o n o f t h e d r i v e r / ) ;
387
397
} else {
388
- expect ( errorsThrown ) . to . have . lengthOf ( 1 ) ;
398
+ // unset or true means no errors should be thrown
399
+ expect ( errorsThrown ) . to . be . empty ;
389
400
}
390
- expect ( errorsThrown [ 0 ] ) . to . be . instanceOf ( MongoCompatibilityError ) ;
391
- expect ( errorsThrown [ 0 ] . message ) . to . match ( / b u t t h i s v e r s i o n o f t h e d r i v e r / ) ;
392
401
} else {
393
- // unset or true means no errors should be thrown
394
- expect ( errorsThrown ) . to . be . empty ;
402
+ expect . fail ( ejson `Unknown outcome shape - ${ phase . outcome } ` ) ;
395
403
}
396
- } else {
397
- expect . fail ( ejson `Unknown outcome shape - ${ phase . outcome } ` ) ;
398
- }
399
404
400
- events = [ ] ;
401
- errorsThrown = [ ] ;
405
+ events = [ ] ;
406
+ errorsThrown = [ ] ;
407
+ }
402
408
}
409
+ } finally {
410
+ await client . close ( ) . catch ( squashError ) ;
403
411
}
404
412
}
405
413
@@ -464,8 +472,12 @@ function assertTopologyDescriptionOutcomeExpectations(
464
472
if ( WIRE_VERSION_KEYS . has ( expectedKey ) && expectedValue === null ) {
465
473
// For wireVersion keys we default to zero instead of null
466
474
expect ( actualServer ) . to . have . property ( expectedKey , 0 ) ;
467
- } else {
475
+ } else if ( expectedKey !== 'error' ) {
468
476
expect ( actualServer ) . to . have . deep . property ( expectedKey , expectedValue ) ;
477
+ } else {
478
+ // Check that if we have
479
+ expect ( actualServer ) . to . have . deep . property ( expectedKey ) . instanceof ( MongoError ) ;
480
+ expect ( actualServer ) . property ( expectedKey ) . property ( 'message' ) . includes ( expectedValue ) ;
469
481
}
470
482
}
471
483
}
0 commit comments