@webex/internal-plugin-metrics 3.8.1-next.12 → 3.8.1-next.13
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.
- package/dist/call-diagnostic/call-diagnostic-metrics.js +157 -27
- package/dist/call-diagnostic/call-diagnostic-metrics.js.map +1 -1
- package/dist/metrics.js +1 -1
- package/dist/types/call-diagnostic/call-diagnostic-metrics.d.ts +32 -1
- package/package.json +2 -2
- package/src/call-diagnostic/call-diagnostic-metrics.ts +144 -0
- package/test/unit/spec/call-diagnostic/call-diagnostic-metrics.ts +404 -1
|
@@ -105,6 +105,8 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
|
|
|
105
105
|
private delayedClientFeatureEvents: DelayedClientFeatureEvent[] = [];
|
|
106
106
|
private eventErrorCache: WeakMap<any, any> = new WeakMap();
|
|
107
107
|
private isMercuryConnected = false;
|
|
108
|
+
private eventLimitTracker: Map<string, number> = new Map();
|
|
109
|
+
private eventLimitWarningsLogged: Set<string> = new Set();
|
|
108
110
|
|
|
109
111
|
// the default validator before piping an event to the batcher
|
|
110
112
|
// this function can be overridden by the user
|
|
@@ -665,6 +667,144 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
|
|
|
665
667
|
this.eventErrorCache = new WeakMap();
|
|
666
668
|
}
|
|
667
669
|
|
|
670
|
+
/**
|
|
671
|
+
* Checks if an event should be limited based on criteria defined in the event dictionary.
|
|
672
|
+
* Returns true if the event should be sent, false if it has reached its limit.
|
|
673
|
+
* @param event - The diagnostic event object
|
|
674
|
+
* @returns boolean indicating whether the event should be sent
|
|
675
|
+
*/
|
|
676
|
+
private shouldSendEvent({event}: Event): boolean {
|
|
677
|
+
const eventName = event?.name as string;
|
|
678
|
+
const correlationId = event?.identifiers?.correlationId;
|
|
679
|
+
|
|
680
|
+
if (!correlationId || correlationId === 'unknown') {
|
|
681
|
+
return true;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
const limitKeyPrefix = `${eventName}:${correlationId}`;
|
|
685
|
+
|
|
686
|
+
switch (eventName) {
|
|
687
|
+
case 'client.media.render.start':
|
|
688
|
+
case 'client.media.render.stop':
|
|
689
|
+
case 'client.media.rx.start':
|
|
690
|
+
case 'client.media.rx.stop':
|
|
691
|
+
case 'client.media.tx.start':
|
|
692
|
+
case 'client.media.tx.stop': {
|
|
693
|
+
// Send only once per mediaType-correlationId pair (or mediaType-correlationId-shareInstanceId for share/share_audio)
|
|
694
|
+
const mediaType = event?.mediaType;
|
|
695
|
+
if (mediaType) {
|
|
696
|
+
if (mediaType === 'share' || mediaType === 'share_audio') {
|
|
697
|
+
const shareInstanceId = event?.shareInstanceId;
|
|
698
|
+
if (shareInstanceId) {
|
|
699
|
+
const limitKey = `${limitKeyPrefix}:${mediaType}:${shareInstanceId}`;
|
|
700
|
+
|
|
701
|
+
return this.checkAndIncrementEventCount(
|
|
702
|
+
limitKey,
|
|
703
|
+
1,
|
|
704
|
+
`${eventName} for ${mediaType} instance ${shareInstanceId}`
|
|
705
|
+
);
|
|
706
|
+
}
|
|
707
|
+
} else {
|
|
708
|
+
const limitKey = `${limitKeyPrefix}:${mediaType}`;
|
|
709
|
+
|
|
710
|
+
return this.checkAndIncrementEventCount(
|
|
711
|
+
limitKey,
|
|
712
|
+
1,
|
|
713
|
+
`${eventName} for mediaType ${mediaType}`
|
|
714
|
+
);
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
break;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
case 'client.roap-message.received':
|
|
721
|
+
case 'client.roap-message.sent': {
|
|
722
|
+
// Send only once per correlationId and roap.messageType/roap.type
|
|
723
|
+
const roapMessageType = event?.roap?.messageType || event?.roap?.type;
|
|
724
|
+
if (roapMessageType) {
|
|
725
|
+
const limitKey = `${limitKeyPrefix}:${roapMessageType}`;
|
|
726
|
+
|
|
727
|
+
return this.checkAndIncrementEventCount(
|
|
728
|
+
limitKey,
|
|
729
|
+
1,
|
|
730
|
+
`${eventName} for ROAP type ${roapMessageType}`
|
|
731
|
+
);
|
|
732
|
+
}
|
|
733
|
+
break;
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
default:
|
|
737
|
+
return true;
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
return true;
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
/**
|
|
744
|
+
* Checks the current count for a limit key and increments if under limit.
|
|
745
|
+
* @param limitKey - The unique key for this limit combination
|
|
746
|
+
* @param maxCount - Maximum allowed count
|
|
747
|
+
* @param eventDescription - Description for logging
|
|
748
|
+
* @returns true if under limit and incremented, false if at/over limit
|
|
749
|
+
*/
|
|
750
|
+
private checkAndIncrementEventCount(
|
|
751
|
+
limitKey: string,
|
|
752
|
+
maxCount: number,
|
|
753
|
+
eventDescription: string
|
|
754
|
+
): boolean {
|
|
755
|
+
const currentCount = this.eventLimitTracker.get(limitKey) || 0;
|
|
756
|
+
|
|
757
|
+
if (currentCount >= maxCount) {
|
|
758
|
+
// Log warning only once per limit key
|
|
759
|
+
if (!this.eventLimitWarningsLogged.has(limitKey)) {
|
|
760
|
+
this.logger.log(
|
|
761
|
+
CALL_DIAGNOSTIC_LOG_IDENTIFIER,
|
|
762
|
+
`CallDiagnosticMetrics: Event limit reached for ${eventDescription}. ` +
|
|
763
|
+
`Max count ${maxCount} exceeded. Event will not be sent.`,
|
|
764
|
+
`limitKey: ${limitKey}`
|
|
765
|
+
);
|
|
766
|
+
this.eventLimitWarningsLogged.add(limitKey);
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
return false;
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
// Increment count and allow event
|
|
773
|
+
this.eventLimitTracker.set(limitKey, currentCount + 1);
|
|
774
|
+
|
|
775
|
+
return true;
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
/**
|
|
779
|
+
* Clears event limit tracking
|
|
780
|
+
*/
|
|
781
|
+
public clearEventLimits(): void {
|
|
782
|
+
this.eventLimitTracker.clear();
|
|
783
|
+
this.eventLimitWarningsLogged.clear();
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
/**
|
|
787
|
+
* Clears event limit tracking for a specific correlationId only.
|
|
788
|
+
* Keeps limits for other meetings intact.
|
|
789
|
+
*/
|
|
790
|
+
public clearEventLimitsForCorrelationId(correlationId: string): void {
|
|
791
|
+
if (!correlationId) {
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
// Keys are formatted as "eventName:correlationId:..." across all limiters.
|
|
795
|
+
const hasCorrIdAtSecondToken = (key: string) => key.split(':')[1] === correlationId;
|
|
796
|
+
for (const key of Array.from(this.eventLimitTracker.keys())) {
|
|
797
|
+
if (hasCorrIdAtSecondToken(key)) {
|
|
798
|
+
this.eventLimitTracker.delete(key);
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
for (const key of Array.from(this.eventLimitWarningsLogged.values())) {
|
|
802
|
+
if (hasCorrIdAtSecondToken(key)) {
|
|
803
|
+
this.eventLimitWarningsLogged.delete(key);
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
|
|
668
808
|
/**
|
|
669
809
|
* Generate error payload for Client Event
|
|
670
810
|
* @param rawError
|
|
@@ -1099,6 +1239,10 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
|
|
|
1099
1239
|
);
|
|
1100
1240
|
const diagnosticEvent = this.prepareClientEvent({name, payload, options});
|
|
1101
1241
|
|
|
1242
|
+
if (!this.shouldSendEvent(diagnosticEvent)) {
|
|
1243
|
+
return Promise.resolve();
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1102
1246
|
if (options?.preLoginId) {
|
|
1103
1247
|
return this.submitToCallDiagnosticsPreLogin(diagnosticEvent, options?.preLoginId);
|
|
1104
1248
|
}
|
|
@@ -2431,7 +2431,7 @@ describe('internal-plugin-metrics', () => {
|
|
|
2431
2431
|
);
|
|
2432
2432
|
});
|
|
2433
2433
|
|
|
2434
|
-
it('should
|
|
2434
|
+
it('should record failure metric when meetingId is provided but meeting is undefined', () => {
|
|
2435
2435
|
webex.meetings.getBasicMeetingInformation = sinon.stub().returns(undefined);
|
|
2436
2436
|
|
|
2437
2437
|
cd.submitClientEvent({name: 'client.alert.displayed', options: {meetingId: 'meetingId'}});
|
|
@@ -2465,6 +2465,228 @@ describe('internal-plugin-metrics', () => {
|
|
|
2465
2465
|
assert.calledWith(cd.submitToCallDiagnosticsPreLogin, testEvent);
|
|
2466
2466
|
assert.notCalled(cd.submitToCallDiagnostics);
|
|
2467
2467
|
});
|
|
2468
|
+
|
|
2469
|
+
describe('Limiting repeated events', () => {
|
|
2470
|
+
beforeEach(() => {
|
|
2471
|
+
cd.clearEventLimits();
|
|
2472
|
+
});
|
|
2473
|
+
|
|
2474
|
+
const createEventLimitRegex = (eventName: string, eventType: string) => {
|
|
2475
|
+
const escapedEventName = eventName.replace(/\./g, '\\.');
|
|
2476
|
+
return new RegExp(`Event limit reached for ${escapedEventName} for ${eventType}`);
|
|
2477
|
+
};
|
|
2478
|
+
|
|
2479
|
+
it('should always send events that are not in the limiting switch cases', () => {
|
|
2480
|
+
const options = {
|
|
2481
|
+
meetingId: fakeMeeting.id,
|
|
2482
|
+
};
|
|
2483
|
+
const submitToCallDiagnosticsStub = sinon.stub(cd, 'submitToCallDiagnostics');
|
|
2484
|
+
|
|
2485
|
+
const baselineCallCount = webex.logger.log.callCount;
|
|
2486
|
+
cd.submitClientEvent({
|
|
2487
|
+
name: 'client.alert.displayed',
|
|
2488
|
+
options,
|
|
2489
|
+
});
|
|
2490
|
+
|
|
2491
|
+
cd.submitClientEvent({
|
|
2492
|
+
name: 'client.alert.displayed',
|
|
2493
|
+
options,
|
|
2494
|
+
});
|
|
2495
|
+
|
|
2496
|
+
cd.submitClientEvent({
|
|
2497
|
+
name: 'client.alert.displayed',
|
|
2498
|
+
options,
|
|
2499
|
+
});
|
|
2500
|
+
|
|
2501
|
+
assert.calledThrice(submitToCallDiagnosticsStub);
|
|
2502
|
+
});
|
|
2503
|
+
|
|
2504
|
+
([
|
|
2505
|
+
['client.media.render.start'],
|
|
2506
|
+
['client.media.render.stop'],
|
|
2507
|
+
['client.media.rx.start'],
|
|
2508
|
+
['client.media.rx.stop'],
|
|
2509
|
+
['client.media.tx.start'],
|
|
2510
|
+
['client.media.tx.stop']
|
|
2511
|
+
] as const).forEach(([name]) => {
|
|
2512
|
+
it(`should only send ${name} once per mediaType`, () => {
|
|
2513
|
+
const options = {
|
|
2514
|
+
meetingId: fakeMeeting.id,
|
|
2515
|
+
};
|
|
2516
|
+
const payload = {
|
|
2517
|
+
mediaType: 'video' as const,
|
|
2518
|
+
};
|
|
2519
|
+
const submitToCallDiagnosticsStub = sinon.stub(cd, 'submitToCallDiagnostics');
|
|
2520
|
+
|
|
2521
|
+
const baselineCallCount = webex.logger.log.callCount;
|
|
2522
|
+
// Send first event
|
|
2523
|
+
cd.submitClientEvent({
|
|
2524
|
+
name,
|
|
2525
|
+
payload,
|
|
2526
|
+
options,
|
|
2527
|
+
});
|
|
2528
|
+
|
|
2529
|
+
assert.calledOnce(submitToCallDiagnosticsStub);
|
|
2530
|
+
submitToCallDiagnosticsStub.resetHistory();
|
|
2531
|
+
|
|
2532
|
+
// Send second event of same type
|
|
2533
|
+
cd.submitClientEvent({
|
|
2534
|
+
name,
|
|
2535
|
+
payload,
|
|
2536
|
+
options,
|
|
2537
|
+
});
|
|
2538
|
+
|
|
2539
|
+
assert.notCalled(submitToCallDiagnosticsStub);
|
|
2540
|
+
assert.calledWith(
|
|
2541
|
+
webex.logger.log,
|
|
2542
|
+
'call-diagnostic-events -> ',
|
|
2543
|
+
sinon.match(createEventLimitRegex(name, 'mediaType video'))
|
|
2544
|
+
);
|
|
2545
|
+
webex.logger.log.resetHistory();
|
|
2546
|
+
|
|
2547
|
+
// Send third event of same type
|
|
2548
|
+
cd.submitClientEvent({
|
|
2549
|
+
name,
|
|
2550
|
+
payload,
|
|
2551
|
+
options,
|
|
2552
|
+
});
|
|
2553
|
+
|
|
2554
|
+
assert.notCalled(submitToCallDiagnosticsStub);
|
|
2555
|
+
assert.neverCalledWithMatch(webex.logger.log,
|
|
2556
|
+
'call-diagnostic-events -> ',
|
|
2557
|
+
sinon.match(createEventLimitRegex(name, 'mediaType video'))
|
|
2558
|
+
);
|
|
2559
|
+
|
|
2560
|
+
// Send fourth event with a different mediaType
|
|
2561
|
+
cd.submitClientEvent({
|
|
2562
|
+
name,
|
|
2563
|
+
payload: {mediaType: 'audio'},
|
|
2564
|
+
options,
|
|
2565
|
+
});
|
|
2566
|
+
|
|
2567
|
+
assert.calledOnce(submitToCallDiagnosticsStub);
|
|
2568
|
+
});
|
|
2569
|
+
|
|
2570
|
+
it(`should handle share media type with shareInstanceId correctly for ${name}`, () => {
|
|
2571
|
+
const options = {
|
|
2572
|
+
meetingId: fakeMeeting.id,
|
|
2573
|
+
};
|
|
2574
|
+
const payload = {
|
|
2575
|
+
mediaType: 'share' as const,
|
|
2576
|
+
shareInstanceId: 'instance-1',
|
|
2577
|
+
};
|
|
2578
|
+
const submitToCallDiagnosticsStub = sinon.stub(cd, 'submitToCallDiagnostics');
|
|
2579
|
+
|
|
2580
|
+
const baselineCallCount = webex.logger.log.callCount;
|
|
2581
|
+
// Send first event
|
|
2582
|
+
cd.submitClientEvent({
|
|
2583
|
+
name,
|
|
2584
|
+
payload,
|
|
2585
|
+
options,
|
|
2586
|
+
});
|
|
2587
|
+
|
|
2588
|
+
// Send second event with same shareInstanceId
|
|
2589
|
+
cd.submitClientEvent({
|
|
2590
|
+
name,
|
|
2591
|
+
payload,
|
|
2592
|
+
options,
|
|
2593
|
+
});
|
|
2594
|
+
|
|
2595
|
+
// Send event with different shareInstanceId
|
|
2596
|
+
cd.submitClientEvent({
|
|
2597
|
+
name,
|
|
2598
|
+
payload: { ...payload, shareInstanceId: 'instance-2' },
|
|
2599
|
+
options,
|
|
2600
|
+
});
|
|
2601
|
+
|
|
2602
|
+
assert.calledTwice(submitToCallDiagnosticsStub);
|
|
2603
|
+
});
|
|
2604
|
+
});
|
|
2605
|
+
|
|
2606
|
+
([
|
|
2607
|
+
['client.roap-message.received'],
|
|
2608
|
+
['client.roap-message.sent']
|
|
2609
|
+
] as const).forEach(([name]) => {
|
|
2610
|
+
it(`should not send third event of same type and not log warning again for ${name}`, () => {
|
|
2611
|
+
const options = {
|
|
2612
|
+
meetingId: fakeMeeting.id,
|
|
2613
|
+
};
|
|
2614
|
+
const payload = {
|
|
2615
|
+
roap: {
|
|
2616
|
+
messageType: 'OFFER' as const,
|
|
2617
|
+
},
|
|
2618
|
+
};
|
|
2619
|
+
const submitToCallDiagnosticsStub = sinon.stub(cd, 'submitToCallDiagnostics');
|
|
2620
|
+
|
|
2621
|
+
// Clear any existing call history to get accurate counts
|
|
2622
|
+
webex.logger.log.resetHistory();
|
|
2623
|
+
|
|
2624
|
+
// Send first event
|
|
2625
|
+
cd.submitClientEvent({
|
|
2626
|
+
name,
|
|
2627
|
+
payload,
|
|
2628
|
+
options,
|
|
2629
|
+
});
|
|
2630
|
+
|
|
2631
|
+
assert.calledOnce(submitToCallDiagnosticsStub);
|
|
2632
|
+
submitToCallDiagnosticsStub.resetHistory();
|
|
2633
|
+
|
|
2634
|
+
// Send second event (should trigger warning)
|
|
2635
|
+
cd.submitClientEvent({
|
|
2636
|
+
name,
|
|
2637
|
+
payload,
|
|
2638
|
+
options,
|
|
2639
|
+
});
|
|
2640
|
+
|
|
2641
|
+
assert.notCalled(submitToCallDiagnosticsStub);
|
|
2642
|
+
assert.calledWith(
|
|
2643
|
+
webex.logger.log,
|
|
2644
|
+
'call-diagnostic-events -> ',
|
|
2645
|
+
sinon.match(createEventLimitRegex(name, 'ROAP type OFFER'))
|
|
2646
|
+
);
|
|
2647
|
+
webex.logger.log.resetHistory();
|
|
2648
|
+
|
|
2649
|
+
cd.submitClientEvent({
|
|
2650
|
+
name,
|
|
2651
|
+
payload,
|
|
2652
|
+
options,
|
|
2653
|
+
});
|
|
2654
|
+
|
|
2655
|
+
assert.notCalled(submitToCallDiagnosticsStub);
|
|
2656
|
+
assert.neverCalledWithMatch(
|
|
2657
|
+
webex.logger.log,
|
|
2658
|
+
'call-diagnostic-events -> ',
|
|
2659
|
+
sinon.match(createEventLimitRegex(name, 'ROAP type OFFER'))
|
|
2660
|
+
);
|
|
2661
|
+
});
|
|
2662
|
+
|
|
2663
|
+
it(`should handle roap.type instead of roap.messageType for ${name}`, () => {
|
|
2664
|
+
const options = {
|
|
2665
|
+
meetingId: fakeMeeting.id,
|
|
2666
|
+
};
|
|
2667
|
+
const payload = {
|
|
2668
|
+
roap: {
|
|
2669
|
+
type: 'ANSWER' as const,
|
|
2670
|
+
},
|
|
2671
|
+
};
|
|
2672
|
+
const submitToCallDiagnosticsStub = sinon.stub(cd, 'submitToCallDiagnostics');
|
|
2673
|
+
|
|
2674
|
+
cd.submitClientEvent({
|
|
2675
|
+
name,
|
|
2676
|
+
payload,
|
|
2677
|
+
options,
|
|
2678
|
+
});
|
|
2679
|
+
|
|
2680
|
+
cd.submitClientEvent({
|
|
2681
|
+
name,
|
|
2682
|
+
payload,
|
|
2683
|
+
options,
|
|
2684
|
+
});
|
|
2685
|
+
|
|
2686
|
+
assert.calledOnce(submitToCallDiagnosticsStub);
|
|
2687
|
+
});
|
|
2688
|
+
});
|
|
2689
|
+
});
|
|
2468
2690
|
});
|
|
2469
2691
|
|
|
2470
2692
|
describe('#submitToCallDiagnostics', () => {
|
|
@@ -4022,5 +4244,186 @@ describe('internal-plugin-metrics', () => {
|
|
|
4022
4244
|
assert.notCalled(submitFeatureEventSpy);
|
|
4023
4245
|
});
|
|
4024
4246
|
});
|
|
4247
|
+
|
|
4248
|
+
describe('#clearEventLimitsForCorrelationId', () => {
|
|
4249
|
+
beforeEach(() => {
|
|
4250
|
+
cd.clearEventLimits();
|
|
4251
|
+
});
|
|
4252
|
+
|
|
4253
|
+
it('should clear event limits for specific correlationId only', () => {
|
|
4254
|
+
// Use the actual correlationIds from our fakeMeeting fixtures
|
|
4255
|
+
const correlationId1 = fakeMeeting.correlationId; // e.g. 'correlationId1'
|
|
4256
|
+
const correlationId2 = fakeMeeting2.correlationId; // e.g. 'correlationId2'
|
|
4257
|
+
const options1 = { meetingId: fakeMeeting.id };
|
|
4258
|
+
const options2 = { meetingId: fakeMeeting2.id };
|
|
4259
|
+
const payload = { mediaType: 'video' as const };
|
|
4260
|
+
|
|
4261
|
+
// Set up events for both correlations to trigger limits
|
|
4262
|
+
cd.submitClientEvent({ name: 'client.media.render.start', payload, options: options1 });
|
|
4263
|
+
cd.submitClientEvent({ name: 'client.media.render.start', payload, options: options2 });
|
|
4264
|
+
cd.submitClientEvent({ name: 'client.media.render.start', payload, options: options1 });
|
|
4265
|
+
cd.submitClientEvent({ name: 'client.media.render.start', payload, options: options2 });
|
|
4266
|
+
assert.isTrue(cd.eventLimitTracker.size > 0);
|
|
4267
|
+
assert.isTrue(cd.eventLimitWarningsLogged.size > 0);
|
|
4268
|
+
|
|
4269
|
+
// Clear limits for only correlationId1 (present)
|
|
4270
|
+
cd.clearEventLimitsForCorrelationId(correlationId1);
|
|
4271
|
+
|
|
4272
|
+
const remainingTrackerKeys = Array.from(cd.eventLimitTracker.keys());
|
|
4273
|
+
const remainingWarningKeys = Array.from(cd.eventLimitWarningsLogged.keys());
|
|
4274
|
+
|
|
4275
|
+
// Should have no keys with correlationId1
|
|
4276
|
+
assert.isFalse(remainingTrackerKeys.some(key => key.split(':')[1] === correlationId1));
|
|
4277
|
+
assert.isFalse(remainingWarningKeys.some(key => key.split(':')[1] === correlationId1));
|
|
4278
|
+
|
|
4279
|
+
// Should still have keys with correlationId2
|
|
4280
|
+
assert.isTrue(remainingTrackerKeys.some(key => key.split(':')[1] === correlationId2));
|
|
4281
|
+
assert.isTrue(remainingWarningKeys.some(key => key.split(':')[1] === correlationId2));
|
|
4282
|
+
});
|
|
4283
|
+
|
|
4284
|
+
it('should handle empty correlationId gracefully', () => {
|
|
4285
|
+
const options = { meetingId: fakeMeeting.id };
|
|
4286
|
+
const payload = { mediaType: 'video' as const };
|
|
4287
|
+
|
|
4288
|
+
// Set up some tracking data
|
|
4289
|
+
cd.submitClientEvent({
|
|
4290
|
+
name: 'client.media.render.start',
|
|
4291
|
+
payload,
|
|
4292
|
+
options,
|
|
4293
|
+
});
|
|
4294
|
+
|
|
4295
|
+
cd.submitClientEvent({
|
|
4296
|
+
name: 'client.media.render.start',
|
|
4297
|
+
payload,
|
|
4298
|
+
options,
|
|
4299
|
+
});
|
|
4300
|
+
|
|
4301
|
+
const initialTrackerSize = cd.eventLimitTracker.size;
|
|
4302
|
+
const initialWarningsSize = cd.eventLimitWarningsLogged.size;
|
|
4303
|
+
|
|
4304
|
+
// Should not clear anything for empty correlationId
|
|
4305
|
+
cd.clearEventLimitsForCorrelationId('');
|
|
4306
|
+
cd.clearEventLimitsForCorrelationId(null as any);
|
|
4307
|
+
cd.clearEventLimitsForCorrelationId(undefined as any);
|
|
4308
|
+
|
|
4309
|
+
assert.equal(cd.eventLimitTracker.size, initialTrackerSize);
|
|
4310
|
+
assert.equal(cd.eventLimitWarningsLogged.size, initialWarningsSize);
|
|
4311
|
+
});
|
|
4312
|
+
|
|
4313
|
+
it('should handle non-existent correlationId gracefully', () => {
|
|
4314
|
+
const options = { meetingId: fakeMeeting.id };
|
|
4315
|
+
const payload = { mediaType: 'video' as const };
|
|
4316
|
+
|
|
4317
|
+
// Set up some tracking data
|
|
4318
|
+
cd.submitClientEvent({
|
|
4319
|
+
name: 'client.media.render.start',
|
|
4320
|
+
payload,
|
|
4321
|
+
options,
|
|
4322
|
+
});
|
|
4323
|
+
|
|
4324
|
+
const initialTrackerSize = cd.eventLimitTracker.size;
|
|
4325
|
+
const initialWarningsSize = cd.eventLimitWarningsLogged.size;
|
|
4326
|
+
|
|
4327
|
+
// Should not clear anything for non-existent correlationId
|
|
4328
|
+
cd.clearEventLimitsForCorrelationId('nonExistentCorrelationId');
|
|
4329
|
+
|
|
4330
|
+
assert.equal(cd.eventLimitTracker.size, initialTrackerSize);
|
|
4331
|
+
assert.equal(cd.eventLimitWarningsLogged.size, initialWarningsSize);
|
|
4332
|
+
});
|
|
4333
|
+
|
|
4334
|
+
it('should clear multiple event types for the same correlationId', () => {
|
|
4335
|
+
const correlationId = fakeMeeting.correlationId;
|
|
4336
|
+
const options = { meetingId: fakeMeeting.id };
|
|
4337
|
+
const videoPayload = { mediaType: 'video' as const };
|
|
4338
|
+
const audioPayload = { mediaType: 'audio' as const };
|
|
4339
|
+
const roapPayload = { roap: { messageType: 'OFFER' as const } };
|
|
4340
|
+
|
|
4341
|
+
// Set up multiple event types for the same correlation
|
|
4342
|
+
cd.submitClientEvent({
|
|
4343
|
+
name: 'client.media.render.start',
|
|
4344
|
+
payload: videoPayload,
|
|
4345
|
+
options,
|
|
4346
|
+
});
|
|
4347
|
+
|
|
4348
|
+
cd.submitClientEvent({
|
|
4349
|
+
name: 'client.media.render.start',
|
|
4350
|
+
payload: audioPayload,
|
|
4351
|
+
options,
|
|
4352
|
+
});
|
|
4353
|
+
|
|
4354
|
+
cd.submitClientEvent({
|
|
4355
|
+
name: 'client.roap-message.sent',
|
|
4356
|
+
payload: roapPayload,
|
|
4357
|
+
options,
|
|
4358
|
+
});
|
|
4359
|
+
|
|
4360
|
+
// Trigger limits
|
|
4361
|
+
cd.submitClientEvent({
|
|
4362
|
+
name: 'client.media.render.start',
|
|
4363
|
+
payload: videoPayload,
|
|
4364
|
+
options,
|
|
4365
|
+
});
|
|
4366
|
+
|
|
4367
|
+
cd.submitClientEvent({
|
|
4368
|
+
name: 'client.media.render.start',
|
|
4369
|
+
payload: audioPayload,
|
|
4370
|
+
options,
|
|
4371
|
+
});
|
|
4372
|
+
|
|
4373
|
+
cd.submitClientEvent({
|
|
4374
|
+
name: 'client.roap-message.sent',
|
|
4375
|
+
payload: roapPayload,
|
|
4376
|
+
options,
|
|
4377
|
+
});
|
|
4378
|
+
|
|
4379
|
+
assert.isTrue(cd.eventLimitTracker.size > 0);
|
|
4380
|
+
assert.isTrue(cd.eventLimitWarningsLogged.size > 0);
|
|
4381
|
+
|
|
4382
|
+
// Clear all limits for this correlationId
|
|
4383
|
+
cd.clearEventLimitsForCorrelationId(correlationId);
|
|
4384
|
+
|
|
4385
|
+
// Should clear all tracking data for this correlationId
|
|
4386
|
+
assert.equal(cd.eventLimitTracker.size, 0);
|
|
4387
|
+
assert.equal(cd.eventLimitWarningsLogged.size, 0);
|
|
4388
|
+
});
|
|
4389
|
+
|
|
4390
|
+
it('should allow events to be sent again after clearing limits for correlationId', () => {
|
|
4391
|
+
const correlationId = fakeMeeting.correlationId;
|
|
4392
|
+
const options = { meetingId: fakeMeeting.id };
|
|
4393
|
+
const payload = { mediaType: 'video' as const };
|
|
4394
|
+
const submitToCallDiagnosticsStub = sinon.stub(cd, 'submitToCallDiagnostics');
|
|
4395
|
+
|
|
4396
|
+
// Send first event (should succeed)
|
|
4397
|
+
cd.submitClientEvent({
|
|
4398
|
+
name: 'client.media.render.start',
|
|
4399
|
+
payload,
|
|
4400
|
+
options,
|
|
4401
|
+
});
|
|
4402
|
+
|
|
4403
|
+
assert.calledOnce(submitToCallDiagnosticsStub);
|
|
4404
|
+
submitToCallDiagnosticsStub.resetHistory();
|
|
4405
|
+
|
|
4406
|
+
// Send second event (should be blocked)
|
|
4407
|
+
cd.submitClientEvent({
|
|
4408
|
+
name: 'client.media.render.start',
|
|
4409
|
+
payload,
|
|
4410
|
+
options,
|
|
4411
|
+
});
|
|
4412
|
+
|
|
4413
|
+
assert.notCalled(submitToCallDiagnosticsStub);
|
|
4414
|
+
|
|
4415
|
+
// Clear limits for this correlationId
|
|
4416
|
+
cd.clearEventLimitsForCorrelationId(correlationId);
|
|
4417
|
+
|
|
4418
|
+
// Send event again (should succeed after clearing)
|
|
4419
|
+
cd.submitClientEvent({
|
|
4420
|
+
name: 'client.media.render.start',
|
|
4421
|
+
payload,
|
|
4422
|
+
options,
|
|
4423
|
+
});
|
|
4424
|
+
|
|
4425
|
+
assert.calledOnce(submitToCallDiagnosticsStub);
|
|
4426
|
+
});
|
|
4427
|
+
});
|
|
4025
4428
|
});
|
|
4026
4429
|
});
|