@@ -27,6 +27,7 @@ import * as mock from '../../tools/mongodb-mock/index';
27
27
import { TestBuilder , UnifiedTestSuiteBuilder } from '../../tools/unified_suite_builder' ;
28
28
import { type FailCommandFailPoint , sleep } from '../../tools/utils' ;
29
29
import { delay , filterForCommands } from '../shared' ;
30
+ import { UUID } from 'bson' ;
30
31
31
32
const initIteratorMode = async ( cs : ChangeStream ) => {
32
33
const initEvent = once ( cs . cursor , 'init' ) ;
@@ -1824,6 +1825,7 @@ describe.only('ChangeStream resumability', function () {
1824
1825
let collection : Collection ;
1825
1826
let changeStream : ChangeStream ;
1826
1827
let aggregateEvents : CommandStartedEvent [ ] = [ ] ;
1828
+ let appName : string ;
1827
1829
1828
1830
const changeStreamResumeOptions : ChangeStreamOptions = {
1829
1831
fullDocument : 'updateLookup' ,
@@ -1882,7 +1884,15 @@ describe.only('ChangeStream resumability', function () {
1882
1884
await utilClient . db ( dbName ) . createCollection ( collectionName ) ;
1883
1885
await utilClient . close ( ) ;
1884
1886
1885
- client = this . configuration . newClient ( { monitorCommands : true } ) ;
1887
+ // we are going to switch primary in tests and cleanup of failpoints is difficult,
1888
+ // so generating unique appname instead of cleaning for each test is an easier solution
1889
+ appName = new UUID ( ) . toString ( ) ;
1890
+
1891
+ client = this . configuration . newClient ( {
1892
+ monitorCommands : true ,
1893
+ serverSelectionTimeoutMS : 5_000 ,
1894
+ appName : appName
1895
+ } ) ;
1886
1896
client . on ( 'commandStarted' , filterForCommands ( [ 'aggregate' ] , aggregateEvents ) ) ;
1887
1897
collection = client . db ( dbName ) . collection ( collectionName ) ;
1888
1898
} ) ;
@@ -2047,61 +2057,42 @@ describe.only('ChangeStream resumability', function () {
2047
2057
} ) ;
2048
2058
} ) ;
2049
2059
2050
- context . only ( 'when the error is not a server error' , function ( ) {
2051
- let client1 : MongoClient ;
2052
- let client2 : MongoClient ;
2053
-
2054
- beforeEach ( async function ( ) {
2055
- client1 = this . configuration . newClient (
2056
- { } ,
2057
- { serverSelectionTimeoutMS : 1000 , appName : 'client-errors' }
2058
- ) ;
2059
- client2 = this . configuration . newClient ( ) ;
2060
-
2061
- collection = client1 . db ( 'client-errors' ) . collection ( 'test' ) ;
2062
- } ) ;
2063
-
2064
- afterEach ( async function ( ) {
2065
- await client2 . db ( 'admin' ) . command ( {
2066
- configureFailPoint : 'failCommand' ,
2067
- mode : 'off' ,
2068
- data : { appName : 'client-errors' }
2069
- } as FailCommandFailPoint ) ;
2070
-
2071
- await client1 ?. close ( ) ;
2072
- await client2 ?. close ( ) ;
2073
- } ) ;
2074
-
2060
+ context ( 'when the error is not a server error' , function ( ) {
2061
+ // This test requires a replica set to call replSetFreeze command
2075
2062
it (
2076
2063
'should resume on ServerSelectionError' ,
2077
- { requires : { topology : '!single' } } ,
2064
+ { requires : { topology : [ 'replicaset' ] } } ,
2078
2065
async function ( ) {
2079
2066
changeStream = collection . watch ( [ ] ) ;
2080
2067
await initIteratorMode ( changeStream ) ;
2081
2068
2082
2069
await collection . insertOne ( { a : 1 } ) ;
2083
2070
2084
- await client2 . db ( 'admin' ) . command ( {
2071
+ // mimic the node termination by closing the connection and failing on heartbeat
2072
+ await client . db ( 'admin' ) . command ( {
2085
2073
configureFailPoint : 'failCommand' ,
2086
2074
mode : 'alwaysOn' ,
2087
2075
data : {
2088
2076
failCommands : [ 'ping' , 'hello' , LEGACY_HELLO_COMMAND ] ,
2089
2077
closeConnection : true ,
2090
2078
handshakeCommands : true ,
2091
2079
failInternalCommands : true ,
2092
- appName : 'client-errors'
2080
+ appName : appName
2093
2081
}
2094
2082
} as FailCommandFailPoint ) ;
2095
- await client2
2096
- . db ( 'admin' )
2097
- . command ( { replSetFreeze : 0 } , { readPreference : ReadPreference . secondary } ) ;
2098
- await client2
2083
+ // force new election in the cluster
2084
+ await client
2099
2085
. db ( 'admin' )
2100
- . command ( { replSetStepDown : 15 , secondaryCatchUpPeriodSecs : 10 , force : true } ) ;
2101
- // await sleep(15_000);
2086
+ . command ( { replSetFreeze : 0 } , { readPreference : ReadPreference . SECONDARY } ) ;
2087
+ await client . db ( 'admin' ) . command ( { replSetStepDown : 30 , force : true } ) ;
2088
+ await sleep ( 1500 ) ;
2102
2089
2103
2090
const change = await changeStream . next ( ) ;
2104
2091
expect ( change ) . to . containSubset ( { operationType : 'insert' , fullDocument : { a : 1 } } ) ;
2092
+
2093
+ expect ( aggregateEvents ) . to . have . lengthOf ( 2 ) ;
2094
+ const [ e1 , e2 ] = aggregateEvents ;
2095
+ expect ( e1 . address ) . to . not . equal ( e2 . address ) ;
2105
2096
}
2106
2097
) ;
2107
2098
} ) ;
@@ -2418,6 +2409,46 @@ describe.only('ChangeStream resumability', function () {
2418
2409
expect ( changeStream . closed ) . to . be . true ;
2419
2410
} ) ;
2420
2411
} ) ;
2412
+
2413
+ context ( 'when the error is not a server error' , function ( ) {
2414
+ // This test requires a replica set to call replSetFreeze command
2415
+ it (
2416
+ 'should resume on ServerSelectionError' ,
2417
+ { requires : { topology : [ 'replicaset' ] } } ,
2418
+ async function ( ) {
2419
+ changeStream = collection . watch ( [ ] ) ;
2420
+ await initIteratorMode ( changeStream ) ;
2421
+
2422
+ await collection . insertOne ( { a : 1 } ) ;
2423
+
2424
+ // mimic the node termination by closing the connection and failing on heartbeat
2425
+ await client . db ( 'admin' ) . command ( {
2426
+ configureFailPoint : 'failCommand' ,
2427
+ mode : 'alwaysOn' ,
2428
+ data : {
2429
+ failCommands : [ 'ping' , 'hello' , LEGACY_HELLO_COMMAND ] ,
2430
+ closeConnection : true ,
2431
+ handshakeCommands : true ,
2432
+ failInternalCommands : true ,
2433
+ appName : appName
2434
+ }
2435
+ } as FailCommandFailPoint ) ;
2436
+ // force new election in the cluster
2437
+ await client
2438
+ . db ( 'admin' )
2439
+ . command ( { replSetFreeze : 0 } , { readPreference : ReadPreference . SECONDARY } ) ;
2440
+ await client . db ( 'admin' ) . command ( { replSetStepDown : 30 , force : true } ) ;
2441
+ await sleep ( 1500 ) ;
2442
+
2443
+ const change = await changeStream . tryNext ( ) ;
2444
+ expect ( change ) . to . containSubset ( { operationType : 'insert' , fullDocument : { a : 1 } } ) ;
2445
+
2446
+ expect ( aggregateEvents ) . to . have . lengthOf ( 2 ) ;
2447
+ const [ e1 , e2 ] = aggregateEvents ;
2448
+ expect ( e1 . address ) . to . not . equal ( e2 . address ) ;
2449
+ }
2450
+ ) ;
2451
+ } ) ;
2421
2452
} ) ;
2422
2453
2423
2454
context ( '#asyncIterator' , function ( ) {
@@ -2554,6 +2585,50 @@ describe.only('ChangeStream resumability', function () {
2554
2585
}
2555
2586
} ) ;
2556
2587
} ) ;
2588
+
2589
+ context ( 'when the error is not a server error' , function ( ) {
2590
+ // This test requires a replica set to call replSetFreeze command
2591
+ it (
2592
+ 'should resume on ServerSelectionError' ,
2593
+ { requires : { topology : [ 'replicaset' ] } } ,
2594
+ async function ( ) {
2595
+ changeStream = collection . watch ( [ ] ) ;
2596
+ await initIteratorMode ( changeStream ) ;
2597
+ const changeStreamIterator = changeStream [ Symbol . asyncIterator ] ( ) ;
2598
+
2599
+ await collection . insertOne ( { a : 1 } ) ;
2600
+
2601
+ // mimic the node termination by closing the connection and failing on heartbeat
2602
+ await client . db ( 'admin' ) . command ( {
2603
+ configureFailPoint : 'failCommand' ,
2604
+ mode : 'alwaysOn' ,
2605
+ data : {
2606
+ failCommands : [ 'ping' , 'hello' , LEGACY_HELLO_COMMAND ] ,
2607
+ closeConnection : true ,
2608
+ handshakeCommands : true ,
2609
+ failInternalCommands : true ,
2610
+ appName : appName
2611
+ }
2612
+ } as FailCommandFailPoint ) ;
2613
+ // force new election in the cluster
2614
+ await client
2615
+ . db ( 'admin' )
2616
+ . command ( { replSetFreeze : 0 } , { readPreference : ReadPreference . SECONDARY } ) ;
2617
+ await client . db ( 'admin' ) . command ( { replSetStepDown : 30 , force : true } ) ;
2618
+ await sleep ( 1500 ) ;
2619
+
2620
+ const change = await changeStreamIterator . next ( ) ;
2621
+ expect ( change . value ) . to . containSubset ( {
2622
+ operationType : 'insert' ,
2623
+ fullDocument : { a : 1 }
2624
+ } ) ;
2625
+
2626
+ expect ( aggregateEvents ) . to . have . lengthOf ( 2 ) ;
2627
+ const [ e1 , e2 ] = aggregateEvents ;
2628
+ expect ( e1 . address ) . to . not . equal ( e2 . address ) ;
2629
+ }
2630
+ ) ;
2631
+ } ) ;
2557
2632
} ) ;
2558
2633
} ) ;
2559
2634
0 commit comments