Skip to content

Commit 276f2be

Browse files
author
Chris Miller
committed
[STREAM-151] 100% test coverage
1 parent e1d8c5b commit 276f2be

3 files changed

Lines changed: 204 additions & 4 deletions

File tree

jest.config.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,10 @@ module.exports = {
3434
coverageDirectory: './coverage',
3535
coverageThreshold: {
3636
global: {
37-
branches: 95,
38-
functions: 95,
39-
lines: 95,
40-
statements: 95
37+
branches: 100,
38+
functions: 100,
39+
lines: 100,
40+
statements: 100
4141
}
4242
},
4343
reporters: [

test/unit/sessions/softphone-session-handler.test.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,34 @@ describe('handleSessionInit()', () => {
269269
expect(acceptSessionSpy).toHaveBeenCalled();
270270
expect(oldSession.sessionReplacedByReinvite).toBeTruthy();
271271
});
272+
273+
it('should create a StatsAggregator when reportStatistics is enabled', async () => {
274+
const superInit = jest.spyOn(BaseSessionHandler.prototype, 'handleSessionInit');
275+
const acceptSessionSpy = jest.spyOn(handler, 'acceptSession').mockImplementation();
276+
mockSdk._config.autoConnectSessions = true;
277+
mockSdk._config.reportStatistics = true;
278+
279+
const session: any = new MockSession();
280+
await handler.handleSessionInit(session);
281+
282+
expect(superInit).toHaveBeenCalled();
283+
expect(session.statsAggregator).toBeDefined();
284+
expect(acceptSessionSpy).toHaveBeenCalled();
285+
});
286+
287+
it('should not create a StatsAggregator when reportStatistics is not enabled', async () => {
288+
const superInit = jest.spyOn(BaseSessionHandler.prototype, 'handleSessionInit');
289+
const acceptSessionSpy = jest.spyOn(handler, 'acceptSession').mockImplementation();
290+
mockSdk._config.autoConnectSessions = true;
291+
mockSdk._config.reportStatistics = false;
292+
293+
const session: any = new MockSession();
294+
await handler.handleSessionInit(session);
295+
296+
expect(superInit).toHaveBeenCalled();
297+
expect(session.statsAggregator).toBeUndefined();
298+
expect(acceptSessionSpy).toHaveBeenCalled();
299+
});
272300
});
273301

274302
describe('acceptSession()', () => {
@@ -2791,4 +2819,29 @@ describe('isConversationHeld()', () => {
27912819
expect(handler.boundPersistentConnectionEventHandler).not.toEqual(old);
27922820
});
27932821
});
2822+
2823+
describe('enableHandler', () => {
2824+
it('should listen for persistent connection events when not a guest', async () => {
2825+
(mockSdk as any).isGuest = false;
2826+
const subscribeSpy = mockSdk._streamingConnection.notifications.subscribe;
2827+
2828+
await handler.enableHandler();
2829+
2830+
expect(subscribeSpy).toHaveBeenCalledWith(
2831+
expect.stringContaining('persistentconnection'),
2832+
expect.any(Function),
2833+
true
2834+
);
2835+
expect(handler.boundPersistentConnectionEventHandler).toBeDefined();
2836+
});
2837+
2838+
it('should not listen for persistent connection events when a guest', async () => {
2839+
(mockSdk as any).isGuest = true;
2840+
const subscribeSpy = mockSdk._streamingConnection.notifications.subscribe;
2841+
2842+
await handler.enableHandler();
2843+
2844+
expect(subscribeSpy).not.toHaveBeenCalled();
2845+
});
2846+
});
27942847
});

test/unit/stats-aggregator.test.ts

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)