@@ -11,6 +11,99 @@ describe('StatsAggregator', () => {
1111 expect ( statsAggregator ) . toBeTruthy ( ) ;
1212 } ) ;
1313 } ) ;
14+ describe ( 'shouldGatherImmediately / eager persistent connection' , ( ) => {
15+ it ( 'should not start gathering stats immediately for eager persistent connections (privAnswerMode === Auto)' , ( ) => {
16+ const mockSession = new MockSession ( ) as unknown as IExtendedMediaSession ;
17+ ( mockSession as any ) . privAnswerMode = 'Auto' ;
18+ const sdk = new SimpleMockSdk ( ) as unknown as GenesysCloudWebrtSdk ;
19+ const statsAggregator = new StatsAggregator ( mockSession , sdk ) ;
20+
21+ // statsGatherer should NOT be created yet for eager persistent connections
22+ expect ( statsAggregator [ 'statsGatherer' ] ) . toBeFalsy ( ) ;
23+ } ) ;
24+
25+ it ( 'should start gathering stats immediately for non-eager sessions' , ( ) => {
26+ const mockSession = new MockSession ( ) as unknown as IExtendedMediaSession ;
27+ ( mockSession as any ) . privAnswerMode = 'Manual' ;
28+ const sdk = new SimpleMockSdk ( ) as unknown as GenesysCloudWebrtSdk ;
29+ const statsAggregator = new StatsAggregator ( mockSession , sdk ) ;
30+
31+ expect ( statsAggregator [ 'statsGatherer' ] ) . toBeTruthy ( ) ;
32+ } ) ;
33+ } ) ;
34+
35+ describe ( 'onSessionStarted' , ( ) => {
36+ it ( 'should start gathering stats and set baseline when the matching session starts' , ( ) => {
37+ const mockSession = new MockSession ( ) as unknown as IExtendedMediaSession ;
38+ ( mockSession as any ) . privAnswerMode = 'Auto' ;
39+ const sdk = new SimpleMockSdk ( ) as unknown as GenesysCloudWebrtSdk ;
40+ const statsAggregator = new StatsAggregator ( mockSession , sdk ) ;
41+
42+ expect ( statsAggregator [ 'statsGatherer' ] ) . toBeFalsy ( ) ;
43+
44+ // Emit sessionStarted with the same session
45+ ( sdk as any ) . emit ( 'sessionStarted' , mockSession ) ;
46+
47+ expect ( statsAggregator [ 'statsGatherer' ] ) . toBeTruthy ( ) ;
48+ expect ( statsAggregator [ 'setBaseline' ] ) . toBe ( true ) ;
49+ } ) ;
50+
51+ it ( 'should not start gathering stats for a different session' , ( ) => {
52+ const mockSession = new MockSession ( ) as unknown as IExtendedMediaSession ;
53+ ( mockSession as any ) . privAnswerMode = 'Auto' ;
54+ const sdk = new SimpleMockSdk ( ) as unknown as GenesysCloudWebrtSdk ;
55+ const statsAggregator = new StatsAggregator ( mockSession , sdk ) ;
56+
57+ const otherSession = new MockSession ( ) as unknown as IExtendedMediaSession ;
58+ ( sdk as any ) . emit ( 'sessionStarted' , otherSession ) ;
59+
60+ expect ( statsAggregator [ 'statsGatherer' ] ) . toBeFalsy ( ) ;
61+ } ) ;
62+ } ) ;
63+
64+ describe ( 'onSessionEnded' , ( ) => {
65+ it ( 'should stop gathering stats when the matching session ends' , ( ) => {
66+ const mockSession = new MockSession ( ) as unknown as IExtendedMediaSession ;
67+ const sdk = new SimpleMockSdk ( ) as unknown as GenesysCloudWebrtSdk ;
68+ const statsAggregator = new StatsAggregator ( mockSession , sdk ) ;
69+
70+ expect ( statsAggregator [ 'statsGatherer' ] ) . toBeTruthy ( ) ;
71+
72+ ( sdk as any ) . emit ( 'sessionEnded' , mockSession ) ;
73+
74+ expect ( statsAggregator [ 'statsGatherer' ] ) . toBeFalsy ( ) ;
75+ } ) ;
76+
77+ it ( 'should not stop gathering stats for a different session' , ( ) => {
78+ const mockSession = new MockSession ( ) as unknown as IExtendedMediaSession ;
79+ const sdk = new SimpleMockSdk ( ) as unknown as GenesysCloudWebrtSdk ;
80+ const statsAggregator = new StatsAggregator ( mockSession , sdk ) ;
81+
82+ const otherSession = new MockSession ( ) as unknown as IExtendedMediaSession ;
83+ ( sdk as any ) . emit ( 'sessionEnded' , otherSession ) ;
84+
85+ expect ( statsAggregator [ 'statsGatherer' ] ) . toBeTruthy ( ) ;
86+ } ) ;
87+ } ) ;
88+
89+ describe ( 'session terminated event' , ( ) => {
90+ it ( 'should stop gathering stats and remove SDK listeners on session terminated' , ( ) => {
91+ const mockSession = new MockSession ( ) as unknown as IExtendedMediaSession ;
92+ const sdk = new SimpleMockSdk ( ) as unknown as GenesysCloudWebrtSdk ;
93+ const statsAggregator = new StatsAggregator ( mockSession , sdk ) ;
94+
95+ expect ( statsAggregator [ 'statsGatherer' ] ) . toBeTruthy ( ) ;
96+
97+ const listenerCountBefore = ( sdk as any ) . listenerCount ( 'sessionStarted' ) ;
98+
99+ ( mockSession as any ) . emit ( 'terminated' ) ;
100+
101+ expect ( statsAggregator [ 'statsGatherer' ] ) . toBeFalsy ( ) ;
102+ // SDK listeners for sessionStarted/sessionEnded should be removed
103+ expect ( ( sdk as any ) . listenerCount ( 'sessionStarted' ) ) . toBe ( listenerCountBefore - 1 ) ;
104+ } ) ;
105+ } ) ;
106+
14107 describe ( 'handleStatsUpdate' , ( ) => {
15108 it ( 'should only handle a GetStatsEvent' , ( ) => {
16109 const mockSession = new MockSession ( ) as unknown as IExtendedMediaSession ;
@@ -108,6 +201,60 @@ describe('StatsAggregator', () => {
108201 ) ;
109202 } ) ;
110203
204+ it ( 'should not send stats when jitter is undefined' , ( ) => {
205+ const mockSession = new MockSession ( ) as unknown as IExtendedMediaSession ;
206+ const sdk = new SimpleMockSdk ( ) as unknown as GenesysCloudWebrtSdk ;
207+ const statsAggregator = new StatsAggregator ( mockSession , sdk ) ;
208+ statsAggregator [ 'sendStats' ] = jest . fn ( ) ;
209+
210+ // Trigger baseline
211+ statsAggregator [ 'setBaseline' ] = true ;
212+ const baselineEvent = {
213+ name : 'getStats' ,
214+ tracks : [ { totalRoundTripTime : 0 , roundTripTimeMeasurements : 0 } ] ,
215+ remoteTracks : [ { packetsReceived : 0 , packetsLost : 0 , jitter : 0.001 , timestamp : Date . now ( ) } ]
216+ } ;
217+ statsAggregator [ 'handleStatsUpdate' ] ( baselineEvent ) ;
218+
219+ // Event with all fields present except jitter
220+ const event = {
221+ name : 'getStats' ,
222+ tracks : [ { totalRoundTripTime : 0.1 , roundTripTimeMeasurements : 1 } ] ,
223+ remoteTracks : [ { packetsReceived : 10 , packetsLost : 0 , timestamp : Date . now ( ) } ]
224+ } ;
225+
226+ statsAggregator [ 'handleStatsUpdate' ] ( event ) ;
227+
228+ expect ( statsAggregator [ 'sendStats' ] ) . not . toHaveBeenCalled ( ) ;
229+ } ) ;
230+
231+ it ( 'should handle packetLoss calculation when packetsReceived is 0' , ( ) => {
232+ const mockSession = new MockSession ( ) as unknown as IExtendedMediaSession ;
233+ const sdk = new SimpleMockSdk ( ) as unknown as GenesysCloudWebrtSdk ;
234+ const statsAggregator = new StatsAggregator ( mockSession , sdk ) ;
235+ statsAggregator [ 'sendStats' ] = jest . fn ( ) ;
236+
237+ // Trigger baseline with same values so packetsReceived delta is 0
238+ statsAggregator [ 'setBaseline' ] = true ;
239+ const baselineEvent = {
240+ name : 'getStats' ,
241+ tracks : [ { totalRoundTripTime : 0 , roundTripTimeMeasurements : 0 } ] ,
242+ remoteTracks : [ { packetsReceived : 10 , packetsLost : 0 , jitter : 0.001 , timestamp : Date . now ( ) } ]
243+ } ;
244+ statsAggregator [ 'handleStatsUpdate' ] ( baselineEvent ) ;
245+
246+ // Same packetsReceived as baseline → delta is 0
247+ const event = {
248+ name : 'getStats' ,
249+ tracks : [ { totalRoundTripTime : 0.1 , roundTripTimeMeasurements : 1 } ] ,
250+ remoteTracks : [ { packetsReceived : 10 , packetsLost : 0 , jitter : 0.002 , timestamp : Date . now ( ) } ]
251+ } ;
252+
253+ statsAggregator [ 'handleStatsUpdate' ] ( event ) ;
254+
255+ expect ( statsAggregator [ 'sendStats' ] ) . toHaveBeenCalled ( ) ;
256+ } ) ;
257+
111258 it ( 'should send stats when we have everything we want' , ( ) => {
112259 const mockSession = new MockSession ( ) as unknown as IExtendedMediaSession ;
113260 const sdk = new SimpleMockSdk ( ) as unknown as GenesysCloudWebrtSdk ;
0 commit comments