@webex/plugin-meetings 3.3.1 → 3.4.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 (126) hide show
  1. package/dist/breakouts/breakout.js +1 -1
  2. package/dist/breakouts/index.js +7 -2
  3. package/dist/breakouts/index.js.map +1 -1
  4. package/dist/constants.js +11 -4
  5. package/dist/constants.js.map +1 -1
  6. package/dist/interpretation/index.js +1 -1
  7. package/dist/interpretation/siLanguage.js +1 -1
  8. package/dist/locus-info/selfUtils.js +0 -5
  9. package/dist/locus-info/selfUtils.js.map +1 -1
  10. package/dist/media/MediaConnectionAwaiter.js +70 -15
  11. package/dist/media/MediaConnectionAwaiter.js.map +1 -1
  12. package/dist/media/index.js +12 -0
  13. package/dist/media/index.js.map +1 -1
  14. package/dist/meeting/connectionStateHandler.js +67 -0
  15. package/dist/meeting/connectionStateHandler.js.map +1 -0
  16. package/dist/meeting/index.js +552 -357
  17. package/dist/meeting/index.js.map +1 -1
  18. package/dist/meeting/locusMediaRequest.js +7 -0
  19. package/dist/meeting/locusMediaRequest.js.map +1 -1
  20. package/dist/meeting/muteState.js +6 -1
  21. package/dist/meeting/muteState.js.map +1 -1
  22. package/dist/meeting/util.js +1 -0
  23. package/dist/meeting/util.js.map +1 -1
  24. package/dist/meeting-info/index.js +4 -4
  25. package/dist/meeting-info/index.js.map +1 -1
  26. package/dist/meeting-info/meeting-info-v2.js +2 -2
  27. package/dist/meeting-info/meeting-info-v2.js.map +1 -1
  28. package/dist/meeting-info/util.js +17 -17
  29. package/dist/meeting-info/util.js.map +1 -1
  30. package/dist/meeting-info/utilv2.js +16 -16
  31. package/dist/meeting-info/utilv2.js.map +1 -1
  32. package/dist/meetings/collection.js +1 -1
  33. package/dist/meetings/collection.js.map +1 -1
  34. package/dist/meetings/index.js +37 -33
  35. package/dist/meetings/index.js.map +1 -1
  36. package/dist/meetings/meetings.types.js +8 -0
  37. package/dist/meetings/meetings.types.js.map +1 -1
  38. package/dist/meetings/util.js +3 -2
  39. package/dist/meetings/util.js.map +1 -1
  40. package/dist/metrics/constants.js +2 -1
  41. package/dist/metrics/constants.js.map +1 -1
  42. package/dist/metrics/index.js +57 -0
  43. package/dist/metrics/index.js.map +1 -1
  44. package/dist/personal-meeting-room/index.js +1 -1
  45. package/dist/personal-meeting-room/index.js.map +1 -1
  46. package/dist/reachability/clusterReachability.js +108 -53
  47. package/dist/reachability/clusterReachability.js.map +1 -1
  48. package/dist/reachability/index.js +415 -56
  49. package/dist/reachability/index.js.map +1 -1
  50. package/dist/types/constants.d.ts +11 -3
  51. package/dist/types/media/MediaConnectionAwaiter.d.ts +24 -4
  52. package/dist/types/meeting/connectionStateHandler.d.ts +30 -0
  53. package/dist/types/meeting/index.d.ts +27 -7
  54. package/dist/types/meeting/locusMediaRequest.d.ts +2 -0
  55. package/dist/types/meeting-info/index.d.ts +3 -2
  56. package/dist/types/meeting-info/meeting-info-v2.d.ts +3 -2
  57. package/dist/types/meeting-info/util.d.ts +5 -4
  58. package/dist/types/meeting-info/utilv2.d.ts +3 -2
  59. package/dist/types/meetings/collection.d.ts +3 -2
  60. package/dist/types/meetings/index.d.ts +4 -3
  61. package/dist/types/meetings/meetings.types.d.ts +9 -0
  62. package/dist/types/metrics/constants.d.ts +1 -0
  63. package/dist/types/metrics/index.d.ts +15 -0
  64. package/dist/types/reachability/clusterReachability.d.ts +31 -3
  65. package/dist/types/reachability/index.d.ts +93 -2
  66. package/dist/webinar/index.js +1 -1
  67. package/package.json +23 -23
  68. package/src/breakouts/index.ts +7 -1
  69. package/src/constants.ts +13 -17
  70. package/src/locus-info/selfUtils.ts +0 -5
  71. package/src/media/MediaConnectionAwaiter.ts +89 -14
  72. package/src/media/index.ts +13 -0
  73. package/src/meeting/connectionStateHandler.ts +65 -0
  74. package/src/meeting/index.ts +526 -292
  75. package/src/meeting/locusMediaRequest.ts +5 -0
  76. package/src/meeting/muteState.ts +6 -1
  77. package/src/meeting/util.ts +1 -0
  78. package/src/meeting-info/index.ts +9 -6
  79. package/src/meeting-info/meeting-info-v2.ts +4 -4
  80. package/src/meeting-info/util.ts +23 -28
  81. package/src/meeting-info/utilv2.ts +18 -24
  82. package/src/meetings/collection.ts +3 -3
  83. package/src/meetings/index.ts +39 -40
  84. package/src/meetings/meetings.types.ts +11 -0
  85. package/src/meetings/util.ts +5 -4
  86. package/src/metrics/constants.ts +1 -0
  87. package/src/metrics/index.ts +44 -0
  88. package/src/personal-meeting-room/index.ts +2 -2
  89. package/src/reachability/clusterReachability.ts +86 -25
  90. package/src/reachability/index.ts +316 -27
  91. package/test/unit/spec/breakouts/index.ts +51 -32
  92. package/test/unit/spec/locus-info/selfUtils.js +25 -23
  93. package/test/unit/spec/media/MediaConnectionAwaiter.ts +131 -32
  94. package/test/unit/spec/media/index.ts +42 -27
  95. package/test/unit/spec/meeting/connectionStateHandler.ts +102 -0
  96. package/test/unit/spec/meeting/index.js +758 -179
  97. package/test/unit/spec/meeting/locusMediaRequest.ts +7 -0
  98. package/test/unit/spec/meeting/muteState.js +24 -0
  99. package/test/unit/spec/meeting-info/index.js +4 -4
  100. package/test/unit/spec/meeting-info/meetinginfov2.js +24 -28
  101. package/test/unit/spec/meeting-info/request.js +2 -2
  102. package/test/unit/spec/meeting-info/utilv2.js +41 -49
  103. package/test/unit/spec/meetings/index.js +14 -0
  104. package/test/unit/spec/metrics/index.js +126 -0
  105. package/test/unit/spec/multistream/mediaRequestManager.ts +2 -2
  106. package/test/unit/spec/personal-meeting-room/personal-meeting-room.js +2 -2
  107. package/test/unit/spec/reachability/clusterReachability.ts +116 -22
  108. package/test/unit/spec/reachability/index.ts +1153 -84
  109. package/test/unit/spec/rtcMetrics/index.ts +1 -0
  110. package/dist/mediaQualityMetrics/config.js +0 -321
  111. package/dist/mediaQualityMetrics/config.js.map +0 -1
  112. package/dist/statsAnalyzer/global.js +0 -44
  113. package/dist/statsAnalyzer/global.js.map +0 -1
  114. package/dist/statsAnalyzer/index.js +0 -1072
  115. package/dist/statsAnalyzer/index.js.map +0 -1
  116. package/dist/statsAnalyzer/mqaUtil.js +0 -368
  117. package/dist/statsAnalyzer/mqaUtil.js.map +0 -1
  118. package/dist/types/mediaQualityMetrics/config.d.ts +0 -247
  119. package/dist/types/statsAnalyzer/global.d.ts +0 -36
  120. package/dist/types/statsAnalyzer/index.d.ts +0 -217
  121. package/dist/types/statsAnalyzer/mqaUtil.d.ts +0 -48
  122. package/src/mediaQualityMetrics/config.ts +0 -255
  123. package/src/statsAnalyzer/global.ts +0 -37
  124. package/src/statsAnalyzer/index.ts +0 -1318
  125. package/src/statsAnalyzer/mqaUtil.ts +0 -463
  126. package/test/unit/spec/stats-analyzer/index.js +0 -1819
@@ -1,12 +1,16 @@
1
1
  import {assert} from '@webex/test-helper-chai';
2
2
  import MockWebex from '@webex/test-helper-mock-webex';
3
3
  import sinon from 'sinon';
4
+ import EventEmitter from 'events';
5
+ import testUtils from '../../../utils/testUtils';
4
6
  import Reachability, {
5
7
  ReachabilityResults,
6
8
  ReachabilityResultsForBackend,
7
9
  } from '@webex/plugin-meetings/src/reachability/';
10
+ import { ClusterNode } from '../../../../src/reachability/request';
8
11
  import MeetingUtil from '@webex/plugin-meetings/src/meeting/util';
9
12
  import * as ClusterReachabilityModule from '@webex/plugin-meetings/src/reachability/clusterReachability';
13
+ import Metrics from '@webex/plugin-meetings/src/metrics';
10
14
 
11
15
  import {IP_VERSION} from '@webex/plugin-meetings/src/constants';
12
16
 
@@ -406,13 +410,71 @@ describe('isWebexMediaBackendUnreachable', () => {
406
410
  });
407
411
  });
408
412
 
413
+ /**
414
+ * helper class to mock ClusterReachability and allow to easily
415
+ * simulate 'resultReady' events from it
416
+ */
417
+ class MockClusterReachability extends EventEmitter {
418
+ mockResult = {
419
+ udp: {
420
+ result: 'untested',
421
+ },
422
+ tcp: {
423
+ result: 'untested',
424
+ },
425
+ xtls: {
426
+ result: 'untested',
427
+ },
428
+ };
429
+
430
+ isVideoMesh: boolean;
431
+ name: string;
432
+
433
+ constructor(name: string, clusterInfo: ClusterNode) {
434
+ super();
435
+ this.name = name;
436
+ this.isVideoMesh = clusterInfo.isVideoMesh;
437
+ }
438
+
439
+ abort = sinon.stub();
440
+ start = sinon.stub();
441
+
442
+ getResult() {
443
+ return this.mockResult;
444
+ }
445
+
446
+ /**
447
+ * Emits a fake 'resultReady' event and makes sure that the same result
448
+ * is returned when getResult() is called.
449
+ *
450
+ * @param protocol
451
+ * @param result
452
+ */
453
+ public emitFakeResult(protocol, result) {
454
+ this.mockResult[protocol] = result;
455
+ this.emit(ClusterReachabilityModule.Events.resultReady, {protocol, ...result});
456
+ }
457
+
458
+ public emitFakeClientMediaIpUpdate(protocol, newIp) {
459
+ this.mockResult[protocol].clientMediaIPs.push(newIp);
460
+ this.emit(ClusterReachabilityModule.Events.clientMediaIpsUpdated, {
461
+ protocol,
462
+ clientMediaIPs: this.mockResult[protocol].clientMediaIPs,
463
+ });
464
+ }
465
+ }
409
466
 
410
467
  describe('gatherReachability', () => {
411
468
  let webex;
469
+ let clock;
470
+ let clusterReachabilityCtorStub;
471
+ let mockClusterReachabilityInstances: Record<string, MockClusterReachability>;
412
472
 
413
473
  beforeEach(async () => {
414
474
  webex = new MockWebex();
415
475
 
476
+ sinon.stub(Metrics, 'sendBehavioralMetric');
477
+
416
478
  await webex.boundedStorage.put(
417
479
  'Reachability',
418
480
  'reachability.result',
@@ -423,94 +485,820 @@ describe('gatherReachability', () => {
423
485
  'reachability.joinCookie',
424
486
  JSON.stringify({old: 'joinCookie'})
425
487
  );
488
+
489
+ clock = sinon.useFakeTimers();
490
+
491
+ mockClusterReachabilityInstances = {};
492
+
493
+ clusterReachabilityCtorStub = sinon
494
+ .stub(ClusterReachabilityModule, 'ClusterReachability')
495
+ .callsFake((id, cluster) => {
496
+ const mockInstance = new MockClusterReachability(id, cluster);
497
+
498
+ mockClusterReachabilityInstances[id] = mockInstance;
499
+ return mockInstance;
500
+ });
426
501
  });
427
502
 
428
503
  afterEach(() => {
429
504
  sinon.restore();
505
+ clock.restore();
430
506
  });
431
507
 
432
- it('stores the reachability', async () => {
508
+ // simulates time progression so that Reachability times out
509
+ const simulateTimeout = async () => {
510
+ await testUtils.flushPromises();
511
+ clock.tick(3000);
512
+ };
513
+
514
+ const checkResults = async (expectedResults, expectedJoinCookie) => {
515
+ const storedResultForReachabilityResult = await webex.boundedStorage.get(
516
+ 'Reachability',
517
+ 'reachability.result'
518
+ );
519
+ const storedResultForJoinCookie = await webex.boundedStorage.get(
520
+ 'Reachability',
521
+ 'reachability.joinCookie'
522
+ );
523
+
524
+ assert.equal(storedResultForReachabilityResult, JSON.stringify(expectedResults));
525
+ assert.equal(storedResultForJoinCookie, JSON.stringify(expectedJoinCookie));
526
+ };
527
+
528
+ [
529
+ // ========================================================================
530
+ {
531
+ title: '1 cluster with events triggered for each protocol',
532
+ waitShortTimeout: false,
533
+ waitLongTimeout: false,
534
+ mockClusters: {
535
+ cluster1: {
536
+ udp: ['udp-url1'],
537
+ tcp: ['tcp-url1'],
538
+ xtls: ['xtls-url1'],
539
+ isVideoMesh: false,
540
+ },
541
+ },
542
+ mockResultReadyEvents: [
543
+ {
544
+ clusterId: 'cluster1',
545
+ protocol: 'tcp',
546
+ result: {
547
+ result: 'reachable',
548
+ latencyInMilliseconds: 11,
549
+ },
550
+ },
551
+ {
552
+ clusterId: 'cluster1',
553
+ protocol: 'udp',
554
+ result: {
555
+ result: 'reachable',
556
+ clientMediaIPs: ['1.2.3.4'],
557
+ latencyInMilliseconds: 22,
558
+ },
559
+ },
560
+ {
561
+ clusterId: 'cluster1',
562
+ protocol: 'xtls',
563
+ result: {
564
+ result: 'reachable',
565
+ latencyInMilliseconds: 33,
566
+ },
567
+ },
568
+ ],
569
+ expectedResults: {
570
+ cluster1: {
571
+ udp: {result: 'reachable', clientMediaIPs: ['1.2.3.4'], latencyInMilliseconds: 22},
572
+ tcp: {result: 'reachable', latencyInMilliseconds: 11},
573
+ xtls: {result: 'reachable', latencyInMilliseconds: 33},
574
+ isVideoMesh: false,
575
+ },
576
+ },
577
+ expectedMetrics: {
578
+ vmn_udp_min: -1,
579
+ vmn_udp_max: -1,
580
+ vmn_udp_average: -1,
581
+ public_udp_min: 22,
582
+ public_udp_max: 22,
583
+ public_udp_average: 22,
584
+ public_tcp_min: 11,
585
+ public_tcp_max: 11,
586
+ public_tcp_average: 11,
587
+ public_xtls_min: 33,
588
+ public_xtls_max: 33,
589
+ public_xtls_average: 33,
590
+ },
591
+ },
592
+ // ========================================================================
593
+ {
594
+ title:
595
+ '3 clusters: one with an event for each protocol, one with no events, one with no urls for tcp and xtls',
596
+ waitShortTimeout: 'public',
597
+ waitLongTimeout: true,
598
+ mockClusters: {
599
+ cluster1: {
600
+ udp: ['udp-url1.1', 'udp-url1.2'],
601
+ tcp: ['tcp-url1.1', 'tcp-url1.2'],
602
+ xtls: ['xtls-url1.1', 'xtls-url1.2'],
603
+ isVideoMesh: false,
604
+ },
605
+ cluster2: {
606
+ udp: ['udp-url2.1'],
607
+ tcp: ['tcp-url2.1'],
608
+ xtls: ['xtls-url2.1'],
609
+ isVideoMesh: false,
610
+ },
611
+ cluster3: {
612
+ udp: ['udp-url1'],
613
+ tcp: [],
614
+ xtls: [],
615
+ isVideoMesh: true,
616
+ },
617
+ },
618
+ mockResultReadyEvents: [
619
+ {
620
+ clusterId: 'cluster1',
621
+ protocol: 'udp',
622
+ result: {
623
+ result: 'reachable',
624
+ clientMediaIPs: ['1.2.3.4'],
625
+ latencyInMilliseconds: 13,
626
+ },
627
+ },
628
+ {
629
+ clusterId: 'cluster1',
630
+ protocol: 'tcp',
631
+ result: {
632
+ result: 'reachable',
633
+ latencyInMilliseconds: 53,
634
+ },
635
+ },
636
+ {
637
+ clusterId: 'cluster1',
638
+ protocol: 'xtls',
639
+ result: {
640
+ result: 'reachable',
641
+ latencyInMilliseconds: 113,
642
+ },
643
+ },
644
+ ],
645
+ expectedResults: {
646
+ cluster1: {
647
+ udp: {result: 'reachable', clientMediaIPs: ['1.2.3.4'], latencyInMilliseconds: 13},
648
+ tcp: {result: 'reachable', latencyInMilliseconds: 53},
649
+ xtls: {result: 'reachable', latencyInMilliseconds: 113},
650
+ isVideoMesh: false,
651
+ },
652
+ cluster2: {
653
+ udp: {result: 'unreachable'},
654
+ tcp: {result: 'unreachable'},
655
+ xtls: {result: 'unreachable'},
656
+ isVideoMesh: false,
657
+ },
658
+ cluster3: {
659
+ udp: {result: 'unreachable'},
660
+ tcp: {result: 'untested'},
661
+ xtls: {result: 'untested'},
662
+ isVideoMesh: true,
663
+ },
664
+ },
665
+ expectedMetrics: {
666
+ vmn_udp_min: -1,
667
+ vmn_udp_max: -1,
668
+ vmn_udp_average: -1,
669
+ public_udp_min: 13,
670
+ public_udp_max: 13,
671
+ public_udp_average: 13,
672
+ public_tcp_min: 53,
673
+ public_tcp_max: 53,
674
+ public_tcp_average: 53,
675
+ public_xtls_min: 113,
676
+ public_xtls_max: 113,
677
+ public_xtls_average: 113,
678
+ },
679
+ },
680
+ // ========================================================================
681
+ {
682
+ title: '3 clusters: all with all results ready in time for all protocols',
683
+ waitShortTimeout: false,
684
+ waitLongTimeout: false,
685
+ mockClusters: {
686
+ cluster1: {
687
+ udp: ['udp-url1'],
688
+ tcp: ['tcp-url1'],
689
+ xtls: ['xtls-url1'],
690
+ isVideoMesh: false,
691
+ },
692
+ cluster2: {
693
+ udp: ['udp-url2'],
694
+ tcp: ['tcp-url2'],
695
+ xtls: ['xtls-url2'],
696
+ isVideoMesh: false,
697
+ },
698
+ cluster3: {
699
+ udp: ['udp-url3'],
700
+ tcp: ['tcp-url3'],
701
+ xtls: ['xtls-url3'],
702
+ isVideoMesh: false,
703
+ },
704
+ },
705
+ mockResultReadyEvents: [
706
+ {
707
+ clusterId: 'cluster1',
708
+ protocol: 'udp',
709
+ result: {
710
+ result: 'reachable',
711
+ clientMediaIPs: ['1.2.3.4'],
712
+ latencyInMilliseconds: 10,
713
+ },
714
+ },
715
+ {
716
+ clusterId: 'cluster1',
717
+ protocol: 'tcp',
718
+ result: {
719
+ result: 'reachable',
720
+ latencyInMilliseconds: 100,
721
+ },
722
+ },
723
+ {
724
+ clusterId: 'cluster1',
725
+ protocol: 'xtls',
726
+ result: {
727
+ result: 'reachable',
728
+ latencyInMilliseconds: 200,
729
+ },
730
+ },
731
+ {
732
+ clusterId: 'cluster2',
733
+ protocol: 'udp',
734
+ result: {
735
+ result: 'reachable',
736
+ clientMediaIPs: ['1.2.3.4'],
737
+ latencyInMilliseconds: 20,
738
+ },
739
+ },
740
+ {
741
+ clusterId: 'cluster2',
742
+ protocol: 'tcp',
743
+ result: {
744
+ result: 'reachable',
745
+ latencyInMilliseconds: 110,
746
+ },
747
+ },
748
+ {
749
+ clusterId: 'cluster2',
750
+ protocol: 'xtls',
751
+ result: {
752
+ result: 'reachable',
753
+ latencyInMilliseconds: 220,
754
+ },
755
+ },
756
+ {
757
+ clusterId: 'cluster3',
758
+ protocol: 'udp',
759
+ result: {
760
+ result: 'reachable',
761
+ clientMediaIPs: ['1.2.3.4'],
762
+ latencyInMilliseconds: 30,
763
+ },
764
+ },
765
+ {
766
+ clusterId: 'cluster3',
767
+ protocol: 'tcp',
768
+ result: {
769
+ result: 'reachable',
770
+ latencyInMilliseconds: 120,
771
+ },
772
+ },
773
+ {
774
+ clusterId: 'cluster3',
775
+ protocol: 'xtls',
776
+ result: {
777
+ result: 'reachable',
778
+ latencyInMilliseconds: 240,
779
+ },
780
+ },
781
+ ],
782
+ expectedResults: {
783
+ cluster1: {
784
+ udp: {result: 'reachable', clientMediaIPs: ['1.2.3.4'], latencyInMilliseconds: 10},
785
+ tcp: {result: 'reachable', latencyInMilliseconds: 100},
786
+ xtls: {result: 'reachable', latencyInMilliseconds: 200},
787
+ isVideoMesh: false,
788
+ },
789
+ cluster2: {
790
+ udp: {result: 'reachable', clientMediaIPs: ['1.2.3.4'], latencyInMilliseconds: 20},
791
+ tcp: {result: 'reachable', latencyInMilliseconds: 110},
792
+ xtls: {result: 'reachable', latencyInMilliseconds: 220},
793
+ isVideoMesh: false,
794
+ },
795
+ cluster3: {
796
+ udp: {result: 'reachable', clientMediaIPs: ['1.2.3.4'], latencyInMilliseconds: 30},
797
+ tcp: {result: 'reachable', latencyInMilliseconds: 120},
798
+ xtls: {result: 'reachable', latencyInMilliseconds: 240},
799
+ isVideoMesh: false,
800
+ },
801
+ },
802
+ expectedMetrics: {
803
+ vmn_udp_min: -1,
804
+ vmn_udp_max: -1,
805
+ vmn_udp_average: -1,
806
+ public_udp_min: 10,
807
+ public_udp_max: 30,
808
+ public_udp_average: 20,
809
+ public_tcp_min: 100,
810
+ public_tcp_max: 120,
811
+ public_tcp_average: 110,
812
+ public_xtls_min: 200,
813
+ public_xtls_max: 240,
814
+ public_xtls_average: 220,
815
+ },
816
+ },
817
+ // ========================================================================
818
+ {
819
+ title: '2 clusters: both with no results at all',
820
+ waitShortTimeout: 'public',
821
+ waitLongTimeout: true,
822
+ mockClusters: {
823
+ cluster1: {
824
+ udp: ['udp-url1'],
825
+ tcp: ['tcp-url1'],
826
+ xtls: ['xtls-url1'],
827
+ isVideoMesh: false,
828
+ },
829
+ cluster2: {
830
+ udp: ['udp-url2'],
831
+ tcp: ['tcp-url2'],
832
+ xtls: ['xtls-url2'],
833
+ isVideoMesh: false,
834
+ },
835
+ },
836
+ mockResultReadyEvents: [],
837
+ expectedResults: {
838
+ cluster1: {
839
+ udp: {result: 'unreachable'},
840
+ tcp: {result: 'unreachable'},
841
+ xtls: {result: 'unreachable'},
842
+ isVideoMesh: false,
843
+ },
844
+ cluster2: {
845
+ udp: {result: 'unreachable'},
846
+ tcp: {result: 'unreachable'},
847
+ xtls: {result: 'unreachable'},
848
+ isVideoMesh: false,
849
+ },
850
+ },
851
+ expectedMetrics: {
852
+ vmn_udp_min: -1,
853
+ vmn_udp_max: -1,
854
+ vmn_udp_average: -1,
855
+ public_udp_min: -1,
856
+ public_udp_max: -1,
857
+ public_udp_average: -1,
858
+ public_tcp_min: -1,
859
+ public_tcp_max: -1,
860
+ public_tcp_average: -1,
861
+ public_xtls_min: -1,
862
+ public_xtls_max: -1,
863
+ public_xtls_average: -1,
864
+ },
865
+ },
866
+ // ========================================================================
867
+ {
868
+ title:
869
+ '3 clusters: 2 VMN clusters missing results, but the public one has all results within 1s',
870
+ waitShortTimeout: 'vmn',
871
+ waitLongTimeout: true,
872
+ mockClusters: {
873
+ vmnCluster1: {
874
+ udp: ['udp-url1'],
875
+ tcp: ['tcp-url1'],
876
+ xtls: ['xtls-url1'],
877
+ isVideoMesh: true,
878
+ },
879
+ publicCluster: {
880
+ udp: ['udp-url2'],
881
+ tcp: ['tcp-url2'],
882
+ xtls: ['xtls-url2'],
883
+ isVideoMesh: false,
884
+ },
885
+ vmnCluster2: {
886
+ udp: ['udp-url3'],
887
+ tcp: ['tcp-url3'],
888
+ xtls: ['xtls-url3'],
889
+ isVideoMesh: true,
890
+ },
891
+ },
892
+ mockResultReadyEvents: [
893
+ {
894
+ clusterId: 'publicCluster',
895
+ protocol: 'udp',
896
+ result: {
897
+ result: 'reachable',
898
+ clientMediaIPs: ['1.2.3.4'],
899
+ latencyInMilliseconds: 10,
900
+ },
901
+ },
902
+ {
903
+ clusterId: 'publicCluster',
904
+ protocol: 'tcp',
905
+ result: {
906
+ result: 'reachable',
907
+ latencyInMilliseconds: 100,
908
+ },
909
+ },
910
+ {
911
+ clusterId: 'publicCluster',
912
+ protocol: 'xtls',
913
+ result: {
914
+ result: 'reachable',
915
+ latencyInMilliseconds: 200,
916
+ },
917
+ },
918
+ ],
919
+ expectedResults: {
920
+ vmnCluster1: {
921
+ udp: {result: 'unreachable'},
922
+ tcp: {result: 'untested'},
923
+ xtls: {result: 'untested'},
924
+ isVideoMesh: true,
925
+ },
926
+ publicCluster: {
927
+ udp: {result: 'reachable', clientMediaIPs: ['1.2.3.4'], latencyInMilliseconds: 10},
928
+ tcp: {result: 'reachable', latencyInMilliseconds: 100},
929
+ xtls: {result: 'reachable', latencyInMilliseconds: 200},
930
+ isVideoMesh: false,
931
+ },
932
+ vmnCluster2: {
933
+ udp: {result: 'unreachable'},
934
+ tcp: {result: 'untested'},
935
+ xtls: {result: 'untested'},
936
+ isVideoMesh: true,
937
+ },
938
+ },
939
+ expectedMetrics: {
940
+ vmn_udp_min: -1,
941
+ vmn_udp_max: -1,
942
+ vmn_udp_average: -1,
943
+ public_udp_min: 10,
944
+ public_udp_max: 10,
945
+ public_udp_average: 10,
946
+ public_tcp_min: 100,
947
+ public_tcp_max: 100,
948
+ public_tcp_average: 100,
949
+ public_xtls_min: 200,
950
+ public_xtls_max: 200,
951
+ public_xtls_average: 200,
952
+ },
953
+ },
954
+ // ========================================================================
955
+ {
956
+ title: '2 VMN clusters with all results',
957
+ waitShortTimeout: false,
958
+ waitLongTimeout: false,
959
+ mockClusters: {
960
+ vmnCluster1: {
961
+ udp: ['udp-url1'],
962
+ tcp: [],
963
+ xtls: [],
964
+ isVideoMesh: true,
965
+ },
966
+ vmnCluster2: {
967
+ udp: ['udp-url3'],
968
+ tcp: [],
969
+ xtls: [],
970
+ isVideoMesh: true,
971
+ },
972
+ },
973
+ mockResultReadyEvents: [
974
+ {
975
+ clusterId: 'vmnCluster1',
976
+ protocol: 'udp',
977
+ result: {
978
+ result: 'reachable',
979
+ clientMediaIPs: ['192.168.10.1'],
980
+ latencyInMilliseconds: 100,
981
+ },
982
+ },
983
+ {
984
+ clusterId: 'vmnCluster2',
985
+ protocol: 'udp',
986
+ result: {
987
+ result: 'reachable',
988
+ clientMediaIPs: ['192.168.0.1'],
989
+ latencyInMilliseconds: 300,
990
+ },
991
+ },
992
+ ],
993
+ expectedResults: {
994
+ vmnCluster1: {
995
+ udp: {result: 'reachable', clientMediaIPs: ['192.168.10.1'], latencyInMilliseconds: 100},
996
+ tcp: {result: 'untested'},
997
+ xtls: {result: 'untested'},
998
+ isVideoMesh: true,
999
+ },
1000
+ vmnCluster2: {
1001
+ udp: {result: 'reachable', clientMediaIPs: ['192.168.0.1'], latencyInMilliseconds: 300},
1002
+ tcp: {result: 'untested'},
1003
+ xtls: {result: 'untested'},
1004
+ isVideoMesh: true,
1005
+ },
1006
+ },
1007
+ expectedMetrics: {
1008
+ vmn_udp_min: 100,
1009
+ vmn_udp_max: 300,
1010
+ vmn_udp_average: 200,
1011
+ public_udp_min: -1,
1012
+ public_udp_max: -1,
1013
+ public_udp_average: -1,
1014
+ public_tcp_min: -1,
1015
+ public_tcp_max: -1,
1016
+ public_tcp_average: -1,
1017
+ public_xtls_min: -1,
1018
+ public_xtls_max: -1,
1019
+ public_xtls_average: -1,
1020
+ },
1021
+ },
1022
+ ].forEach(
1023
+ ({
1024
+ title,
1025
+ waitShortTimeout,
1026
+ waitLongTimeout,
1027
+ mockClusters,
1028
+ mockResultReadyEvents,
1029
+ expectedResults,
1030
+ expectedMetrics,
1031
+ }) =>
1032
+ it(`works correctly for the case: ${title}`, async () => {
1033
+ webex.config.meetings.experimental = {
1034
+ enableTcpReachability: true,
1035
+ enableTlsReachability: true,
1036
+ };
1037
+
1038
+ const receivedEvents = {
1039
+ done: 0,
1040
+ firstResultAvailable: {
1041
+ udp: 0,
1042
+ tcp: 0,
1043
+ xtls: 0,
1044
+ },
1045
+ };
1046
+
1047
+ const reachability = new Reachability(webex);
1048
+
1049
+ reachability.on('reachability:done', () => {
1050
+ receivedEvents.done += 1;
1051
+ });
1052
+ reachability.on('reachability:firstResultAvailable', ({protocol}) => {
1053
+ receivedEvents.firstResultAvailable[protocol] += 1;
1054
+ });
1055
+
1056
+ const mockGetClustersResult = {
1057
+ clusters: {},
1058
+ joinCookie: {id: 'id'},
1059
+ };
1060
+
1061
+ Object.entries(mockClusters).forEach(([id, mockCluster]) => {
1062
+ mockGetClustersResult.clusters[id] = mockCluster;
1063
+ });
1064
+
1065
+ reachability.reachabilityRequest.getClusters = sinon.stub().returns(mockGetClustersResult);
1066
+
1067
+ const resultPromise = reachability.gatherReachability();
1068
+
1069
+ await testUtils.flushPromises();
1070
+
1071
+ // check that ClusterReachability instance was created for each cluster
1072
+ Object.entries(mockClusters).forEach(([id, mockCluster]) => {
1073
+ assert.calledWith(clusterReachabilityCtorStub, id, mockCluster);
1074
+ });
1075
+
1076
+ // trigger mock result events from ClusterReachability instances
1077
+ mockResultReadyEvents.forEach((mockEvent) => {
1078
+ mockClusterReachabilityInstances[mockEvent.clusterId].emitFakeResult(
1079
+ mockEvent.protocol,
1080
+ mockEvent.result
1081
+ );
1082
+ });
1083
+
1084
+ if (waitShortTimeout === 'public') {
1085
+ clock.tick(3000);
1086
+ }
1087
+ if (waitShortTimeout === 'vmn') {
1088
+ clock.tick(1000);
1089
+ }
1090
+
1091
+ await resultPromise;
1092
+
1093
+ await checkResults(expectedResults, mockGetClustersResult.joinCookie);
1094
+
1095
+ if (waitLongTimeout) {
1096
+ // we need to wait either 14 or 12 seconds to get to the 15s timeout (depending on how much we waited earlier)
1097
+ clock.tick(waitShortTimeout === 'vmn' ? 14000 : 12000);
1098
+
1099
+ // we check the results again after the long timeout - they should be the same
1100
+ await checkResults(expectedResults, mockGetClustersResult.joinCookie);
1101
+ }
1102
+
1103
+ // now check events emitted by Reachability class
1104
+ assert.equal(receivedEvents['done'], 1);
1105
+
1106
+ // if we've mocked at least one event for any protocol, check that we received
1107
+ // firstResultAvailable event for that protocol
1108
+ if (mockResultReadyEvents.filter((event) => event.protocol === 'udp').length > 0) {
1109
+ assert.equal(receivedEvents['firstResultAvailable']['udp'], 1);
1110
+ }
1111
+ if (mockResultReadyEvents.filter((event) => event.protocol === 'tcp').length > 0) {
1112
+ assert.equal(receivedEvents['firstResultAvailable']['tcp'], 1);
1113
+ }
1114
+ if (mockResultReadyEvents.filter((event) => event.protocol === 'xtls').length > 0) {
1115
+ assert.equal(receivedEvents['firstResultAvailable']['xtls'], 1);
1116
+ }
1117
+
1118
+ // finally, check the metrics
1119
+ assert.calledWith(
1120
+ Metrics.sendBehavioralMetric,
1121
+ 'js_sdk_reachability_completed',
1122
+ expectedMetrics
1123
+ );
1124
+ })
1125
+ );
1126
+
1127
+ it('keeps updating reachability results after the 3s public cloud timeout expires', async () => {
1128
+ webex.config.meetings.experimental = {
1129
+ enableTcpReachability: true,
1130
+ enableTlsReachability: true,
1131
+ };
1132
+
433
1133
  const reachability = new Reachability(webex);
434
1134
 
435
- const reachabilityResults = {
1135
+ const mockGetClustersResult = {
436
1136
  clusters: {
437
- clusterId: {
438
- udp: 'testUDP',
1137
+ clusterA: {
1138
+ udp: ['udp-urlA'],
1139
+ tcp: ['tcp-urlA'],
1140
+ xtls: ['xtls-urlA'],
1141
+ isVideoMesh: false,
1142
+ },
1143
+ clusterB: {
1144
+ udp: ['udp-urlB'],
1145
+ tcp: ['tcp-urlB'],
1146
+ xtls: ['xtls-urlB'],
1147
+ isVideoMesh: false,
439
1148
  },
440
1149
  },
441
- };
442
- const getClustersResult = {
443
- clusters: {clusterId: 'cluster'},
444
1150
  joinCookie: {id: 'id'},
445
1151
  };
446
1152
 
447
- reachability.reachabilityRequest.getClusters = sinon.stub().returns(getClustersResult);
448
- (reachability as any).performReachabilityChecks = sinon.stub().returns(reachabilityResults);
1153
+ reachability.reachabilityRequest.getClusters = sinon.stub().returns(mockGetClustersResult);
449
1154
 
450
- const result = await reachability.gatherReachability();
1155
+ const resultPromise = reachability.gatherReachability();
451
1156
 
452
- assert.equal(result, reachabilityResults);
1157
+ await testUtils.flushPromises();
453
1158
 
454
- const storedResultForReachabilityResult = await webex.boundedStorage.get(
455
- 'Reachability',
456
- 'reachability.result'
457
- );
458
- const storedResultForJoinCookie = await webex.boundedStorage.get(
459
- 'Reachability',
460
- 'reachability.joinCookie'
1159
+ // trigger some mock result events from ClusterReachability instances
1160
+ mockClusterReachabilityInstances['clusterA'].emitFakeResult('udp', {
1161
+ result: 'reachable',
1162
+ clientMediaIPs: ['1.2.3.4'],
1163
+ latencyInMilliseconds: 11,
1164
+ });
1165
+ mockClusterReachabilityInstances['clusterB'].emitFakeResult('udp', {
1166
+ result: 'reachable',
1167
+ clientMediaIPs: ['10.20.30.40'],
1168
+ latencyInMilliseconds: 22,
1169
+ });
1170
+
1171
+ clock.tick(3000);
1172
+ await resultPromise;
1173
+
1174
+ // check that the reachability results contain the 2 results from above
1175
+ await checkResults(
1176
+ {
1177
+ clusterA: {
1178
+ udp: {result: 'reachable', clientMediaIPs: ['1.2.3.4'], latencyInMilliseconds: 11},
1179
+ tcp: {result: 'unreachable'},
1180
+ xtls: {result: 'unreachable'},
1181
+ isVideoMesh: false,
1182
+ },
1183
+ clusterB: {
1184
+ udp: {result: 'reachable', clientMediaIPs: ['10.20.30.40'], latencyInMilliseconds: 22},
1185
+ tcp: {result: 'unreachable'},
1186
+ xtls: {result: 'unreachable'},
1187
+ isVideoMesh: false,
1188
+ },
1189
+ },
1190
+ mockGetClustersResult.joinCookie
461
1191
  );
462
1192
 
463
- assert.equal(JSON.stringify(result), storedResultForReachabilityResult);
464
- assert.equal(JSON.stringify(getClustersResult.joinCookie), storedResultForJoinCookie);
1193
+ // now simulate some more "late" results
1194
+ mockClusterReachabilityInstances['clusterA'].emitFakeResult('tcp', {
1195
+ result: 'reachable',
1196
+ latencyInMilliseconds: 101,
1197
+ });
1198
+ mockClusterReachabilityInstances['clusterB'].emitFakeResult('xtls', {
1199
+ result: 'reachable',
1200
+ latencyInMilliseconds: 102,
1201
+ });
1202
+
1203
+ // and wait for the final overall timeout
1204
+ clock.tick(12000);
1205
+
1206
+ // the reachability results should include all results from above (including the late ones)
1207
+ await checkResults(
1208
+ {
1209
+ clusterA: {
1210
+ udp: {result: 'reachable', clientMediaIPs: ['1.2.3.4'], latencyInMilliseconds: 11},
1211
+ tcp: {result: 'reachable', latencyInMilliseconds: 101},
1212
+ xtls: {result: 'unreachable'},
1213
+ isVideoMesh: false,
1214
+ },
1215
+ clusterB: {
1216
+ udp: {result: 'reachable', clientMediaIPs: ['10.20.30.40'], latencyInMilliseconds: 22},
1217
+ tcp: {result: 'unreachable'},
1218
+ xtls: {result: 'reachable', latencyInMilliseconds: 102},
1219
+ isVideoMesh: false,
1220
+ },
1221
+ },
1222
+ mockGetClustersResult.joinCookie
1223
+ );
465
1224
  });
466
1225
 
467
- it('keeps the stored reachability from previous call to gatherReachability if getClusters fails', async () => {
1226
+ it('handles clientMediaIpsUpdated event by updating clientMediaIps in results', async () => {
1227
+ webex.config.meetings.experimental = {
1228
+ enableTcpReachability: true,
1229
+ enableTlsReachability: true,
1230
+ };
1231
+
468
1232
  const reachability = new Reachability(webex);
469
1233
 
470
- const reachabilityResults = {
1234
+ const mockGetClustersResult = {
471
1235
  clusters: {
472
- clusterId: {
473
- udp: 'testUDP',
1236
+ clusterA: {
1237
+ udp: ['udp-urlA'],
1238
+ tcp: ['tcp-urlA'],
1239
+ xtls: ['xtls-urlA'],
1240
+ isVideoMesh: false,
474
1241
  },
475
1242
  },
476
- };
477
- const getClustersResult = {
478
- clusters: {clusterId: 'cluster'},
479
1243
  joinCookie: {id: 'id'},
480
1244
  };
481
1245
 
1246
+ reachability.reachabilityRequest.getClusters = sinon.stub().returns(mockGetClustersResult);
1247
+
1248
+ const resultPromise = reachability.gatherReachability();
1249
+
1250
+ await testUtils.flushPromises();
1251
+
1252
+ // trigger a mock result event
1253
+ mockClusterReachabilityInstances['clusterA'].emitFakeResult('udp', {
1254
+ result: 'reachable',
1255
+ clientMediaIPs: ['64.103.40.20'],
1256
+ latencyInMilliseconds: 11,
1257
+ });
1258
+ // followed by some updates to client media IPs
1259
+ mockClusterReachabilityInstances['clusterA'].emitFakeClientMediaIpUpdate('udp', '64.103.40.21');
1260
+ mockClusterReachabilityInstances['clusterA'].emitFakeClientMediaIpUpdate('udp', '64.103.40.22');
1261
+
1262
+ // wait for the final overall timeout
1263
+ clock.tick(15000);
1264
+ await resultPromise;
1265
+
1266
+ // check that the reachability results contain all the client media ips
1267
+ await checkResults(
1268
+ {
1269
+ clusterA: {
1270
+ udp: {
1271
+ result: 'reachable',
1272
+ clientMediaIPs: ['64.103.40.20', '64.103.40.21', '64.103.40.22'],
1273
+ latencyInMilliseconds: 11,
1274
+ },
1275
+ tcp: {result: 'unreachable'},
1276
+ xtls: {result: 'unreachable'},
1277
+ isVideoMesh: false,
1278
+ },
1279
+ },
1280
+ mockGetClustersResult.joinCookie
1281
+ );
1282
+ });
1283
+
1284
+ it('keeps the stored reachability from previous call to gatherReachability if getClusters fails', async () => {
1285
+ const reachability = new Reachability(webex);
1286
+
482
1287
  reachability.reachabilityRequest.getClusters = sinon.stub().throws();
483
1288
 
484
1289
  const result = await reachability.gatherReachability();
485
1290
 
486
1291
  assert.empty(result);
487
1292
 
488
- const storedResultForReachabilityResult = await webex.boundedStorage.get(
489
- 'Reachability',
490
- 'reachability.result'
491
- );
492
- const storedResultForJoinCookie = await webex.boundedStorage.get(
493
- 'Reachability',
494
- 'reachability.joinCookie'
495
- );
496
-
497
- assert.equal(JSON.stringify({old: 'results'}), storedResultForReachabilityResult);
498
- assert.equal(JSON.stringify({old: 'joinCookie'}), storedResultForJoinCookie);
1293
+ await checkResults({old: 'results'}, {old: 'joinCookie'});
499
1294
  });
500
1295
 
501
1296
  it('keeps the stored reachability from previous call to gatherReachability if performReachabilityChecks fails', async () => {
502
1297
  const reachability = new Reachability(webex);
503
1298
 
504
- const reachabilityResults = {
505
- clusters: {
506
- clusterId: {
507
- udp: 'testUDP',
508
- },
509
- },
510
- };
511
1299
  const getClustersResult = {
512
1300
  clusters: {clusterId: 'cluster'},
513
- joinCookie: {id: 'id'},
1301
+ joinCookie: {id: 'cookie id'},
514
1302
  };
515
1303
 
516
1304
  reachability.reachabilityRequest.getClusters = sinon.stub().returns(getClustersResult);
@@ -520,17 +1308,7 @@ describe('gatherReachability', () => {
520
1308
 
521
1309
  assert.empty(result);
522
1310
 
523
- const storedResultForReachabilityResult = await webex.boundedStorage.get(
524
- 'Reachability',
525
- 'reachability.result'
526
- );
527
- const storedResultForJoinCookie = await webex.boundedStorage.get(
528
- 'Reachability',
529
- 'reachability.joinCookie'
530
- );
531
-
532
- assert.equal(JSON.stringify({old: 'results'}), storedResultForReachabilityResult);
533
- assert.equal(JSON.stringify({old: 'joinCookie'}), storedResultForJoinCookie);
1311
+ await checkResults({old: 'results'}, {id: 'cookie id'});
534
1312
  });
535
1313
 
536
1314
  it('starts ClusterReachability on each media cluster', async () => {
@@ -561,14 +1339,10 @@ describe('gatherReachability', () => {
561
1339
 
562
1340
  reachability.reachabilityRequest.getClusters = sinon.stub().returns(getClustersResult);
563
1341
 
564
- const startStub = sinon.stub().resolves({});
565
- const clusterReachabilityCtorStub = sinon
566
- .stub(ClusterReachabilityModule, 'ClusterReachability')
567
- .callsFake(() => ({
568
- start: startStub,
569
- }));
1342
+ const promise = reachability.gatherReachability();
570
1343
 
571
- await reachability.gatherReachability();
1344
+ await simulateTimeout();
1345
+ await promise;
572
1346
 
573
1347
  assert.calledTwice(clusterReachabilityCtorStub);
574
1348
  assert.calledWith(clusterReachabilityCtorStub, 'cluster 1', {
@@ -585,7 +1359,8 @@ describe('gatherReachability', () => {
585
1359
  isVideoMesh: true,
586
1360
  });
587
1361
 
588
- assert.calledTwice(startStub);
1362
+ assert.calledOnce(mockClusterReachabilityInstances['cluster 1'].start);
1363
+ assert.calledOnce(mockClusterReachabilityInstances['cluster 2'].start);
589
1364
  });
590
1365
 
591
1366
  it('does not do TCP reachability if it is disabled in config', async () => {
@@ -610,13 +1385,9 @@ describe('gatherReachability', () => {
610
1385
 
611
1386
  reachability.reachabilityRequest.getClusters = sinon.stub().returns(getClustersResult);
612
1387
 
613
- const clusterReachabilityCtorStub = sinon
614
- .stub(ClusterReachabilityModule, 'ClusterReachability')
615
- .callsFake(() => ({
616
- start: sinon.stub().resolves({}),
617
- }));
618
-
619
- await reachability.gatherReachability();
1388
+ const promise = reachability.gatherReachability();
1389
+ await simulateTimeout();
1390
+ await promise;
620
1391
 
621
1392
  assert.calledOnceWithExactly(clusterReachabilityCtorStub, 'cluster name', {
622
1393
  isVideoMesh: false,
@@ -648,13 +1419,10 @@ describe('gatherReachability', () => {
648
1419
 
649
1420
  reachability.reachabilityRequest.getClusters = sinon.stub().returns(getClustersResult);
650
1421
 
651
- const clusterReachabilityCtorStub = sinon
652
- .stub(ClusterReachabilityModule, 'ClusterReachability')
653
- .callsFake(() => ({
654
- start: sinon.stub().resolves({}),
655
- }));
1422
+ const promise = reachability.gatherReachability();
656
1423
 
657
- await reachability.gatherReachability();
1424
+ await simulateTimeout();
1425
+ await promise;
658
1426
 
659
1427
  assert.calledOnceWithExactly(clusterReachabilityCtorStub, 'cluster name', {
660
1428
  isVideoMesh: false,
@@ -686,13 +1454,10 @@ describe('gatherReachability', () => {
686
1454
 
687
1455
  reachability.reachabilityRequest.getClusters = sinon.stub().returns(getClustersResult);
688
1456
 
689
- const clusterReachabilityCtorStub = sinon
690
- .stub(ClusterReachabilityModule, 'ClusterReachability')
691
- .callsFake(() => ({
692
- start: sinon.stub().resolves({}),
693
- }));
1457
+ const promise = reachability.gatherReachability();
694
1458
 
695
- await reachability.gatherReachability();
1459
+ await simulateTimeout();
1460
+ await promise;
696
1461
 
697
1462
  assert.calledOnceWithExactly(clusterReachabilityCtorStub, 'cluster name', {
698
1463
  isVideoMesh: false,
@@ -1033,3 +1798,307 @@ describe('getReachabilityMetrics', () => {
1033
1798
  );
1034
1799
  });
1035
1800
  });
1801
+
1802
+ class TestReachability extends Reachability {
1803
+ constructor(webex: object) {
1804
+ super(webex);
1805
+ }
1806
+
1807
+ public testGetStatistics(
1808
+ results: Array<ClusterReachabilityModule.ClusterReachabilityResult & {isVideoMesh: boolean}>,
1809
+ protocol: 'udp' | 'tcp' | 'xtls',
1810
+ isVideoMesh: boolean
1811
+ ) {
1812
+ return this.getStatistics(results, protocol, isVideoMesh);
1813
+ }
1814
+
1815
+ public testSendMetric() {
1816
+ return this.sendMetric();
1817
+ }
1818
+
1819
+ public setFakeClusterReachability(fakeClusterReachability) {
1820
+ this.clusterReachability = fakeClusterReachability;
1821
+ }
1822
+ }
1823
+
1824
+ describe('getStatistics', () => {
1825
+ let webex;
1826
+ let reachability;
1827
+
1828
+ beforeEach(() => {
1829
+ webex = new MockWebex();
1830
+ reachability = new TestReachability(webex);
1831
+ });
1832
+
1833
+ it('takes values from the correct protocol', () => {
1834
+ const results = [
1835
+ {
1836
+ udp: {
1837
+ result: 'reachable',
1838
+ latencyInMilliseconds: 10,
1839
+ },
1840
+ tcp: {
1841
+ result: 'reachable',
1842
+ latencyInMilliseconds: 1010,
1843
+ },
1844
+ xtls: {
1845
+ result: 'reachable',
1846
+ latencyInMilliseconds: 2010,
1847
+ },
1848
+ isVideoMesh: false,
1849
+ },
1850
+ {
1851
+ udp: {
1852
+ result: 'reachable',
1853
+ latencyInMilliseconds: 20,
1854
+ },
1855
+ tcp: {
1856
+ result: 'reachable',
1857
+ latencyInMilliseconds: 1020,
1858
+ },
1859
+ xtls: {
1860
+ result: 'reachable',
1861
+ latencyInMilliseconds: 2020,
1862
+ },
1863
+ isVideoMesh: false,
1864
+ },
1865
+ {
1866
+ udp: {
1867
+ result: 'reachable',
1868
+ latencyInMilliseconds: 30,
1869
+ },
1870
+ tcp: {
1871
+ result: 'reachable',
1872
+ latencyInMilliseconds: 1030,
1873
+ },
1874
+ xtls: {
1875
+ result: 'reachable',
1876
+ latencyInMilliseconds: 2030,
1877
+ },
1878
+ isVideoMesh: false,
1879
+ },
1880
+ ];
1881
+
1882
+ assert.deepEqual(reachability.testGetStatistics(results, 'udp', false), {
1883
+ min: 10,
1884
+ max: 30,
1885
+ average: 20,
1886
+ });
1887
+ assert.deepEqual(reachability.testGetStatistics(results, 'tcp', false), {
1888
+ min: 1010,
1889
+ max: 1030,
1890
+ average: 1020,
1891
+ });
1892
+ assert.deepEqual(reachability.testGetStatistics(results, 'xtls', false), {
1893
+ min: 2010,
1894
+ max: 2030,
1895
+ average: 2020,
1896
+ });
1897
+ });
1898
+
1899
+ it('filters based on isVideoMesh value', () => {
1900
+ const results = [
1901
+ {
1902
+ udp: {
1903
+ result: 'reachable',
1904
+ latencyInMilliseconds: 10,
1905
+ },
1906
+ isVideoMesh: true,
1907
+ },
1908
+ {
1909
+ udp: {
1910
+ result: 'reachable',
1911
+ latencyInMilliseconds: 20,
1912
+ },
1913
+ isVideoMesh: true,
1914
+ },
1915
+ {
1916
+ udp: {
1917
+ result: 'reachable',
1918
+ latencyInMilliseconds: 30,
1919
+ },
1920
+ isVideoMesh: true,
1921
+ },
1922
+ {
1923
+ udp: {
1924
+ result: 'reachable',
1925
+ latencyInMilliseconds: 100,
1926
+ },
1927
+ isVideoMesh: false,
1928
+ },
1929
+ {
1930
+ udp: {
1931
+ result: 'reachable',
1932
+ latencyInMilliseconds: 200,
1933
+ },
1934
+ isVideoMesh: false,
1935
+ },
1936
+ ];
1937
+
1938
+ assert.deepEqual(reachability.testGetStatistics(results, 'udp', true), {
1939
+ min: 10,
1940
+ max: 30,
1941
+ average: 20,
1942
+ });
1943
+ assert.deepEqual(reachability.testGetStatistics(results, 'udp', false), {
1944
+ min: 100,
1945
+ max: 200,
1946
+ average: 150,
1947
+ });
1948
+ });
1949
+
1950
+ it('only takes into account "reachable" results', () => {
1951
+ const results = [
1952
+ {
1953
+ udp: {
1954
+ result: 'reachable',
1955
+ latencyInMilliseconds: 10,
1956
+ },
1957
+ isVideoMesh: false,
1958
+ },
1959
+ {
1960
+ udp: {
1961
+ result: 'unreachable',
1962
+ latencyInMilliseconds: 100, // value put in here just for testing, in practice we wouldn't have any value here if it was unreachable
1963
+ },
1964
+ isVideoMesh: false,
1965
+ },
1966
+ {
1967
+ udp: {
1968
+ result: 'reachable',
1969
+ latencyInMilliseconds: 20,
1970
+ },
1971
+ isVideoMesh: false,
1972
+ },
1973
+ {
1974
+ udp: {
1975
+ result: 'untested',
1976
+ latencyInMilliseconds: 200, // value put in here just for testing, in practice we wouldn't have any value here if it was untested
1977
+ },
1978
+ isVideoMesh: false,
1979
+ },
1980
+ ];
1981
+
1982
+ assert.deepEqual(reachability.testGetStatistics(results, 'udp', false), {
1983
+ min: 10,
1984
+ max: 20,
1985
+ average: 15,
1986
+ });
1987
+ });
1988
+
1989
+ it('handles the case when results are empty', () => {
1990
+ assert.deepEqual(reachability.testGetStatistics([], 'udp', false), {
1991
+ min: -1,
1992
+ max: -1,
1993
+ average: -1,
1994
+ });
1995
+ });
1996
+
1997
+ it('handles the case when results are empty after filtering', () => {
1998
+ const fakeResults = [
1999
+ {
2000
+ udp: {
2001
+ result: 'untested', // it will get filtered out because of this value
2002
+ latencyInMilliseconds: 10,
2003
+ },
2004
+ tcp: {
2005
+ result: 'reachable',
2006
+ latencyInMilliseconds: 10, // it will get filtered out because of the tcp protocol
2007
+ },
2008
+ isVideoMesh: false,
2009
+ },
2010
+ {
2011
+ udp: {
2012
+ result: 'reachable',
2013
+ latencyInMilliseconds: 10,
2014
+ },
2015
+ isVideoMesh: true, // it will get filtered out because of this value
2016
+ },
2017
+ ];
2018
+
2019
+ assert.deepEqual(reachability.testGetStatistics(fakeResults, 'udp', false), {
2020
+ min: -1,
2021
+ max: -1,
2022
+ average: -1,
2023
+ });
2024
+ });
2025
+ });
2026
+
2027
+ describe('sendMetric', () => {
2028
+ let webex;
2029
+ let reachability;
2030
+
2031
+ beforeEach(() => {
2032
+ webex = new MockWebex();
2033
+ reachability = new TestReachability(webex);
2034
+
2035
+ sinon.stub(Metrics, 'sendBehavioralMetric');
2036
+ });
2037
+
2038
+ it('works as expected', async () => {
2039
+ // setup stub for getStatistics to return values that show what parameters it was called with,
2040
+ // this way we can verify that the correct results of calls to getStatistics are placed
2041
+ // in correct data fields when sendBehavioralMetric() is called
2042
+ const getStatisticsStub = sinon
2043
+ .stub(reachability, 'getStatistics')
2044
+ .callsFake((results, protocol, isVideoMesh) => {
2045
+ return {result: 'fake', protocol, isVideoMesh};
2046
+ });
2047
+
2048
+ // setup fake clusterReachability results
2049
+ reachability.setFakeClusterReachability({
2050
+ cluster1: {
2051
+ getResult: sinon.stub().returns({result: 'result 1'}),
2052
+ isVideoMesh: true,
2053
+ },
2054
+ cluster2: {
2055
+ getResult: sinon.stub().returns({result: 'result 2'}),
2056
+ isVideoMesh: false,
2057
+ },
2058
+ cluster3: {
2059
+ getResult: sinon.stub().returns({result: 'result 3'}),
2060
+ isVideoMesh: false,
2061
+ },
2062
+ });
2063
+
2064
+ await reachability.sendMetric();
2065
+
2066
+ // each call to getStatistics should be made with all the results from all fake clusterReachability:
2067
+ const expectedResults = [
2068
+ {
2069
+ result: 'result 1',
2070
+ isVideoMesh: true,
2071
+ },
2072
+ {
2073
+ result: 'result 2',
2074
+ isVideoMesh: false,
2075
+ },
2076
+ {
2077
+ result: 'result 3',
2078
+ isVideoMesh: false,
2079
+ },
2080
+ ];
2081
+
2082
+ // check that getStatistics is called 4 times and each time with all the results
2083
+ assert.callCount(getStatisticsStub, 4);
2084
+ assert.alwaysCalledWith(getStatisticsStub, expectedResults, sinon.match.any, sinon.match.any);
2085
+
2086
+ assert.calledWith(Metrics.sendBehavioralMetric, 'js_sdk_reachability_completed', {
2087
+ vmn_udp_result: 'fake',
2088
+ vmn_udp_protocol: 'udp',
2089
+ vmn_udp_isVideoMesh: true,
2090
+
2091
+ public_udp_result: 'fake',
2092
+ public_udp_protocol: 'udp',
2093
+ public_udp_isVideoMesh: false,
2094
+
2095
+ public_tcp_result: 'fake',
2096
+ public_tcp_protocol: 'tcp',
2097
+ public_tcp_isVideoMesh: false,
2098
+
2099
+ public_xtls_result: 'fake',
2100
+ public_xtls_protocol: 'xtls',
2101
+ public_xtls_isVideoMesh: false,
2102
+ });
2103
+ });
2104
+ });