1+ import { setTimeout } from 'node:timers/promises' ;
2+
3+ import { expect } from 'chai' ;
4+ import * as sinon from 'sinon' ;
5+
6+ import {
7+ Connection ,
8+ type MongoClient ,
9+ promiseWithResolvers ,
10+ type ServerHeartbeatSucceededEvent
11+ } from '../../mongodb' ;
112import { loadSpecTests } from '../../spec' ;
213import { runUnifiedSuite } from '../../tools/unified-spec-runner/runner' ;
314
@@ -8,3 +19,105 @@ describe('SDAM Unified Tests (Node Driver)', function () {
819 ) ;
920 runUnifiedSuite ( clonedAndAlteredSpecTests ) ;
1021} ) ;
22+
23+ describe ( 'Monitoring rtt tests' , function ( ) {
24+ let client : MongoClient ;
25+ let heartbeatDurations : Record < string , number [ ] > ;
26+ const HEARTBEATS_TO_COLLECT_PER_NODE = 65 ;
27+ const IGNORE_SIZE = 5 ;
28+ const DELAY_MS = 10 ;
29+
30+ beforeEach ( function ( ) {
31+ heartbeatDurations = Object . create ( null ) ;
32+ } ) ;
33+
34+ afterEach ( async function ( ) {
35+ if ( client ) {
36+ await client . close ( ) ;
37+ }
38+ sinon . restore ( ) ;
39+ } ) ;
40+
41+ for ( const serverMonitoringMode of [ 'poll' , 'stream' ] ) {
42+ context ( `when serverMonitoringMode is set to '${ serverMonitoringMode } '` , function ( ) {
43+ context ( 'after collecting a number of heartbeats' , function ( ) {
44+ beforeEach ( async function ( ) {
45+ client = this . configuration . newClient ( {
46+ heartbeatFrequencyMS : 100 ,
47+ serverMonitoringMode
48+ } ) ;
49+
50+ // make sendCommand delay for DELAY_MS ms to ensure that the actual time between sending
51+ // a heartbeat and receiving a response don't drop below 1ms. This is done since our
52+ // testing is colocated with its mongo deployment so network latency is very low
53+ const stub = sinon
54+ // @ts -expect-error accessing private method
55+ . stub ( Connection . prototype , 'sendCommand' )
56+ . callsFake ( async function * ( ...args ) {
57+ await setTimeout ( DELAY_MS ) ;
58+ yield * stub . wrappedMethod . call ( this , ...args ) ;
59+ } ) ;
60+ await client . connect ( ) ;
61+
62+ const { promise, resolve } = promiseWithResolvers < void > ( ) ;
63+ client . on ( 'serverHeartbeatSucceeded' , ( ev : ServerHeartbeatSucceededEvent ) => {
64+ heartbeatDurations [ ev . connectionId ] ??= [ ] ;
65+ if (
66+ heartbeatDurations [ ev . connectionId ] . length <
67+ HEARTBEATS_TO_COLLECT_PER_NODE + IGNORE_SIZE
68+ )
69+ heartbeatDurations [ ev . connectionId ] . push ( ev . duration ) ;
70+
71+ // We ignore the first few heartbeats since the problem reported in NODE-6172 showed that the
72+ // first few heartbeats were recorded properly
73+ if (
74+ Object . keys ( heartbeatDurations ) . length === client . topology . s . servers . size &&
75+ Object . values ( heartbeatDurations ) . every (
76+ d => d . length === HEARTBEATS_TO_COLLECT_PER_NODE + IGNORE_SIZE
77+ )
78+ ) {
79+ client . removeAllListeners ( 'serverHeartbeatSucceeded' ) ;
80+ resolve ( ) ;
81+ }
82+ } ) ;
83+ await promise ;
84+ } ) ;
85+
86+ it (
87+ 'heartbeat duration is not incorrectly reported as zero on ServerHeartbeatSucceededEvents' ,
88+ {
89+ metadata : {
90+ requires : { topology : '!load-balanced' }
91+ } ,
92+ test : async function ( ) {
93+ for ( const durations of Object . values ( heartbeatDurations ) ) {
94+ const relevantDurations = durations . slice ( IGNORE_SIZE ) ;
95+ expect ( relevantDurations ) . to . have . length . gt ( 0 ) ;
96+ const averageDuration =
97+ relevantDurations . reduce ( ( acc , x ) => acc + x ) / relevantDurations . length ;
98+ expect ( averageDuration ) . to . be . gt ( DELAY_MS ) ;
99+ }
100+ }
101+ }
102+ ) ;
103+
104+ it ( 'ServerDescription.roundTripTime is not incorrectly reported as zero' , {
105+ metadata : {
106+ requires : { topology : '!load-balanced' }
107+ } ,
108+ test : async function ( ) {
109+ for ( const [ server , durations ] of Object . entries ( heartbeatDurations ) ) {
110+ const relevantDurations = durations . slice ( IGNORE_SIZE ) ;
111+ expect ( relevantDurations ) . to . have . length . gt ( 0 ) ;
112+ const averageDuration =
113+ relevantDurations . reduce ( ( acc , x ) => acc + x ) / relevantDurations . length ;
114+ const rtt = client . topology . description . servers . get ( server ) . roundTripTime ;
115+ expect ( rtt ) . to . not . equal ( 0 ) ;
116+ expect ( rtt ) . to . be . approximately ( averageDuration , 3 ) ;
117+ }
118+ }
119+ } ) ;
120+ } ) ;
121+ } ) ;
122+ }
123+ } ) ;
0 commit comments