@webex/internal-plugin-metrics 3.0.0-bnr.5 → 3.0.0-next.2

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 (78) hide show
  1. package/.eslintrc.js +6 -0
  2. package/babel.config.js +3 -0
  3. package/dist/batcher.js +41 -3
  4. package/dist/batcher.js.map +1 -1
  5. package/dist/call-diagnostic/call-diagnostic-metrics-batcher.js +64 -0
  6. package/dist/call-diagnostic/call-diagnostic-metrics-batcher.js.map +1 -0
  7. package/dist/call-diagnostic/call-diagnostic-metrics-latencies.js +474 -0
  8. package/dist/call-diagnostic/call-diagnostic-metrics-latencies.js.map +1 -0
  9. package/dist/call-diagnostic/call-diagnostic-metrics.js +850 -0
  10. package/dist/call-diagnostic/call-diagnostic-metrics.js.map +1 -0
  11. package/dist/call-diagnostic/call-diagnostic-metrics.util.js +349 -0
  12. package/dist/call-diagnostic/call-diagnostic-metrics.util.js.map +1 -0
  13. package/dist/call-diagnostic/config.js +609 -0
  14. package/dist/call-diagnostic/config.js.map +1 -0
  15. package/dist/client-metrics-batcher.js +3 -3
  16. package/dist/client-metrics-batcher.js.map +1 -1
  17. package/dist/config.js +6 -9
  18. package/dist/config.js.map +1 -1
  19. package/dist/index.js +35 -2
  20. package/dist/index.js.map +1 -1
  21. package/dist/metrics.js +28 -22
  22. package/dist/metrics.js.map +1 -1
  23. package/dist/metrics.types.js +7 -0
  24. package/dist/metrics.types.js.map +1 -0
  25. package/dist/new-metrics.js +302 -0
  26. package/dist/new-metrics.js.map +1 -0
  27. package/dist/prelogin-metrics-batcher.js +81 -0
  28. package/dist/prelogin-metrics-batcher.js.map +1 -0
  29. package/dist/types/batcher.d.ts +5 -0
  30. package/dist/types/call-diagnostic/call-diagnostic-metrics-latencies.d.ts +204 -0
  31. package/dist/types/call-diagnostic/call-diagnostic-metrics.d.ts +427 -0
  32. package/dist/types/call-diagnostic/call-diagnostic-metrics.util.d.ts +103 -0
  33. package/dist/types/call-diagnostic/config.d.ts +178 -0
  34. package/dist/types/config.d.ts +18 -0
  35. package/dist/types/index.d.ts +15 -3
  36. package/dist/types/metrics.d.ts +1 -0
  37. package/dist/types/metrics.types.d.ts +105 -0
  38. package/dist/types/new-metrics.d.ts +131 -0
  39. package/dist/types/prelogin-metrics-batcher.d.ts +2 -0
  40. package/dist/types/utils.d.ts +6 -0
  41. package/dist/utils.js +26 -0
  42. package/dist/utils.js.map +1 -0
  43. package/jest.config.js +3 -0
  44. package/package.json +34 -10
  45. package/process +1 -0
  46. package/src/batcher.js +38 -0
  47. package/src/call-diagnostic/call-diagnostic-metrics-batcher.ts +72 -0
  48. package/src/call-diagnostic/call-diagnostic-metrics-latencies.ts +435 -0
  49. package/src/call-diagnostic/call-diagnostic-metrics.ts +913 -0
  50. package/src/call-diagnostic/call-diagnostic-metrics.util.ts +392 -0
  51. package/src/call-diagnostic/config.ts +685 -0
  52. package/src/client-metrics-batcher.js +1 -0
  53. package/src/config.js +1 -0
  54. package/src/index.ts +54 -0
  55. package/src/metrics.js +20 -16
  56. package/src/metrics.types.ts +168 -0
  57. package/src/new-metrics.ts +278 -0
  58. package/src/prelogin-metrics-batcher.ts +95 -0
  59. package/src/utils.ts +17 -0
  60. package/test/unit/spec/batcher.js +2 -0
  61. package/test/unit/spec/call-diagnostic/call-diagnostic-metrics-batcher.ts +458 -0
  62. package/test/unit/spec/call-diagnostic/call-diagnostic-metrics-latencies.ts +520 -0
  63. package/test/unit/spec/call-diagnostic/call-diagnostic-metrics.ts +2297 -0
  64. package/test/unit/spec/call-diagnostic/call-diagnostic-metrics.util.ts +628 -0
  65. package/test/unit/spec/client-metrics-batcher.js +2 -0
  66. package/test/unit/spec/metrics.js +76 -95
  67. package/test/unit/spec/new-metrics.ts +233 -0
  68. package/test/unit/spec/prelogin-metrics-batcher.ts +250 -0
  69. package/test/unit/spec/utils.ts +22 -0
  70. package/tsconfig.json +6 -0
  71. package/dist/call-diagnostic-events-batcher.js +0 -60
  72. package/dist/call-diagnostic-events-batcher.js.map +0 -1
  73. package/dist/internal-plugin-metrics.d.ts +0 -21
  74. package/dist/tsdoc-metadata.json +0 -11
  75. package/src/call-diagnostic-events-batcher.js +0 -62
  76. package/src/index.js +0 -17
  77. package/test/unit/spec/call-diagnostic-events-batcher.js +0 -195
  78. package/dist/types/{call-diagnostic-events-batcher.d.ts → call-diagnostic/call-diagnostic-metrics-batcher.d.ts} +1 -1
@@ -0,0 +1,628 @@
1
+ import {assert} from '@webex/test-helper-chai';
2
+ import sinon from 'sinon';
3
+ import {WebexHttpError} from '@webex/webex-core';
4
+
5
+ import * as CallDiagnosticUtils from '../../../../src/call-diagnostic/call-diagnostic-metrics.util';
6
+ import CallDiagnosticLatencies from '../../../../src/call-diagnostic/call-diagnostic-metrics-latencies';
7
+ import {
8
+ DTLS_HANDSHAKE_FAILED_CLIENT_CODE,
9
+ ICE_FAILED_WITHOUT_TURN_TLS_CLIENT_CODE,
10
+ ICE_FAILED_WITH_TURN_TLS_CLIENT_CODE,
11
+ MISSING_ROAP_ANSWER_CLIENT_CODE,
12
+ } from '../../../../src/call-diagnostic/config';
13
+ import Logger from '@webex/plugin-logger';
14
+
15
+ const {
16
+ clearEmptyKeysRecursively,
17
+ extractVersionMetadata,
18
+ getBuildType,
19
+ isBrowserMediaErrorName,
20
+ isLocusServiceErrorCode,
21
+ isMeetingInfoServiceError,
22
+ prepareDiagnosticMetricItem,
23
+ setMetricTimings,
24
+ isNetworkError,
25
+ isUnauthorizedError,
26
+ generateClientErrorCodeForIceFailure,
27
+ isSdpOfferCreationError,
28
+ } = CallDiagnosticUtils;
29
+
30
+ describe('internal-plugin-metrics', () => {
31
+ describe('clearEmptyKeysRecursively', () => {
32
+ it('should clear empty objects and empty nested objects recursively', () => {
33
+ const obj: any = {
34
+ foo: '',
35
+ bar: {},
36
+ baz: [],
37
+ nested: {
38
+ prop: {},
39
+ arr: ['test'],
40
+ },
41
+ };
42
+ clearEmptyKeysRecursively(obj);
43
+ assert.deepEqual(obj, {nested: {arr: ['test']}});
44
+ });
45
+
46
+ it('should not modify non-empty objects and arrays', () => {
47
+ const obj = {
48
+ foo: 'bar',
49
+ arr: [1, 2, 3],
50
+ };
51
+ clearEmptyKeysRecursively(obj);
52
+ assert.deepEqual(obj, {foo: 'bar', arr: [1, 2, 3]});
53
+ });
54
+
55
+ it('should not modify non-object and non-array values', () => {
56
+ const obj = {
57
+ prop1: 'value1',
58
+ prop2: 123,
59
+ };
60
+ clearEmptyKeysRecursively(obj);
61
+ assert.deepEqual(obj, {prop1: 'value1', prop2: 123});
62
+ });
63
+
64
+ it('should handle nested empty objects and arrays', () => {
65
+ const obj: any = {
66
+ foo: {
67
+ bar: {},
68
+ baz: [],
69
+ },
70
+ };
71
+ clearEmptyKeysRecursively(obj);
72
+ assert.deepEqual(obj, {foo: {}});
73
+ });
74
+
75
+ it('should handle an empty input object', () => {
76
+ const obj = {};
77
+ clearEmptyKeysRecursively(obj);
78
+ assert.deepEqual(obj, {});
79
+ });
80
+ });
81
+
82
+ describe('isLocusServiceErrorCode', () => {
83
+ [
84
+ [10000, false],
85
+ [2400000, true],
86
+ ['2400000', true],
87
+ [2400001, true],
88
+ ['2400001', true],
89
+ [240000, false],
90
+ [14000000, false],
91
+ ].forEach(([error, expected]) => {
92
+ it(`for code ${error} returns the correct result`, () => {
93
+ //@ts-ignore
94
+ assert.deepEqual(isLocusServiceErrorCode(error), expected);
95
+ });
96
+ });
97
+ });
98
+
99
+ describe('isMeetingInfoServiceError', () => {
100
+ [
101
+ [{body: {data: {meetingInfo: 'something'}}}, true],
102
+ [{body: {url: 'abcde-123-wbxappapi-efgh'}}, true],
103
+ [{body: {data: {meetingInformation: 'something'}}}, false],
104
+ [{body: {uri: 'abcde-123-wbxappap-efgh'}}, false],
105
+ ['2400001', false],
106
+ [2400001, false],
107
+ [{}, false],
108
+ ].forEach(([rawError, expected]) => {
109
+ it(`for rawError ${rawError} returns the correct result`, () => {
110
+ //@ts-ignore
111
+ assert.deepEqual(isMeetingInfoServiceError(rawError), expected);
112
+ });
113
+ });
114
+ });
115
+
116
+ describe('isNetworkError', () => {
117
+ [
118
+ [{body: {data: {meetingInfo: 'something'}}}, false],
119
+ [
120
+ new WebexHttpError.NetworkOrCORSError({
121
+ url: 'https://example.com',
122
+ statusCode: 0,
123
+ body: {},
124
+ options: {headers: {}, url: 'https://example.com'},
125
+ }),
126
+ true,
127
+ ],
128
+ [
129
+ new WebexHttpError.Unauthorized({
130
+ url: 'https://example.com',
131
+ statusCode: 0,
132
+ body: {},
133
+ options: {headers: {}, url: 'https://example.com'},
134
+ }),
135
+ false,
136
+ ],
137
+ ].forEach(([rawError, expected]) => {
138
+ it(`for rawError ${rawError} returns the correct result`, () => {
139
+ //@ts-ignore
140
+ assert.deepEqual(isNetworkError(rawError), expected);
141
+ });
142
+ });
143
+ });
144
+
145
+ describe('isUnauthorizedError', () => {
146
+ [
147
+ [
148
+ 'unauthorized',
149
+ new WebexHttpError.Unauthorized({
150
+ url: 'https://example.com',
151
+ statusCode: 0,
152
+ body: {},
153
+ options: {headers: {}, url: 'https://example.com'},
154
+ }),
155
+ true,
156
+ ],
157
+ [
158
+ 'network or cors',
159
+ new WebexHttpError.NetworkOrCORSError({
160
+ url: 'https://example.com',
161
+ statusCode: 0,
162
+ body: {},
163
+ options: {headers: {}, url: 'https://example.com'},
164
+ }),
165
+ false,
166
+ ],
167
+ ['other', {body: {data: {meetingInfo: 'something'}}}, false],
168
+ ].forEach(([errorType, rawError, expected]) => {
169
+ it(`for ${errorType} rawError returns the correct result`, () => {
170
+ assert.strictEqual(isUnauthorizedError(rawError), expected);
171
+ });
172
+ });
173
+ });
174
+
175
+ describe('isSdpOfferCreationError', () => {
176
+ type TestWcmeError = {
177
+ type: string;
178
+ message: string;
179
+ };
180
+
181
+ type TestSdpOfferCreationError = {
182
+ code: number;
183
+ message: string;
184
+ name: string;
185
+ cause: TestWcmeError;
186
+ };
187
+
188
+ const error: TestSdpOfferCreationError = {
189
+ code: 30005,
190
+ name: 'SdpOfferCreationError',
191
+ message: 'No codecs present in m-line with MID 0 after filtering.',
192
+ cause: {
193
+ type: 'SDP_MUNGE_MISSING_CODECS',
194
+ message: 'No codecs present in m-line with MID 0 after filtering.',
195
+ },
196
+ };
197
+ [
198
+ ['isSdpOfferCreationError', error, true],
199
+ ['generic error', new Error('this is an error'), false],
200
+ ].forEach(([errorType, rawError, expected]) => {
201
+ it(`for ${errorType} rawError returns the correct result`, () => {
202
+ assert.strictEqual(isSdpOfferCreationError(rawError), expected);
203
+ });
204
+ });
205
+ });
206
+
207
+ describe('isBrowserMediaErrorName', () => {
208
+ [
209
+ ['PermissionDeniedError', true],
210
+ ['PermissionDeniedErrors', false],
211
+ ['NotAllowedError', true],
212
+ ['NotAllowedErrors', false],
213
+ ['NotReadableError', true],
214
+ ['NotReadableErrors', false],
215
+ ['AbortError', true],
216
+ ['AbortErrors', false],
217
+ ['NotFoundError', true],
218
+ ['NotFoundErrors', false],
219
+ ['OverconstrainedError', true],
220
+ ['OverconstrainedErrors', false],
221
+ ['SecurityError', true],
222
+ ['SecurityErrors', false],
223
+ ['TypeError', true],
224
+ ['TypeErrors', false],
225
+ ['', false],
226
+ ['SomethingElse', false],
227
+ [{name: 'SomethingElse'}, false],
228
+ ].forEach(([errorName, expected]) => {
229
+ it(`for rawError ${errorName} returns the correct result`, () => {
230
+ //@ts-ignore
231
+ assert.deepEqual(isBrowserMediaErrorName(errorName), expected);
232
+ });
233
+ });
234
+ });
235
+
236
+ describe('getBuildType', () => {
237
+ beforeEach(() => {
238
+ process.env.NODE_ENV = 'production';
239
+ });
240
+
241
+ [
242
+ ['https://localhost', undefined, 'test'],
243
+ ['https://127.0.0.1', undefined, 'test'],
244
+ ['https://web.webex.com', undefined, 'prod'],
245
+ ['https://web.webex.com', true, 'test'],
246
+ ].forEach(([webClientDomain, markAsTestEvent, expected]) => {
247
+ it(`returns expected result for ${webClientDomain}`, () => {
248
+ assert.deepEqual(getBuildType(webClientDomain, markAsTestEvent as any), expected);
249
+ });
250
+ });
251
+
252
+ it('returns "test" for NODE_ENV "foo"', () => {
253
+ process.env.NODE_ENV = 'foo';
254
+ assert.deepEqual(getBuildType('production'), 'test');
255
+ });
256
+
257
+ it('returns "test" for NODE_ENV "production" and markAsTestEvent = true', () => {
258
+ process.env.NODE_ENV = 'production';
259
+ assert.deepEqual(getBuildType('my.domain', true), 'test');
260
+ });
261
+ });
262
+
263
+ describe('prepareDiagnosticMetricItem', () => {
264
+ let webex: any;
265
+
266
+ const check = (eventName: string, expectedEvent: any) => {
267
+ const eventPayload = {event: {name: eventName}};
268
+ const item = prepareDiagnosticMetricItem(webex, {
269
+ eventPayload,
270
+ type: ['diagnostic-event'],
271
+ });
272
+
273
+ assert.deepEqual(item, {
274
+ eventPayload: {
275
+ origin: {
276
+ buildType: 'prod',
277
+ networkType: 'unknown',
278
+ },
279
+ event: {name: eventName, ...expectedEvent},
280
+ },
281
+ type: ['diagnostic-event'],
282
+ });
283
+ };
284
+
285
+ beforeEach(async () => {
286
+ webex = {internal: {newMetrics: {}}};
287
+ webex.internal.newMetrics.callDiagnosticLatencies = new CallDiagnosticLatencies(
288
+ {},
289
+ {parent: webex}
290
+ );
291
+ webex.logger = new Logger({}, {parent: webex});
292
+ });
293
+
294
+ beforeEach(() => {
295
+ process.env.NODE_ENV = 'production';
296
+ });
297
+
298
+ afterEach(() => {
299
+ process.env.NODE_ENV = 'test';
300
+ });
301
+
302
+ [
303
+ ['client.exit.app', {}],
304
+ ['client.webexapp.launched', {
305
+ joinTimes: {
306
+ downloadTime: undefined,
307
+ }
308
+ }],
309
+ [
310
+ 'client.interstitial-window.launched',
311
+ {
312
+ joinTimes: {
313
+ clickToInterstitial: undefined,
314
+ meetingInfoReqResp: undefined,
315
+ },
316
+ },
317
+ ],
318
+ [
319
+ 'client.call.initiated',
320
+ {
321
+ joinTimes: {
322
+ showInterstitialTime: undefined,
323
+ meetingInfoReqResp: undefined,
324
+ registerWDMDeviceJMT: undefined,
325
+ },
326
+ },
327
+ ],
328
+ [
329
+ 'client.locus.join.response',
330
+ {
331
+ joinTimes: {
332
+ meetingInfoReqResp: undefined,
333
+ callInitJoinReq: undefined,
334
+ joinReqResp: undefined,
335
+ joinReqSentReceived: undefined,
336
+ pageJmt: undefined,
337
+ clickToInterstitial: undefined,
338
+ interstitialToJoinOK: undefined,
339
+ totalJmt: undefined,
340
+ clientJmt: undefined,
341
+ downloadTime: undefined
342
+ },
343
+ },
344
+ ],
345
+ [
346
+ 'client.ice.end',
347
+ {
348
+ joinTimes: {
349
+ ICESetupTime: undefined,
350
+ audioICESetupTime: undefined,
351
+ videoICESetupTime: undefined,
352
+ shareICESetupTime: undefined,
353
+ },
354
+ },
355
+ ],
356
+ [
357
+ 'client.media.rx.start',
358
+ {
359
+ joinTimes: {
360
+ localSDPGenRemoteSDPRecv: undefined,
361
+ },
362
+ },
363
+ ],
364
+ [
365
+ 'client.media-engine.ready',
366
+ {
367
+ joinTimes: {
368
+ totalMediaJMT: undefined,
369
+ interstitialToMediaOKJMT: undefined,
370
+ callInitMediaEngineReady: undefined,
371
+ stayLobbyTime: undefined,
372
+ },
373
+ },
374
+ ],
375
+ [
376
+ 'client.mediaquality.event',
377
+ {
378
+ audioSetupDelay: {
379
+ joinRespRxStart: undefined,
380
+ joinRespTxStart: undefined,
381
+ },
382
+ videoSetupDelay: {
383
+ joinRespRxStart: undefined,
384
+ joinRespTxStart: undefined,
385
+ },
386
+ },
387
+ ],
388
+ ].forEach(([eventName, expectedEvent]) => {
389
+ it(`returns expected result for ${eventName}`, () => {
390
+ check(eventName as string, expectedEvent);
391
+ });
392
+ });
393
+
394
+ it('calls getBuildType correctly', () => {
395
+ const getBuildTypeSpy = sinon.spy(CallDiagnosticUtils, 'getBuildType');
396
+ const markAsTestEvent = true;
397
+ const webClientDomain = 'https://web.webex.com';
398
+
399
+ // just submit any event
400
+ prepareDiagnosticMetricItem(webex, {
401
+ eventPayload: {
402
+ event: {name: 'client.exit.app', eventData: {markAsTestEvent, webClientDomain}},
403
+ },
404
+ type: ['diagnostic-event'],
405
+ });
406
+
407
+ assert.calledOnceWithExactly(getBuildTypeSpy, webClientDomain, markAsTestEvent);
408
+ });
409
+ });
410
+
411
+ describe('setMetricTimings', () => {
412
+ let webex: any;
413
+
414
+ const check = (options: any, expectedOptions: any) => {
415
+ const newOptions = setMetricTimings(options);
416
+
417
+ assert.deepEqual(newOptions, expectedOptions);
418
+ };
419
+
420
+ it(`returns expected options`, () => {
421
+ const now = new Date();
422
+ sinon.useFakeTimers(now.getTime());
423
+
424
+ const options = {
425
+ json: true,
426
+ body: JSON.stringify({
427
+ metrics: [
428
+ {
429
+ eventPayload: {
430
+ originTime: {
431
+ triggered: 555,
432
+ sent: 666,
433
+ },
434
+ },
435
+ },
436
+ ],
437
+ }),
438
+ };
439
+
440
+ const expectedOptions = {
441
+ json: true,
442
+ body: JSON.stringify({
443
+ metrics: [
444
+ {
445
+ eventPayload: {
446
+ originTime: {
447
+ triggered: now.toISOString(),
448
+ sent: now.toISOString(),
449
+ },
450
+ },
451
+ },
452
+ ],
453
+ }),
454
+ };
455
+
456
+ check(options, expectedOptions);
457
+ sinon.restore();
458
+ });
459
+
460
+ it(`returns expected options for multiple metrics`, () => {
461
+ const now = new Date();
462
+ sinon.useFakeTimers(now.getTime());
463
+
464
+ const options = {
465
+ json: true,
466
+ body: JSON.stringify({
467
+ metrics: [
468
+ {
469
+ eventPayload: {
470
+ originTime: {
471
+ triggered: 555,
472
+ sent: 666,
473
+ },
474
+ },
475
+ },
476
+ {
477
+ eventPayload: {
478
+ originTime: {
479
+ triggered: 777,
480
+ sent: 888,
481
+ },
482
+ },
483
+ },
484
+ ],
485
+ }),
486
+ };
487
+
488
+ const expectedOptions = {
489
+ json: true,
490
+ body: JSON.stringify({
491
+ metrics: [
492
+ {
493
+ eventPayload: {
494
+ originTime: {
495
+ triggered: now.toISOString(),
496
+ sent: now.toISOString(),
497
+ },
498
+ },
499
+ },
500
+ {
501
+ eventPayload: {
502
+ originTime: {
503
+ triggered: now.toISOString(),
504
+ sent: now.toISOString(),
505
+ },
506
+ },
507
+ },
508
+ ],
509
+ }),
510
+ };
511
+
512
+ check(options, expectedOptions);
513
+ sinon.restore();
514
+ });
515
+
516
+ it(`returns expected options when json is falsey`, () => {
517
+ const now = new Date();
518
+ sinon.useFakeTimers(now.getTime());
519
+
520
+ const options = {
521
+ body: JSON.stringify({
522
+ metrics: [
523
+ {
524
+ eventPayload: {
525
+ originTime: {
526
+ triggered: 555,
527
+ sent: 666,
528
+ },
529
+ },
530
+ },
531
+ ],
532
+ }),
533
+ };
534
+
535
+ const expectedOptions = {
536
+ body: JSON.stringify({
537
+ metrics: [
538
+ {
539
+ eventPayload: {
540
+ originTime: {
541
+ triggered: 555,
542
+ sent: 666,
543
+ },
544
+ },
545
+ },
546
+ ],
547
+ }),
548
+ };
549
+
550
+ check(options, expectedOptions);
551
+ sinon.restore();
552
+ });
553
+
554
+ it(`does not throw when there is no body`, () => {
555
+ const options = {};
556
+
557
+ const expectedOptions = {};
558
+
559
+ check(options, expectedOptions);
560
+ });
561
+
562
+ it(`does not throw when body is empty`, () => {
563
+ const options = {body: '"{}"'};
564
+
565
+ const expectedOptions = {body: '"{}"'};
566
+
567
+ check(options, expectedOptions);
568
+ });
569
+ });
570
+
571
+ describe('extractVersionMetadata', () => {
572
+ [
573
+ ['1.2.3', {majorVersion: 1, minorVersion: 2}],
574
+ ['0.0.1', {majorVersion: 0, minorVersion: 0}],
575
+ ['0.0.0', {majorVersion: 0, minorVersion: 0}],
576
+ ['1.2', {majorVersion: 1, minorVersion: 2}],
577
+ ['1', {majorVersion: 1, minorVersion: NaN}],
578
+ ['foo', {majorVersion: NaN, minorVersion: NaN}],
579
+ ['1.foo', {majorVersion: 1, minorVersion: NaN}],
580
+ ['foo.1', {majorVersion: NaN, minorVersion: 1}],
581
+ ['foo.bar', {majorVersion: NaN, minorVersion: NaN}],
582
+ ].forEach(([version, expected]) => {
583
+ it(`returns expected result for ${version}`, () => {
584
+ assert.deepEqual(extractVersionMetadata(version as string), expected);
585
+ });
586
+ });
587
+ });
588
+
589
+ describe('generateClientErrorCodeForIceFailure', () => {
590
+ [
591
+ {
592
+ signalingState: 'have-local-offer',
593
+ iceConnectionState: 'connected',
594
+ turnServerUsed: true,
595
+ errorCode: MISSING_ROAP_ANSWER_CLIENT_CODE,
596
+ },
597
+ {
598
+ signalingState: 'stable',
599
+ iceConnectionState: 'connected',
600
+ turnServerUsed: true,
601
+ errorCode: DTLS_HANDSHAKE_FAILED_CLIENT_CODE,
602
+ },
603
+ {
604
+ signalingState: 'stable',
605
+ iceConnectionState: 'failed',
606
+ turnServerUsed: true,
607
+ errorCode: ICE_FAILED_WITH_TURN_TLS_CLIENT_CODE,
608
+ },
609
+ {
610
+ signalingState: 'stable',
611
+ iceConnectionState: 'failed',
612
+ turnServerUsed: false,
613
+ errorCode: ICE_FAILED_WITHOUT_TURN_TLS_CLIENT_CODE,
614
+ },
615
+ ].forEach(({signalingState, iceConnectionState, turnServerUsed, errorCode}: any) => {
616
+ it('returns expected result', () => {
617
+ assert.deepEqual(
618
+ generateClientErrorCodeForIceFailure({
619
+ signalingState,
620
+ iceConnectionState,
621
+ turnServerUsed,
622
+ }),
623
+ errorCode
624
+ );
625
+ });
626
+ });
627
+ });
628
+ });
@@ -37,6 +37,7 @@ describe('plugin-metrics', () => {
37
37
  return Promise.resolve({
38
38
  statusCode: 204,
39
39
  body: undefined,
40
+ waitForServiceTimeout: 30,
40
41
  options,
41
42
  });
42
43
  };
@@ -97,6 +98,7 @@ describe('plugin-metrics', () => {
97
98
  return Promise.resolve({
98
99
  statusCode: 204,
99
100
  body: undefined,
101
+ waitForServiceTimeout: 30,
100
102
  options,
101
103
  });
102
104
  });