@@ -6,6 +6,8 @@ import * as tsSdpModule from '@webex/ts-sdp';
66import MediaProperties from '@webex/plugin-meetings/src/media/properties' ;
77import { Defer } from '@webex/common' ;
88import MediaConnectionAwaiter from '../../../../src/media/MediaConnectionAwaiter' ;
9+ import Metrics from '../../../../src/metrics' ;
10+ import BEHAVIORAL_METRICS from '../../../../src/metrics/constants' ;
911
1012describe ( 'MediaProperties' , ( ) => {
1113 let mediaProperties ;
@@ -389,4 +391,139 @@ describe('MediaProperties', () => {
389391 } ) ;
390392 } ) ;
391393 } ) ;
394+
395+ // issue types and subtypes used in these tests are just examples
396+ // they don't reflect real issue types/subtypes used in production
397+ describe ( 'sendMediaIssueMetric' , ( ) => {
398+ let sendBehavioralMetricStub ;
399+ let clock ;
400+
401+ beforeEach ( ( ) => {
402+ clock = sinon . useFakeTimers ( ) ;
403+ sendBehavioralMetricStub = sinon . stub ( Metrics , 'sendBehavioralMetric' ) ;
404+ } ) ;
405+
406+ afterEach ( ( ) => {
407+ clock . restore ( ) ;
408+ } ) ;
409+
410+ it ( 'should send a behavioral metric with correct parameters' , ( ) => {
411+ const issueType = 'audio' ;
412+ const issueSubType = 'packet-loss' ;
413+ const correlationId = 'test-correlation-id-123' ;
414+
415+ mediaProperties . sendMediaIssueMetric ( issueType , issueSubType , correlationId ) ;
416+
417+ assert . calledOnce ( sendBehavioralMetricStub ) ;
418+ assert . calledWith ( sendBehavioralMetricStub , BEHAVIORAL_METRICS . MEDIA_ISSUE_DETECTED , {
419+ correlationId,
420+ 'audio_packet-loss' : 1 ,
421+ } ) ;
422+ } ) ;
423+
424+ it ( 'should increment count while being throttled and reset it once metric goes out' , ( ) => {
425+ const issueType = 'video' ;
426+ const issueSubType = 'freeze' ;
427+ const correlationId = 'test-correlation-id' ;
428+
429+ // Call multiple times with same issue type/subtype
430+ mediaProperties . sendMediaIssueMetric ( issueType , issueSubType , correlationId ) ;
431+ mediaProperties . sendMediaIssueMetric ( issueType , issueSubType , correlationId ) ;
432+ mediaProperties . sendMediaIssueMetric ( issueType , issueSubType , correlationId ) ;
433+
434+ // First call should go through immediately, subsequent calls are throttled
435+ assert . calledOnce ( sendBehavioralMetricStub ) ;
436+ assert . calledWith ( sendBehavioralMetricStub , BEHAVIORAL_METRICS . MEDIA_ISSUE_DETECTED , {
437+ correlationId,
438+ video_freeze : 1 , // Only the first call goes through due to throttling
439+ } ) ;
440+ sendBehavioralMetricStub . resetHistory ( ) ;
441+
442+ assert . equal ( mediaProperties . mediaIssueCounters [ 'video_freeze' ] , 2 ) ; // counter should be reset after the first metric goes out, hence only 2 not 3 here
443+
444+ clock . tick ( 5 * 60 * 1000 ) ; // Advance time by 5 minutes to expire throttle
445+
446+ assert . calledOnceWithExactly (
447+ sendBehavioralMetricStub ,
448+ BEHAVIORAL_METRICS . MEDIA_ISSUE_DETECTED ,
449+ {
450+ correlationId,
451+ video_freeze : 2 ,
452+ }
453+ ) ;
454+ } ) ;
455+
456+ it ( 'should track different issue types separately in counters' , ( ) => {
457+ const correlationId = 'test-correlation-id' ;
458+
459+ // Send different issue types
460+ mediaProperties . sendMediaIssueMetric ( 'audio' , 'packet-loss' , correlationId ) ;
461+ mediaProperties . sendMediaIssueMetric ( 'video' , 'freeze' , correlationId ) ;
462+ mediaProperties . sendMediaIssueMetric ( 'audio' , 'packet-loss' , correlationId ) ;
463+ mediaProperties . sendMediaIssueMetric ( 'audio' , 'packet-loss' , correlationId ) ;
464+ mediaProperties . sendMediaIssueMetric ( 'audio' , 'packet-loss' , correlationId ) ;
465+ mediaProperties . sendMediaIssueMetric ( 'video' , 'freeze' , correlationId ) ;
466+
467+ // First call should go through immediately, subsequent calls are throttled
468+ assert . calledOnceWithExactly (
469+ sendBehavioralMetricStub ,
470+ BEHAVIORAL_METRICS . MEDIA_ISSUE_DETECTED ,
471+ {
472+ correlationId,
473+ 'audio_packet-loss' : 1 ,
474+ }
475+ ) ;
476+
477+ // But the counters should be tracked separately
478+ assert . equal ( mediaProperties . mediaIssueCounters [ 'audio_packet-loss' ] , 3 ) ;
479+ assert . equal ( mediaProperties . mediaIssueCounters [ 'video_freeze' ] , 2 ) ;
480+
481+ sendBehavioralMetricStub . resetHistory ( ) ;
482+
483+ clock . tick ( 5 * 60 * 1000 ) ; // Advance time by 5 minutes to expire throttle
484+
485+ assert . calledOnceWithExactly (
486+ sendBehavioralMetricStub ,
487+ BEHAVIORAL_METRICS . MEDIA_ISSUE_DETECTED ,
488+ {
489+ correlationId,
490+ video_freeze : 2 ,
491+ 'audio_packet-loss' : 3 ,
492+ }
493+ ) ;
494+ } ) ;
495+
496+ it ( 'should flush throttled metrics when unsetPeerConnection is called' , ( ) => {
497+ const issueType = 'share' ;
498+ const issueSubType = 'connection-lost' ;
499+ const correlationId = 'test-correlation-id' ;
500+
501+ // Send metrics multiple times
502+ mediaProperties . sendMediaIssueMetric ( issueType , issueSubType , correlationId ) ;
503+ mediaProperties . sendMediaIssueMetric ( issueType , issueSubType , correlationId ) ;
504+
505+ // First call should go through immediately
506+ assert . calledOnceWithExactly (
507+ sendBehavioralMetricStub ,
508+ BEHAVIORAL_METRICS . MEDIA_ISSUE_DETECTED ,
509+ {
510+ correlationId,
511+ 'share_connection-lost' : 1 ,
512+ }
513+ ) ;
514+ sendBehavioralMetricStub . resetHistory ( ) ;
515+
516+ // Call unsetPeerConnection which should flush throttled metrics
517+ mediaProperties . unsetPeerConnection ( ) ;
518+
519+ assert . calledOnceWithExactly (
520+ sendBehavioralMetricStub ,
521+ BEHAVIORAL_METRICS . MEDIA_ISSUE_DETECTED ,
522+ {
523+ correlationId,
524+ 'share_connection-lost' : 1 ,
525+ }
526+ ) ;
527+ } ) ;
528+ } ) ;
392529} ) ;
0 commit comments