@@ -31,6 +31,7 @@ const connectionOptionsDefaults = {
31
31
32
32
/** The absolute minimum socket API needed by Connection as of writing this test */
33
33
class FakeSocket extends EventEmitter {
34
+ writableEnded : boolean ;
34
35
address ( ) {
35
36
// is never called
36
37
}
@@ -39,6 +40,14 @@ class FakeSocket extends EventEmitter {
39
40
}
40
41
destroy ( ) {
41
42
// is called, has no side effects
43
+ this . writableEnded = true ;
44
+ }
45
+ end ( cb ) {
46
+ this . writableEnded = true ;
47
+ // nextTick to simulate I/O delay
48
+ if ( typeof cb === 'function' ) {
49
+ process . nextTick ( cb ) ;
50
+ }
42
51
}
43
52
get remoteAddress ( ) {
44
53
return 'iLoveJavaScript' ;
@@ -48,6 +57,20 @@ class FakeSocket extends EventEmitter {
48
57
}
49
58
}
50
59
60
+ class InputStream extends Readable {
61
+ writableEnded : boolean ;
62
+ constructor ( options ?) {
63
+ super ( options ) ;
64
+ }
65
+
66
+ end ( cb ) {
67
+ this . writableEnded = true ;
68
+ if ( typeof cb === 'function' ) {
69
+ process . nextTick ( cb ) ;
70
+ }
71
+ }
72
+ }
73
+
51
74
describe ( 'new Connection()' , function ( ) {
52
75
let server ;
53
76
after ( ( ) => mock . cleanup ( ) ) ;
@@ -106,7 +129,7 @@ describe('new Connection()', function () {
106
129
expect ( err ) . to . be . instanceOf ( MongoNetworkTimeoutError ) ;
107
130
expect ( result ) . to . not . exist ;
108
131
109
- expect ( conn ) . property ( 'stream' ) . property ( 'destroyed ' , true ) ;
132
+ expect ( conn ) . property ( 'stream' ) . property ( 'writableEnded ' , true ) ;
110
133
111
134
done ( ) ;
112
135
} ) ;
@@ -175,7 +198,7 @@ describe('new Connection()', function () {
175
198
176
199
context ( 'when multiple hellos exist on the stream' , function ( ) {
177
200
let callbackSpy ;
178
- const inputStream = new Readable ( ) ;
201
+ const inputStream = new InputStream ( ) ;
179
202
const document = { ok : 1 } ;
180
203
const last = { isWritablePrimary : true } ;
181
204
@@ -394,7 +417,7 @@ describe('new Connection()', function () {
394
417
connection = sinon . spy ( new Connection ( driverSocket , connectionOptionsDefaults ) ) ;
395
418
const messageStreamSymbol = getSymbolFrom ( connection , 'messageStream' ) ;
396
419
kDelayedTimeoutId = getSymbolFrom ( connection , 'delayedTimeoutId' ) ;
397
- messageStream = connection [ messageStreamSymbol ] ;
420
+ messageStream = sinon . spy ( connection [ messageStreamSymbol ] ) ;
398
421
} ) ;
399
422
400
423
afterEach ( ( ) => {
@@ -407,13 +430,15 @@ describe('new Connection()', function () {
407
430
408
431
driverSocket . emit ( 'timeout' ) ;
409
432
expect ( connection . onTimeout ) . to . have . been . calledOnce ;
433
+ expect ( connection . destroy ) . to . not . have . been . called ;
410
434
expect ( connection ) . to . have . property ( kDelayedTimeoutId ) . that . is . instanceOf ( NodeJSTimeoutClass ) ;
411
435
expect ( connection ) . to . have . property ( 'closed' , false ) ;
412
- expect ( driverSocket . destroy ) . to . not . have . been . called ;
436
+ expect ( driverSocket . end ) . to . not . have . been . called ;
413
437
414
438
clock . tick ( 1 ) ;
415
439
416
- expect ( driverSocket . destroy ) . to . have . been . calledOnce ;
440
+ expect ( driverSocket . end ) . to . have . been . calledOnce ;
441
+ expect ( connection . destroy ) . to . have . been . calledOnce ;
417
442
expect ( connection ) . to . have . property ( 'closed' , true ) ;
418
443
} ) ;
419
444
@@ -438,6 +463,88 @@ describe('new Connection()', function () {
438
463
expect ( connection ) . to . have . property ( 'closed' , false ) ;
439
464
expect ( connection ) . to . have . property ( kDelayedTimeoutId , null ) ;
440
465
} ) ;
466
+
467
+ it ( 'destroys the message stream and socket' , ( ) => {
468
+ expect ( connection ) . to . have . property ( kDelayedTimeoutId , null ) ;
469
+
470
+ driverSocket . emit ( 'timeout' ) ;
471
+
472
+ clock . tick ( 1 ) ;
473
+
474
+ expect ( connection . onTimeout ) . to . have . been . calledOnce ;
475
+ expect ( connection ) . to . have . property ( kDelayedTimeoutId ) . that . is . instanceOf ( NodeJSTimeoutClass ) ;
476
+
477
+ expect ( messageStream . destroy ) . to . have . been . calledOnce ;
478
+ expect ( driverSocket . destroy ) . to . not . have . been . called ;
479
+ expect ( driverSocket . end ) . to . have . been . calledOnce ;
480
+ } ) ;
481
+ } ) ;
482
+
483
+ describe ( 'onError()' , ( ) => {
484
+ let connection : sinon . SinonSpiedInstance < Connection > ;
485
+ let clock : sinon . SinonFakeTimers ;
486
+ let timerSandbox : sinon . SinonFakeTimers ;
487
+ let driverSocket : sinon . SinonSpiedInstance < FakeSocket > ;
488
+ let messageStream : MessageStream ;
489
+ beforeEach ( ( ) => {
490
+ timerSandbox = createTimerSandbox ( ) ;
491
+ clock = sinon . useFakeTimers ( ) ;
492
+ driverSocket = sinon . spy ( new FakeSocket ( ) ) ;
493
+ // @ts -expect-error: driverSocket does not fully satisfy the stream type, but that's okay
494
+ connection = sinon . spy ( new Connection ( driverSocket , connectionOptionsDefaults ) ) ;
495
+ const messageStreamSymbol = getSymbolFrom ( connection , 'messageStream' ) ;
496
+ messageStream = sinon . spy ( connection [ messageStreamSymbol ] ) ;
497
+ } ) ;
498
+
499
+ afterEach ( ( ) => {
500
+ timerSandbox . restore ( ) ;
501
+ clock . restore ( ) ;
502
+ } ) ;
503
+
504
+ it ( 'destroys the message stream and socket' , ( ) => {
505
+ messageStream . emit ( 'error' ) ;
506
+ clock . tick ( 1 ) ;
507
+ expect ( connection . onError ) . to . have . been . calledOnce ;
508
+ connection . destroy ( { force : false } ) ;
509
+ clock . tick ( 1 ) ;
510
+ expect ( messageStream . destroy ) . to . have . been . called ;
511
+ expect ( driverSocket . destroy ) . to . not . have . been . called ;
512
+ expect ( driverSocket . end ) . to . have . been . calledOnce ;
513
+ } ) ;
514
+ } ) ;
515
+
516
+ describe ( 'onClose()' , ( ) => {
517
+ let connection : sinon . SinonSpiedInstance < Connection > ;
518
+ let clock : sinon . SinonFakeTimers ;
519
+ let timerSandbox : sinon . SinonFakeTimers ;
520
+ let driverSocket : sinon . SinonSpiedInstance < FakeSocket > ;
521
+ let messageStream : MessageStream ;
522
+ beforeEach ( ( ) => {
523
+ timerSandbox = createTimerSandbox ( ) ;
524
+ clock = sinon . useFakeTimers ( ) ;
525
+
526
+ driverSocket = sinon . spy ( new FakeSocket ( ) ) ;
527
+ // @ts -expect-error: driverSocket does not fully satisfy the stream type, but that's okay
528
+ connection = sinon . spy ( new Connection ( driverSocket , connectionOptionsDefaults ) ) ;
529
+ const messageStreamSymbol = getSymbolFrom ( connection , 'messageStream' ) ;
530
+ messageStream = sinon . spy ( connection [ messageStreamSymbol ] ) ;
531
+ } ) ;
532
+
533
+ afterEach ( ( ) => {
534
+ timerSandbox . restore ( ) ;
535
+ clock . restore ( ) ;
536
+ } ) ;
537
+
538
+ it ( 'destroys the message stream and socket' , ( ) => {
539
+ driverSocket . emit ( 'close' ) ;
540
+ clock . tick ( 1 ) ;
541
+ expect ( connection . onClose ) . to . have . been . calledOnce ;
542
+ connection . destroy ( { force : false } ) ;
543
+ clock . tick ( 1 ) ;
544
+ expect ( messageStream . destroy ) . to . have . been . called ;
545
+ expect ( driverSocket . destroy ) . to . not . have . been . called ;
546
+ expect ( driverSocket . end ) . to . have . been . calledOnce ;
547
+ } ) ;
441
548
} ) ;
442
549
443
550
describe ( '.hasSessionSupport' , function ( ) {
@@ -491,4 +598,96 @@ describe('new Connection()', function () {
491
598
} ) ;
492
599
} ) ;
493
600
} ) ;
601
+
602
+ describe ( 'destroy()' , ( ) => {
603
+ let connection : sinon . SinonSpiedInstance < Connection > ;
604
+ let clock : sinon . SinonFakeTimers ;
605
+ let timerSandbox : sinon . SinonFakeTimers ;
606
+ let driverSocket : sinon . SinonSpiedInstance < FakeSocket > ;
607
+ let messageStream : MessageStream ;
608
+ beforeEach ( ( ) => {
609
+ timerSandbox = createTimerSandbox ( ) ;
610
+ clock = sinon . useFakeTimers ( ) ;
611
+
612
+ driverSocket = sinon . spy ( new FakeSocket ( ) ) ;
613
+ // @ts -expect-error: driverSocket does not fully satisfy the stream type, but that's okay
614
+ connection = sinon . spy ( new Connection ( driverSocket , connectionOptionsDefaults ) ) ;
615
+ const messageStreamSymbol = getSymbolFrom ( connection , 'messageStream' ) ;
616
+ messageStream = sinon . spy ( connection [ messageStreamSymbol ] ) ;
617
+ } ) ;
618
+
619
+ afterEach ( ( ) => {
620
+ timerSandbox . restore ( ) ;
621
+ clock . restore ( ) ;
622
+ } ) ;
623
+
624
+ context ( 'when options.force == true' , function ( ) {
625
+ it ( 'calls stream.destroy' , ( ) => {
626
+ connection . destroy ( { force : true } ) ;
627
+ clock . tick ( 1 ) ;
628
+ expect ( driverSocket . destroy ) . to . have . been . calledOnce ;
629
+ } ) ;
630
+
631
+ it ( 'does not call stream.end' , ( ) => {
632
+ connection . destroy ( { force : true } ) ;
633
+ clock . tick ( 1 ) ;
634
+ expect ( driverSocket . end ) . to . not . have . been . called ;
635
+ } ) ;
636
+
637
+ it ( 'destroys the tcp socket' , ( ) => {
638
+ connection . destroy ( { force : true } ) ;
639
+ clock . tick ( 1 ) ;
640
+ expect ( driverSocket . destroy ) . to . have . been . calledOnce ;
641
+ } ) ;
642
+
643
+ it ( 'destroys the messageStream' , ( ) => {
644
+ connection . destroy ( { force : true } ) ;
645
+ clock . tick ( 1 ) ;
646
+ expect ( messageStream . destroy ) . to . have . been . calledOnce ;
647
+ } ) ;
648
+
649
+ it ( 'calls stream.destroy whenever destroy is called ' , ( ) => {
650
+ connection . destroy ( { force : true } ) ;
651
+ connection . destroy ( { force : true } ) ;
652
+ connection . destroy ( { force : true } ) ;
653
+ clock . tick ( 1 ) ;
654
+ expect ( driverSocket . destroy ) . to . have . been . calledThrice ;
655
+ } ) ;
656
+ } ) ;
657
+
658
+ context ( 'when options.force == false' , function ( ) {
659
+ it ( 'calls stream.end' , ( ) => {
660
+ connection . destroy ( { force : false } ) ;
661
+ clock . tick ( 1 ) ;
662
+ expect ( driverSocket . end ) . to . have . been . calledOnce ;
663
+ } ) ;
664
+
665
+ it ( 'does not call stream.destroy' , ( ) => {
666
+ connection . destroy ( { force : false } ) ;
667
+ clock . tick ( 1 ) ;
668
+ expect ( driverSocket . destroy ) . to . not . have . been . called ;
669
+ } ) ;
670
+
671
+ it ( 'ends the tcp socket' , ( ) => {
672
+ connection . destroy ( { force : false } ) ;
673
+ clock . tick ( 1 ) ;
674
+ expect ( driverSocket . end ) . to . have . been . calledOnce ;
675
+ } ) ;
676
+
677
+ it ( 'destroys the messageStream' , ( ) => {
678
+ connection . destroy ( { force : false } ) ;
679
+ clock . tick ( 1 ) ;
680
+ expect ( messageStream . destroy ) . to . have . been . calledOnce ;
681
+ } ) ;
682
+
683
+ it ( 'calls stream.end exactly once when destroy is called multiple times' , ( ) => {
684
+ connection . destroy ( { force : false } ) ;
685
+ connection . destroy ( { force : false } ) ;
686
+ connection . destroy ( { force : false } ) ;
687
+ connection . destroy ( { force : false } ) ;
688
+ clock . tick ( 1 ) ;
689
+ expect ( driverSocket . end ) . to . have . been . calledOnce ;
690
+ } ) ;
691
+ } ) ;
692
+ } ) ;
494
693
} ) ;
0 commit comments