@webex/plugin-meetings 3.4.0 → 3.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/dist/breakouts/breakout.js +1 -1
  2. package/dist/breakouts/index.js +1 -1
  3. package/dist/interpretation/index.js +1 -1
  4. package/dist/interpretation/siLanguage.js +1 -1
  5. package/dist/media/index.js +6 -9
  6. package/dist/media/index.js.map +1 -1
  7. package/dist/meeting/index.js +122 -49
  8. package/dist/meeting/index.js.map +1 -1
  9. package/dist/meeting/util.js +1 -0
  10. package/dist/meeting/util.js.map +1 -1
  11. package/dist/meetings/index.js +4 -2
  12. package/dist/meetings/index.js.map +1 -1
  13. package/dist/reachability/index.js +175 -103
  14. package/dist/reachability/index.js.map +1 -1
  15. package/dist/reconnection-manager/index.js +1 -1
  16. package/dist/reconnection-manager/index.js.map +1 -1
  17. package/dist/rtcMetrics/index.js +26 -6
  18. package/dist/rtcMetrics/index.js.map +1 -1
  19. package/dist/types/meeting/index.d.ts +11 -2
  20. package/dist/types/meetings/index.d.ts +2 -1
  21. package/dist/types/reachability/index.d.ts +14 -2
  22. package/dist/types/rtcMetrics/index.d.ts +11 -1
  23. package/dist/webinar/index.js +1 -1
  24. package/package.json +22 -22
  25. package/src/media/index.ts +5 -9
  26. package/src/meeting/index.ts +54 -10
  27. package/src/meeting/util.ts +2 -0
  28. package/src/meetings/index.ts +4 -3
  29. package/src/reachability/index.ts +49 -4
  30. package/src/reconnection-manager/index.ts +1 -1
  31. package/src/rtcMetrics/index.ts +25 -5
  32. package/test/integration/spec/converged-space-meetings.js +1 -1
  33. package/test/unit/spec/breakouts/index.ts +1 -0
  34. package/test/unit/spec/interceptors/locusRetry.ts +11 -10
  35. package/test/unit/spec/media/MediaConnectionAwaiter.ts +1 -0
  36. package/test/unit/spec/media/index.ts +34 -7
  37. package/test/unit/spec/media/properties.ts +1 -1
  38. package/test/unit/spec/meeting/connectionStateHandler.ts +1 -0
  39. package/test/unit/spec/meeting/index.js +77 -6
  40. package/test/unit/spec/meeting/locusMediaRequest.ts +3 -2
  41. package/test/unit/spec/meeting/request.js +1 -0
  42. package/test/unit/spec/meeting/utils.js +4 -0
  43. package/test/unit/spec/meeting-info/meetinginfov2.js +10 -11
  44. package/test/unit/spec/meeting-info/request.js +1 -1
  45. package/test/unit/spec/meetings/index.js +30 -3
  46. package/test/unit/spec/members/request.js +2 -1
  47. package/test/unit/spec/multistream/mediaRequestManager.ts +1 -0
  48. package/test/unit/spec/multistream/receiveSlot.ts +1 -0
  49. package/test/unit/spec/multistream/receiveSlotManager.ts +1 -0
  50. package/test/unit/spec/multistream/remoteMedia.ts +1 -0
  51. package/test/unit/spec/multistream/remoteMediaGroup.ts +1 -0
  52. package/test/unit/spec/multistream/remoteMediaManager.ts +1 -0
  53. package/test/unit/spec/multistream/sendSlotManager.ts +1 -0
  54. package/test/unit/spec/personal-meeting-room/personal-meeting-room.js +0 -1
  55. package/test/unit/spec/reachability/index.ts +211 -13
  56. package/test/unit/spec/reachability/request.js +1 -0
  57. package/test/unit/spec/roap/request.ts +1 -0
  58. package/test/unit/spec/rtcMetrics/index.ts +31 -0
  59. package/src/networkQualityMonitor/index.ts +0 -211
  60. package/test/unit/spec/networkQualityMonitor/index.js +0 -99
@@ -79,6 +79,7 @@ describe('plugin-meetings', () => {
79
79
  let locusInfo;
80
80
  let services;
81
81
  let catalog;
82
+ let startReachabilityStub;
82
83
 
83
84
  describe('meetings index', () => {
84
85
  beforeEach(() => {
@@ -129,9 +130,7 @@ describe('plugin-meetings', () => {
129
130
  logger,
130
131
  });
131
132
 
132
- Object.assign(webex.meetings, {
133
- startReachability: sinon.stub().returns(Promise.resolve()),
134
- });
133
+ startReachabilityStub = sinon.stub(webex.meetings, 'startReachability').resolves();
135
134
 
136
135
  Object.assign(webex.internal, {
137
136
  llm: {on: sinon.stub()},
@@ -197,6 +196,34 @@ describe('plugin-meetings', () => {
197
196
  assert.calledOnce(MeetingsUtil.checkH264Support);
198
197
  });
199
198
 
199
+ describe('#startReachability', () => {
200
+ let gatherReachabilitySpy;
201
+ let fakeResult = {id: 'fake-result'};
202
+
203
+ beforeEach(() => {
204
+ startReachabilityStub.restore();
205
+ gatherReachabilitySpy = sinon
206
+ .stub(webex.meetings.getReachability(), 'gatherReachability')
207
+ .resolves(fakeResult);
208
+ });
209
+
210
+ it('should gather reachability with default trigger value', async () => {
211
+ const result = await webex.meetings.startReachability();
212
+
213
+ assert.calledOnceWithExactly(gatherReachabilitySpy, 'client');
214
+ assert.equal(result, fakeResult);
215
+ });
216
+
217
+ it('should gather reachability and pass custom trigger value', async () => {
218
+ const trigger = 'custom-trigger';
219
+
220
+ const result = await webex.meetings.startReachability(trigger);
221
+
222
+ assert.calledOnceWithExactly(gatherReachabilitySpy, trigger);
223
+ assert.equal(result, fakeResult);
224
+ });
225
+ });
226
+
200
227
  describe('#_toggleUnifiedMeetings', () => {
201
228
  it('should have toggleUnifiedMeetings', () => {
202
229
  assert.equal(typeof webex.meetings._toggleUnifiedMeetings, 'function');
@@ -1,3 +1,4 @@
1
+ import 'jsdom-global/register';
1
2
  import sinon from 'sinon';
2
3
  import chai from 'chai';
3
4
  import uuid from 'uuid';
@@ -131,7 +132,7 @@ describe('plugin-meetings', () => {
131
132
  locusUrl: url1,
132
133
  memberIds: ['1', '2'],
133
134
  };
134
-
135
+
135
136
  await membersRequest.admitMember(options)
136
137
 
137
138
  checkRequest({
@@ -1,3 +1,4 @@
1
+ import 'jsdom-global/register';
1
2
  import {MediaRequestManager} from '@webex/plugin-meetings/src/multistream/mediaRequestManager';
2
3
  import {ReceiveSlot} from '@webex/plugin-meetings/src/multistream/receiveSlot';
3
4
  import sinon from 'sinon';
@@ -1,4 +1,5 @@
1
1
  /* eslint-disable require-jsdoc */
2
+ import 'jsdom-global/register';
2
3
  import EventEmitter from 'events';
3
4
 
4
5
  import {MediaType, ReceiveSlotEvents as WcmeReceiveSlotEvents} from '@webex/internal-media-core';
@@ -1,3 +1,4 @@
1
+ import 'jsdom-global/register';
1
2
  import sinon from 'sinon';
2
3
  import {assert} from '@webex/test-helper-chai';
3
4
  import {MediaType} from '@webex/internal-media-core';
@@ -1,4 +1,5 @@
1
1
  /* eslint-disable require-jsdoc */
2
+ import 'jsdom-global/register';
2
3
  import EventEmitter from 'events';
3
4
 
4
5
  import {MediaType} from '@webex/internal-media-core';
@@ -1,3 +1,4 @@
1
+ import 'jsdom-global/register';
1
2
  import EventEmitter from 'events';
2
3
 
3
4
  import {MediaType} from '@webex/internal-media-core';
@@ -1,4 +1,5 @@
1
1
  /* eslint-disable require-jsdoc */
2
+ import 'jsdom-global/register';
2
3
  import EventEmitter from 'events';
3
4
 
4
5
  import {MediaType} from '@webex/internal-media-core';
@@ -1,3 +1,4 @@
1
+ import 'jsdom-global/register';
1
2
  import SendSlotManager from '@webex/plugin-meetings/src/multistream/sendSlotManager';
2
3
  import { LocalStream, MediaType, MultistreamRoapMediaConnection } from "@webex/internal-media-core";
3
4
  import {expect} from '@webex/test-helper-chai';
@@ -1,7 +1,6 @@
1
1
  /*!
2
2
  * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.
3
3
  */
4
-
5
4
  import 'jsdom-global/register';
6
5
  import {assert} from '@webex/test-helper-chai';
7
6
  import sinon from 'sinon';
@@ -4,10 +4,9 @@ import sinon from 'sinon';
4
4
  import EventEmitter from 'events';
5
5
  import testUtils from '../../../utils/testUtils';
6
6
  import Reachability, {
7
- ReachabilityResults,
8
7
  ReachabilityResultsForBackend,
9
8
  } from '@webex/plugin-meetings/src/reachability/';
10
- import { ClusterNode } from '../../../../src/reachability/request';
9
+ import {ClusterNode} from '../../../../src/reachability/request';
11
10
  import MeetingUtil from '@webex/plugin-meetings/src/meeting/util';
12
11
  import * as ClusterReachabilityModule from '@webex/plugin-meetings/src/reachability/clusterReachability';
13
12
  import Metrics from '@webex/plugin-meetings/src/metrics';
@@ -145,7 +144,6 @@ describe('isAnyPublicClusterReachable', () => {
145
144
  });
146
145
  });
147
146
 
148
-
149
147
  describe('isWebexMediaBackendUnreachable', () => {
150
148
  let webex;
151
149
 
@@ -486,6 +484,16 @@ describe('gatherReachability', () => {
486
484
  JSON.stringify({old: 'joinCookie'})
487
485
  );
488
486
 
487
+ webex.internal.device.ipNetworkDetector = {
488
+ supportsIpV4: false,
489
+ supportsIpV6: false,
490
+ firstIpV4: -1,
491
+ firstIpV6: -1,
492
+ firstMdns: -1,
493
+ totalTime: -1,
494
+ detect: sinon.stub().resolves(),
495
+ };
496
+
489
497
  clock = sinon.useFakeTimers();
490
498
 
491
499
  mockClusterReachabilityInstances = {};
@@ -498,6 +506,11 @@ describe('gatherReachability', () => {
498
506
  mockClusterReachabilityInstances[id] = mockInstance;
499
507
  return mockInstance;
500
508
  });
509
+
510
+ webex.config.meetings.experimental = {
511
+ enableTcpReachability: false,
512
+ enableTlsReachability: false,
513
+ };
501
514
  });
502
515
 
503
516
  afterEach(() => {
@@ -1035,6 +1048,16 @@ describe('gatherReachability', () => {
1035
1048
  enableTlsReachability: true,
1036
1049
  };
1037
1050
 
1051
+ // the metrics related to ipver and trigger are not tested in these tests and are all the same, so setting them up here
1052
+ const expectedMetricsFull = {
1053
+ ...expectedMetrics,
1054
+ ipver_firstIpV4: -1,
1055
+ ipver_firstIpV6: -1,
1056
+ ipver_firstMdns: -1,
1057
+ ipver_totalTime: -1,
1058
+ trigger: 'test',
1059
+ };
1060
+
1038
1061
  const receivedEvents = {
1039
1062
  done: 0,
1040
1063
  firstResultAvailable: {
@@ -1064,7 +1087,7 @@ describe('gatherReachability', () => {
1064
1087
 
1065
1088
  reachability.reachabilityRequest.getClusters = sinon.stub().returns(mockGetClustersResult);
1066
1089
 
1067
- const resultPromise = reachability.gatherReachability();
1090
+ const resultPromise = reachability.gatherReachability('test');
1068
1091
 
1069
1092
  await testUtils.flushPromises();
1070
1093
 
@@ -1119,11 +1142,122 @@ describe('gatherReachability', () => {
1119
1142
  assert.calledWith(
1120
1143
  Metrics.sendBehavioralMetric,
1121
1144
  'js_sdk_reachability_completed',
1122
- expectedMetrics
1145
+ expectedMetricsFull
1123
1146
  );
1124
1147
  })
1125
1148
  );
1126
1149
 
1150
+ it('sends the trigger parameter in the metrics', async () => {
1151
+ const reachability = new TestReachability(webex);
1152
+
1153
+ const mockGetClustersResult = {
1154
+ clusters: {
1155
+ clusterA: {
1156
+ udp: ['udp-url'],
1157
+ tcp: [],
1158
+ xtls: [],
1159
+ isVideoMesh: false,
1160
+ },
1161
+ },
1162
+ joinCookie: {id: 'id'},
1163
+ };
1164
+
1165
+ reachability.reachabilityRequest.getClusters = sinon.stub().returns(mockGetClustersResult);
1166
+
1167
+ const resultPromise = reachability.gatherReachability('some trigger');
1168
+
1169
+ // let it time out
1170
+ await testUtils.flushPromises();
1171
+ clock.tick(15000);
1172
+ await resultPromise;
1173
+
1174
+ // check the metric contains the right trigger value
1175
+ assert.calledWith(
1176
+ Metrics.sendBehavioralMetric,
1177
+ 'js_sdk_reachability_completed',
1178
+ sinon.match({trigger: 'some trigger'})
1179
+ );
1180
+ });
1181
+
1182
+ it(`starts ip network version detection and includes the results in the metrics`, async () => {
1183
+ webex.config.meetings.experimental = {
1184
+ enableTcpReachability: true,
1185
+ enableTlsReachability: true,
1186
+ };
1187
+ webex.internal.device.ipNetworkDetector = {
1188
+ supportsIpV4: true,
1189
+ supportsIpV6: true,
1190
+ firstIpV4: 10,
1191
+ firstIpV6: 20,
1192
+ firstMdns: 30,
1193
+ totalTime: 40,
1194
+ detect: sinon.stub().resolves(),
1195
+ };
1196
+
1197
+ const receivedEvents = {
1198
+ done: 0,
1199
+ };
1200
+
1201
+ const reachability = new Reachability(webex);
1202
+
1203
+ reachability.on('reachability:done', () => {
1204
+ receivedEvents.done += 1;
1205
+ });
1206
+
1207
+ // simulate having just 1 cluster, we don't need more for this test
1208
+ reachability.reachabilityRequest.getClusters = sinon.stub().returns({
1209
+ clusters: {
1210
+ publicCluster: {
1211
+ udp: ['udp-url'],
1212
+ tcp: [],
1213
+ xtls: [],
1214
+ isVideoMesh: false,
1215
+ },
1216
+ },
1217
+ joinCookie: {id: 'id'},
1218
+ });
1219
+
1220
+ const resultPromise = reachability.gatherReachability('test');
1221
+
1222
+ await testUtils.flushPromises();
1223
+
1224
+ // trigger mock result events from ClusterReachability instance
1225
+ mockClusterReachabilityInstances['publicCluster'].emitFakeResult('udp', {
1226
+ result: 'reachable',
1227
+ clientMediaIPs: ['1.2.3.4'],
1228
+ latencyInMilliseconds: 100,
1229
+ });
1230
+
1231
+ await resultPromise;
1232
+
1233
+ // check events emitted by Reachability class
1234
+ assert.equal(receivedEvents['done'], 1);
1235
+
1236
+ // and that ip network detection was started
1237
+ assert.calledOnceWithExactly(webex.internal.device.ipNetworkDetector.detect);
1238
+
1239
+ // finally, check the metrics - they should contain values from ipNetworkDetector
1240
+ assert.calledWith(Metrics.sendBehavioralMetric, 'js_sdk_reachability_completed', {
1241
+ vmn_udp_min: -1,
1242
+ vmn_udp_max: -1,
1243
+ vmn_udp_average: -1,
1244
+ public_udp_min: 100,
1245
+ public_udp_max: 100,
1246
+ public_udp_average: 100,
1247
+ public_tcp_min: -1,
1248
+ public_tcp_max: -1,
1249
+ public_tcp_average: -1,
1250
+ public_xtls_min: -1,
1251
+ public_xtls_max: -1,
1252
+ public_xtls_average: -1,
1253
+ ipver_firstIpV4: webex.internal.device.ipNetworkDetector.firstIpV4,
1254
+ ipver_firstIpV6: webex.internal.device.ipNetworkDetector.firstIpV6,
1255
+ ipver_firstMdns: webex.internal.device.ipNetworkDetector.firstMdns,
1256
+ ipver_totalTime: webex.internal.device.ipNetworkDetector.totalTime,
1257
+ trigger: 'test',
1258
+ });
1259
+ });
1260
+
1127
1261
  it('keeps updating reachability results after the 3s public cloud timeout expires', async () => {
1128
1262
  webex.config.meetings.experimental = {
1129
1263
  enableTcpReachability: true,
@@ -1152,7 +1286,7 @@ describe('gatherReachability', () => {
1152
1286
 
1153
1287
  reachability.reachabilityRequest.getClusters = sinon.stub().returns(mockGetClustersResult);
1154
1288
 
1155
- const resultPromise = reachability.gatherReachability();
1289
+ const resultPromise = reachability.gatherReachability('test');
1156
1290
 
1157
1291
  await testUtils.flushPromises();
1158
1292
 
@@ -1245,7 +1379,7 @@ describe('gatherReachability', () => {
1245
1379
 
1246
1380
  reachability.reachabilityRequest.getClusters = sinon.stub().returns(mockGetClustersResult);
1247
1381
 
1248
- const resultPromise = reachability.gatherReachability();
1382
+ const resultPromise = reachability.gatherReachability('test');
1249
1383
 
1250
1384
  await testUtils.flushPromises();
1251
1385
 
@@ -1286,7 +1420,7 @@ describe('gatherReachability', () => {
1286
1420
 
1287
1421
  reachability.reachabilityRequest.getClusters = sinon.stub().throws();
1288
1422
 
1289
- const result = await reachability.gatherReachability();
1423
+ const result = await reachability.gatherReachability('test');
1290
1424
 
1291
1425
  assert.empty(result);
1292
1426
 
@@ -1304,7 +1438,7 @@ describe('gatherReachability', () => {
1304
1438
  reachability.reachabilityRequest.getClusters = sinon.stub().returns(getClustersResult);
1305
1439
  (reachability as any).performReachabilityChecks = sinon.stub().throws();
1306
1440
 
1307
- const result = await reachability.gatherReachability();
1441
+ const result = await reachability.gatherReachability('test');
1308
1442
 
1309
1443
  assert.empty(result);
1310
1444
 
@@ -1339,7 +1473,7 @@ describe('gatherReachability', () => {
1339
1473
 
1340
1474
  reachability.reachabilityRequest.getClusters = sinon.stub().returns(getClustersResult);
1341
1475
 
1342
- const promise = reachability.gatherReachability();
1476
+ const promise = reachability.gatherReachability('test');
1343
1477
 
1344
1478
  await simulateTimeout();
1345
1479
  await promise;
@@ -1385,7 +1519,7 @@ describe('gatherReachability', () => {
1385
1519
 
1386
1520
  reachability.reachabilityRequest.getClusters = sinon.stub().returns(getClustersResult);
1387
1521
 
1388
- const promise = reachability.gatherReachability();
1522
+ const promise = reachability.gatherReachability('test');
1389
1523
  await simulateTimeout();
1390
1524
  await promise;
1391
1525
 
@@ -1419,7 +1553,7 @@ describe('gatherReachability', () => {
1419
1553
 
1420
1554
  reachability.reachabilityRequest.getClusters = sinon.stub().returns(getClustersResult);
1421
1555
 
1422
- const promise = reachability.gatherReachability();
1556
+ const promise = reachability.gatherReachability('test');
1423
1557
 
1424
1558
  await simulateTimeout();
1425
1559
  await promise;
@@ -1454,7 +1588,7 @@ describe('gatherReachability', () => {
1454
1588
 
1455
1589
  reachability.reachabilityRequest.getClusters = sinon.stub().returns(getClustersResult);
1456
1590
 
1457
- const promise = reachability.gatherReachability();
1591
+ const promise = reachability.gatherReachability('test');
1458
1592
 
1459
1593
  await simulateTimeout();
1460
1594
  await promise;
@@ -1466,6 +1600,70 @@ describe('gatherReachability', () => {
1466
1600
  xtls: [], // empty list because TLS is disabled in config
1467
1601
  });
1468
1602
  });
1603
+
1604
+ it('retry of getClusters is succesfull', async () => {
1605
+ webex.config.meetings.experimental = {
1606
+ enableTcpReachability: true,
1607
+ enableTlsReachability: false,
1608
+ };
1609
+
1610
+ const getClustersResult = {
1611
+ clusters: {
1612
+ 'cluster name': {
1613
+ udp: ['testUDP1', 'testUDP2'],
1614
+ tcp: ['testTCP1', 'testTCP2'],
1615
+ xtls: ['testXTLS1', 'testXTLS2'],
1616
+ isVideoMesh: false,
1617
+ },
1618
+ },
1619
+ joinCookie: {id: 'id'},
1620
+ };
1621
+
1622
+ const reachability = new Reachability(webex);
1623
+
1624
+ let getClustersCallCount = 0;
1625
+
1626
+ reachability.reachabilityRequest.getClusters = sinon.stub().callsFake(() => {
1627
+ getClustersCallCount++;
1628
+
1629
+ if (getClustersCallCount == 1) {
1630
+ throw new Error('fake error');
1631
+ }
1632
+
1633
+ return getClustersResult;
1634
+ });
1635
+
1636
+ const promise = reachability.gatherReachability('test');
1637
+
1638
+ await simulateTimeout();
1639
+ await promise;
1640
+
1641
+ assert.equal(getClustersCallCount, 2);
1642
+
1643
+ assert.calledOnce(clusterReachabilityCtorStub);
1644
+ });
1645
+
1646
+ it('two failed calls to getClusters', async () => {
1647
+ const reachability = new Reachability(webex);
1648
+
1649
+ let getClustersCallCount = 0;
1650
+
1651
+ reachability.reachabilityRequest.getClusters = sinon.stub().callsFake(() => {
1652
+ getClustersCallCount++;
1653
+
1654
+ throw new Error('fake error');
1655
+ });
1656
+
1657
+ const promise = reachability.gatherReachability('test');
1658
+
1659
+ await simulateTimeout();
1660
+
1661
+ await promise;
1662
+
1663
+ assert.equal(getClustersCallCount, 2);
1664
+
1665
+ assert.neverCalledWith(clusterReachabilityCtorStub);
1666
+ });
1469
1667
  });
1470
1668
 
1471
1669
  describe('getReachabilityResults', () => {
@@ -1,3 +1,4 @@
1
+ import 'jsdom-global/register';
1
2
  import sinon from 'sinon';
2
3
  import {assert} from '@webex/test-helper-chai';
3
4
  import MockWebex from '@webex/test-helper-mock-webex';
@@ -1,3 +1,4 @@
1
+ import 'jsdom-global/register';
1
2
  import sinon from 'sinon';
2
3
  import {assert} from '@webex/test-helper-chai';
3
4
  import MockWebex from '@webex/test-helper-mock-webex';
@@ -120,4 +120,35 @@ describe('RtcMetrics', () => {
120
120
  metrics.addMetrics({ name: 'stats-report', payload: [STATS_WITH_IP] });
121
121
  assert.calledOnce(anonymizeIpSpy);
122
122
  })
123
+
124
+ it('should send metrics on first stats-report', () => {
125
+ assert.callCount(webex.request, 0);
126
+
127
+ metrics.addMetrics(FAKE_METRICS_ITEM);
128
+ assert.callCount(webex.request, 0);
129
+
130
+ // first stats-report should trigger a call to webex.request
131
+ metrics.addMetrics({ name: 'stats-report', payload: [STATS_WITH_IP] });
132
+ assert.callCount(webex.request, 1);
133
+ });
134
+
135
+ it('should send metrics on first stats-report after a new connection', () => {
136
+ assert.callCount(webex.request, 0);
137
+
138
+ // first stats-report should trigger a call to webex.request
139
+ metrics.addMetrics({ name: 'stats-report', payload: [STATS_WITH_IP] });
140
+ assert.callCount(webex.request, 1);
141
+
142
+ // subsequent stats-report doesn't trigger it
143
+ metrics.addMetrics({ name: 'stats-report', payload: [STATS_WITH_IP] });
144
+ assert.callCount(webex.request, 1);
145
+
146
+ // now, simulate a failure - that triggers a new connection and upload of the metrics
147
+ metrics.addMetrics(FAILURE_METRICS_ITEM);
148
+ assert.callCount(webex.request, 2);
149
+
150
+ // and another stats-report should trigger another upload of the metrics
151
+ metrics.addMetrics({ name: 'stats-report', payload: [STATS_WITH_IP] });
152
+ assert.callCount(webex.request, 3);
153
+ });
123
154
  });