@webex/plugin-meetings 3.3.1-next.2 → 3.3.1-next.21
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/breakouts/breakout.js +1 -1
- package/dist/breakouts/index.js +7 -2
- package/dist/breakouts/index.js.map +1 -1
- package/dist/interpretation/index.js +1 -1
- package/dist/interpretation/siLanguage.js +1 -1
- package/dist/media/MediaConnectionAwaiter.js +50 -13
- package/dist/media/MediaConnectionAwaiter.js.map +1 -1
- package/dist/mediaQualityMetrics/config.js +16 -6
- package/dist/mediaQualityMetrics/config.js.map +1 -1
- package/dist/meeting/connectionStateHandler.js +67 -0
- package/dist/meeting/connectionStateHandler.js.map +1 -0
- package/dist/meeting/index.js +98 -46
- package/dist/meeting/index.js.map +1 -1
- package/dist/metrics/constants.js +2 -1
- package/dist/metrics/constants.js.map +1 -1
- package/dist/metrics/index.js +57 -0
- package/dist/metrics/index.js.map +1 -1
- package/dist/reachability/clusterReachability.js +108 -53
- package/dist/reachability/clusterReachability.js.map +1 -1
- package/dist/reachability/index.js +415 -56
- package/dist/reachability/index.js.map +1 -1
- package/dist/statsAnalyzer/index.js +81 -27
- package/dist/statsAnalyzer/index.js.map +1 -1
- package/dist/statsAnalyzer/mqaUtil.js +36 -10
- package/dist/statsAnalyzer/mqaUtil.js.map +1 -1
- package/dist/types/media/MediaConnectionAwaiter.d.ts +18 -4
- package/dist/types/mediaQualityMetrics/config.d.ts +11 -0
- package/dist/types/meeting/connectionStateHandler.d.ts +30 -0
- package/dist/types/meeting/index.d.ts +2 -0
- package/dist/types/metrics/constants.d.ts +1 -0
- package/dist/types/metrics/index.d.ts +15 -0
- package/dist/types/reachability/clusterReachability.d.ts +31 -3
- package/dist/types/reachability/index.d.ts +93 -2
- package/dist/types/statsAnalyzer/index.d.ts +15 -6
- package/dist/types/statsAnalyzer/mqaUtil.d.ts +17 -4
- package/dist/webinar/index.js +1 -1
- package/package.json +23 -22
- package/src/breakouts/index.ts +7 -1
- package/src/media/MediaConnectionAwaiter.ts +66 -11
- package/src/mediaQualityMetrics/config.ts +14 -3
- package/src/meeting/connectionStateHandler.ts +65 -0
- package/src/meeting/index.ts +72 -14
- package/src/metrics/constants.ts +1 -0
- package/src/metrics/index.ts +44 -0
- package/src/reachability/clusterReachability.ts +86 -25
- package/src/reachability/index.ts +316 -27
- package/src/statsAnalyzer/index.ts +85 -24
- package/src/statsAnalyzer/mqaUtil.ts +55 -7
- package/test/unit/spec/breakouts/index.ts +51 -32
- package/test/unit/spec/media/MediaConnectionAwaiter.ts +90 -32
- package/test/unit/spec/meeting/connectionStateHandler.ts +102 -0
- package/test/unit/spec/meeting/index.js +158 -36
- package/test/unit/spec/metrics/index.js +126 -0
- package/test/unit/spec/reachability/clusterReachability.ts +116 -22
- package/test/unit/spec/reachability/index.ts +1153 -84
- package/test/unit/spec/stats-analyzer/index.js +647 -319
|
@@ -2216,7 +2216,7 @@ describe('plugin-meetings', () => {
|
|
|
2216
2216
|
turnDiscoverySkippedReason: undefined,
|
|
2217
2217
|
});
|
|
2218
2218
|
meeting.meetingState = 'ACTIVE';
|
|
2219
|
-
meeting.mediaProperties.waitForMediaConnectionConnected.rejects(
|
|
2219
|
+
meeting.mediaProperties.waitForMediaConnectionConnected.rejects({iceConnected: false});
|
|
2220
2220
|
|
|
2221
2221
|
const forceRtcMetricsSend = sinon.stub().resolves();
|
|
2222
2222
|
const closeMediaConnectionStub = sinon.stub();
|
|
@@ -2240,12 +2240,12 @@ describe('plugin-meetings', () => {
|
|
|
2240
2240
|
assert.calledTwice(generateClientErrorCodeForIceFailureStub);
|
|
2241
2241
|
assert.calledWith(generateClientErrorCodeForIceFailureStub, {
|
|
2242
2242
|
signalingState: 'unknown',
|
|
2243
|
-
|
|
2243
|
+
iceConnected: false,
|
|
2244
2244
|
turnServerUsed: false,
|
|
2245
2245
|
});
|
|
2246
2246
|
assert.calledWith(generateClientErrorCodeForIceFailureStub, {
|
|
2247
2247
|
signalingState: 'unknown',
|
|
2248
|
-
|
|
2248
|
+
iceConnected: false,
|
|
2249
2249
|
turnServerUsed: true,
|
|
2250
2250
|
});
|
|
2251
2251
|
|
|
@@ -2440,7 +2440,7 @@ describe('plugin-meetings', () => {
|
|
|
2440
2440
|
assert.calledOnce(generateClientErrorCodeForIceFailureStub);
|
|
2441
2441
|
assert.calledWith(generateClientErrorCodeForIceFailureStub, {
|
|
2442
2442
|
signalingState: 'unknown',
|
|
2443
|
-
|
|
2443
|
+
iceConnected: undefined,
|
|
2444
2444
|
turnServerUsed: false,
|
|
2445
2445
|
});
|
|
2446
2446
|
|
|
@@ -2715,7 +2715,7 @@ describe('plugin-meetings', () => {
|
|
|
2715
2715
|
turnDiscoverySkippedReason: undefined,
|
|
2716
2716
|
});
|
|
2717
2717
|
meeting.meetingState = 'ACTIVE';
|
|
2718
|
-
meeting.mediaProperties.waitForMediaConnectionConnected.rejects(
|
|
2718
|
+
meeting.mediaProperties.waitForMediaConnectionConnected.rejects({iceConnected: false});
|
|
2719
2719
|
|
|
2720
2720
|
const forceRtcMetricsSend = sinon.stub().resolves();
|
|
2721
2721
|
const closeMediaConnectionStub = sinon.stub();
|
|
@@ -2762,6 +2762,66 @@ describe('plugin-meetings', () => {
|
|
|
2762
2762
|
assert.isOk(errorThrown);
|
|
2763
2763
|
});
|
|
2764
2764
|
|
|
2765
|
+
it('should send ICE_CANDIDATE_ERROR metric if media connection fails and ice candidate errors have been gathered', async () => {
|
|
2766
|
+
let errorThrown = undefined;
|
|
2767
|
+
|
|
2768
|
+
meeting.roap.doTurnDiscovery = sinon.stub().returns({
|
|
2769
|
+
turnServerInfo: undefined,
|
|
2770
|
+
turnDiscoverySkippedReason: undefined,
|
|
2771
|
+
});
|
|
2772
|
+
meeting.meetingState = 'ACTIVE';
|
|
2773
|
+
meeting.mediaProperties.waitForMediaConnectionConnected.rejects({iceConnected: false});
|
|
2774
|
+
|
|
2775
|
+
const forceRtcMetricsSend = sinon.stub().resolves();
|
|
2776
|
+
const closeMediaConnectionStub = sinon.stub();
|
|
2777
|
+
Media.createMediaConnection = sinon.stub().returns({
|
|
2778
|
+
close: closeMediaConnectionStub,
|
|
2779
|
+
forceRtcMetricsSend,
|
|
2780
|
+
getConnectionState: sinon.stub().returns(ConnectionState.Connected),
|
|
2781
|
+
initiateOffer: sinon.stub().resolves({}),
|
|
2782
|
+
on: sinon.stub(),
|
|
2783
|
+
});
|
|
2784
|
+
|
|
2785
|
+
meeting.iceCandidateErrors.set('701_error', 2);
|
|
2786
|
+
meeting.iceCandidateErrors.set('701_turn_host_lookup_received_error', 1);
|
|
2787
|
+
|
|
2788
|
+
await meeting
|
|
2789
|
+
.addMedia({
|
|
2790
|
+
mediaSettings: {},
|
|
2791
|
+
})
|
|
2792
|
+
.catch((err) => {
|
|
2793
|
+
errorThrown = err;
|
|
2794
|
+
assert.instanceOf(err, AddMediaFailed);
|
|
2795
|
+
});
|
|
2796
|
+
|
|
2797
|
+
// Check that the only metric sent is ADD_MEDIA_FAILURE
|
|
2798
|
+
assert.calledOnceWithExactly(
|
|
2799
|
+
Metrics.sendBehavioralMetric,
|
|
2800
|
+
BEHAVIORAL_METRICS.ADD_MEDIA_FAILURE,
|
|
2801
|
+
{
|
|
2802
|
+
correlation_id: meeting.correlationId,
|
|
2803
|
+
locus_id: meeting.locusUrl.split('/').pop(),
|
|
2804
|
+
reason: errorThrown.message,
|
|
2805
|
+
stack: errorThrown.stack,
|
|
2806
|
+
code: errorThrown.code,
|
|
2807
|
+
turnDiscoverySkippedReason: undefined,
|
|
2808
|
+
turnServerUsed: true,
|
|
2809
|
+
retriedWithTurnServer: false,
|
|
2810
|
+
isMultistream: false,
|
|
2811
|
+
isJoinWithMediaRetry: false,
|
|
2812
|
+
signalingState: 'unknown',
|
|
2813
|
+
connectionState: 'unknown',
|
|
2814
|
+
iceConnectionState: 'unknown',
|
|
2815
|
+
selectedCandidatePairChanges: 2,
|
|
2816
|
+
numTransports: 1,
|
|
2817
|
+
'701_error': 2,
|
|
2818
|
+
'701_turn_host_lookup_received_error': 1
|
|
2819
|
+
}
|
|
2820
|
+
);
|
|
2821
|
+
|
|
2822
|
+
assert.isOk(errorThrown);
|
|
2823
|
+
});
|
|
2824
|
+
|
|
2765
2825
|
describe('handles StatsAnalyzer events', () => {
|
|
2766
2826
|
let prevConfigValue;
|
|
2767
2827
|
let statsAnalyzerStub;
|
|
@@ -3058,7 +3118,7 @@ describe('plugin-meetings', () => {
|
|
|
3058
3118
|
|
|
3059
3119
|
meeting.meetingState = 'ACTIVE';
|
|
3060
3120
|
meeting.mediaProperties.waitForMediaConnectionConnected.rejects(
|
|
3061
|
-
|
|
3121
|
+
{iceConnected: false}
|
|
3062
3122
|
);
|
|
3063
3123
|
|
|
3064
3124
|
let errorThrown = false;
|
|
@@ -3073,7 +3133,7 @@ describe('plugin-meetings', () => {
|
|
|
3073
3133
|
|
|
3074
3134
|
assert.calledOnceWithExactly(generateClientErrorCodeForIceFailureStub, {
|
|
3075
3135
|
signalingState: 'unknown',
|
|
3076
|
-
|
|
3136
|
+
iceConnected: false,
|
|
3077
3137
|
turnServerUsed: true,
|
|
3078
3138
|
});
|
|
3079
3139
|
|
|
@@ -6275,7 +6335,7 @@ describe('plugin-meetings', () => {
|
|
|
6275
6335
|
},
|
|
6276
6336
|
'SELF_OBSERVING'
|
|
6277
6337
|
);
|
|
6278
|
-
|
|
6338
|
+
|
|
6279
6339
|
|
|
6280
6340
|
// Verify that the event handler behaves as expected
|
|
6281
6341
|
expect(meeting.statsAnalyzer.stopAnalyzer.calledOnce).to.be.true;
|
|
@@ -7079,6 +7139,10 @@ describe('plugin-meetings', () => {
|
|
|
7079
7139
|
id: 'stream',
|
|
7080
7140
|
getTracks: () => [{id: 'track', addEventListener: sinon.stub()}],
|
|
7081
7141
|
};
|
|
7142
|
+
const simulateConnectionStateChange = (newState) => {
|
|
7143
|
+
meeting.mediaProperties.webrtcMediaConnection.getConnectionState = sinon.stub().returns(newState);
|
|
7144
|
+
eventListeners[Event.PEER_CONNECTION_STATE_CHANGED]();
|
|
7145
|
+
}
|
|
7082
7146
|
|
|
7083
7147
|
beforeEach(() => {
|
|
7084
7148
|
eventListeners = {};
|
|
@@ -7088,6 +7152,7 @@ describe('plugin-meetings', () => {
|
|
|
7088
7152
|
on: sinon.stub().callsFake((event, listener) => {
|
|
7089
7153
|
eventListeners[event] = listener;
|
|
7090
7154
|
}),
|
|
7155
|
+
getConnectionState: sinon.stub().returns(ConnectionState.New),
|
|
7091
7156
|
};
|
|
7092
7157
|
MediaUtil.createMediaStream.returns(fakeStream);
|
|
7093
7158
|
});
|
|
@@ -7099,7 +7164,9 @@ describe('plugin-meetings', () => {
|
|
|
7099
7164
|
assert.isFunction(eventListeners[Event.ROAP_FAILURE]);
|
|
7100
7165
|
assert.isFunction(eventListeners[Event.ROAP_MESSAGE_TO_SEND]);
|
|
7101
7166
|
assert.isFunction(eventListeners[Event.REMOTE_TRACK_ADDED]);
|
|
7102
|
-
assert.isFunction(eventListeners[Event.
|
|
7167
|
+
assert.isFunction(eventListeners[Event.PEER_CONNECTION_STATE_CHANGED]);
|
|
7168
|
+
assert.isFunction(eventListeners[Event.ICE_CONNECTION_STATE_CHANGED]);
|
|
7169
|
+
assert.isFunction(eventListeners[Event.ICE_CANDIDATE_ERROR]);
|
|
7103
7170
|
});
|
|
7104
7171
|
|
|
7105
7172
|
it('should trigger a media:ready event when REMOTE_TRACK_ADDED is fired', () => {
|
|
@@ -7135,13 +7202,45 @@ describe('plugin-meetings', () => {
|
|
|
7135
7202
|
});
|
|
7136
7203
|
});
|
|
7137
7204
|
|
|
7205
|
+
describe('should react on a ICE_CANDIDATE_ERROR event', () => {
|
|
7206
|
+
beforeEach(() => {
|
|
7207
|
+
meeting.setupMediaConnectionListeners();
|
|
7208
|
+
|
|
7209
|
+
});
|
|
7210
|
+
|
|
7211
|
+
it('should not collect skipped ice candidates error', () => {
|
|
7212
|
+
eventListeners[Event.ICE_CANDIDATE_ERROR]({error: { errorCode: 600, errorText: 'Address not associated with the desired network interface.' }});
|
|
7213
|
+
|
|
7214
|
+
assert.equal(meeting.iceCandidateErrors.size, 0);
|
|
7215
|
+
});
|
|
7216
|
+
|
|
7217
|
+
it('should collect valid ice candidates error', () => {
|
|
7218
|
+
eventListeners[Event.ICE_CANDIDATE_ERROR]({error: { errorCode: 701, errorText: '' }});
|
|
7219
|
+
|
|
7220
|
+
assert.equal(meeting.iceCandidateErrors.size, 1);
|
|
7221
|
+
assert.equal(meeting.iceCandidateErrors.has('701_'), true);
|
|
7222
|
+
});
|
|
7223
|
+
|
|
7224
|
+
it('should increment counter if same valid ice candidates error collected', () => {
|
|
7225
|
+
eventListeners[Event.ICE_CANDIDATE_ERROR]({error: { errorCode: 701, errorText: '' }});
|
|
7226
|
+
|
|
7227
|
+
eventListeners[Event.ICE_CANDIDATE_ERROR]({error: { errorCode: 701, errorText: 'STUN host lookup received error.' }});
|
|
7228
|
+
eventListeners[Event.ICE_CANDIDATE_ERROR]({error: { errorCode: 701, errorText: 'STUN host lookup received error.' }});
|
|
7229
|
+
|
|
7230
|
+
assert.equal(meeting.iceCandidateErrors.size, 2);
|
|
7231
|
+
assert.equal(meeting.iceCandidateErrors.has('701_'), true);
|
|
7232
|
+
assert.equal(meeting.iceCandidateErrors.get('701_'), 1);
|
|
7233
|
+
assert.equal(meeting.iceCandidateErrors.has('701_stun_host_lookup_received_error'), true);
|
|
7234
|
+
assert.equal(meeting.iceCandidateErrors.get('701_stun_host_lookup_received_error'), 2);
|
|
7235
|
+
});
|
|
7236
|
+
});
|
|
7237
|
+
|
|
7138
7238
|
describe('CONNECTION_STATE_CHANGED event when state = "Connecting"', () => {
|
|
7139
7239
|
it('sends client.ice.start correctly when hasMediaConnectionConnectedAtLeastOnce = true', () => {
|
|
7140
7240
|
meeting.hasMediaConnectionConnectedAtLeastOnce = true;
|
|
7141
7241
|
meeting.setupMediaConnectionListeners();
|
|
7142
|
-
|
|
7143
|
-
|
|
7144
|
-
});
|
|
7242
|
+
|
|
7243
|
+
simulateConnectionStateChange(ConnectionState.Connecting);
|
|
7145
7244
|
|
|
7146
7245
|
assert.notCalled(webex.internal.newMetrics.submitClientEvent);
|
|
7147
7246
|
});
|
|
@@ -7149,9 +7248,8 @@ describe('plugin-meetings', () => {
|
|
|
7149
7248
|
it('sends client.ice.start correctly when hasMediaConnectionConnectedAtLeastOnce = false', () => {
|
|
7150
7249
|
meeting.hasMediaConnectionConnectedAtLeastOnce = false;
|
|
7151
7250
|
meeting.setupMediaConnectionListeners();
|
|
7152
|
-
|
|
7153
|
-
|
|
7154
|
-
});
|
|
7251
|
+
|
|
7252
|
+
simulateConnectionStateChange(ConnectionState.Connecting);
|
|
7155
7253
|
|
|
7156
7254
|
assert.calledOnce(webex.internal.newMetrics.submitClientEvent);
|
|
7157
7255
|
assert.calledWithMatch(webex.internal.newMetrics.submitClientEvent, {
|
|
@@ -7177,6 +7275,7 @@ describe('plugin-meetings', () => {
|
|
|
7177
7275
|
on: sinon.stub().callsFake((event, listener) => {
|
|
7178
7276
|
eventListeners[event] = listener;
|
|
7179
7277
|
}),
|
|
7278
|
+
getConnectionState: sinon.stub().returns(ConnectionState.Connected),
|
|
7180
7279
|
};
|
|
7181
7280
|
};
|
|
7182
7281
|
|
|
@@ -7230,9 +7329,7 @@ describe('plugin-meetings', () => {
|
|
|
7230
7329
|
assert.equal(meeting.hasMediaConnectionConnectedAtLeastOnce, false);
|
|
7231
7330
|
|
|
7232
7331
|
// simulate first connection success
|
|
7233
|
-
|
|
7234
|
-
state: 'Connected',
|
|
7235
|
-
});
|
|
7332
|
+
simulateConnectionStateChange(ConnectionState.Connected);
|
|
7236
7333
|
checkExpectedSpies({
|
|
7237
7334
|
icePhase: 'JOIN_MEETING_FINAL',
|
|
7238
7335
|
setNetworkStatusCallParams: [NETWORK_STATUS.CONNECTED],
|
|
@@ -7242,12 +7339,9 @@ describe('plugin-meetings', () => {
|
|
|
7242
7339
|
// now simulate short connection loss, client.ice.end is not sent a second time as hasMediaConnectionConnectedAtLeastOnce = true
|
|
7243
7340
|
resetSpies();
|
|
7244
7341
|
|
|
7245
|
-
|
|
7246
|
-
|
|
7247
|
-
|
|
7248
|
-
eventListeners[Event.CONNECTION_STATE_CHANGED]({
|
|
7249
|
-
state: 'Connected',
|
|
7250
|
-
});
|
|
7342
|
+
simulateConnectionStateChange(ConnectionState.Disconnected);
|
|
7343
|
+
|
|
7344
|
+
simulateConnectionStateChange(ConnectionState.Connected);
|
|
7251
7345
|
|
|
7252
7346
|
checkExpectedSpies({
|
|
7253
7347
|
setNetworkStatusCallParams: [NETWORK_STATUS.DISCONNECTED, NETWORK_STATUS.CONNECTED],
|
|
@@ -7255,12 +7349,9 @@ describe('plugin-meetings', () => {
|
|
|
7255
7349
|
|
|
7256
7350
|
resetSpies();
|
|
7257
7351
|
|
|
7258
|
-
|
|
7259
|
-
|
|
7260
|
-
|
|
7261
|
-
eventListeners[Event.CONNECTION_STATE_CHANGED]({
|
|
7262
|
-
state: 'Connected',
|
|
7263
|
-
});
|
|
7352
|
+
simulateConnectionStateChange(ConnectionState.Disconnected);
|
|
7353
|
+
|
|
7354
|
+
simulateConnectionStateChange(ConnectionState.Connected);
|
|
7264
7355
|
});
|
|
7265
7356
|
});
|
|
7266
7357
|
|
|
@@ -7282,9 +7373,8 @@ describe('plugin-meetings', () => {
|
|
|
7282
7373
|
|
|
7283
7374
|
const mockDisconnectedEvent = () => {
|
|
7284
7375
|
meeting.setupMediaConnectionListeners();
|
|
7285
|
-
|
|
7286
|
-
|
|
7287
|
-
});
|
|
7376
|
+
|
|
7377
|
+
simulateConnectionStateChange(ConnectionState.Disconnected);
|
|
7288
7378
|
};
|
|
7289
7379
|
|
|
7290
7380
|
const checkBehavioralMetricSent = (hasMediaConnectionConnectedAtLeastOnce = false) => {
|
|
@@ -7348,9 +7438,8 @@ describe('plugin-meetings', () => {
|
|
|
7348
7438
|
describe('CONNECTION_STATE_CHANGED event when state = "Failed"', () => {
|
|
7349
7439
|
const mockFailedEvent = () => {
|
|
7350
7440
|
meeting.setupMediaConnectionListeners();
|
|
7351
|
-
|
|
7352
|
-
|
|
7353
|
-
});
|
|
7441
|
+
|
|
7442
|
+
simulateConnectionStateChange(ConnectionState.Failed);
|
|
7354
7443
|
};
|
|
7355
7444
|
|
|
7356
7445
|
const checkBehavioralMetricSent = (hasMediaConnectionConnectedAtLeastOnce = false) => {
|
|
@@ -9781,6 +9870,7 @@ describe('plugin-meetings', () => {
|
|
|
9781
9870
|
beforeEach(() => {
|
|
9782
9871
|
webex.internal.llm.isConnected = sinon.stub().returns(false);
|
|
9783
9872
|
webex.internal.llm.getLocusUrl = sinon.stub();
|
|
9873
|
+
webex.internal.llm.getDatachannelUrl = sinon.stub();
|
|
9784
9874
|
webex.internal.llm.registerAndConnect = sinon
|
|
9785
9875
|
.stub()
|
|
9786
9876
|
.returns(Promise.resolve('something'));
|
|
@@ -9808,6 +9898,7 @@ describe('plugin-meetings', () => {
|
|
|
9808
9898
|
meeting.joinedWith = {state: 'JOINED'};
|
|
9809
9899
|
webex.internal.llm.isConnected.returns(true);
|
|
9810
9900
|
webex.internal.llm.getLocusUrl.returns('a url');
|
|
9901
|
+
webex.internal.llm.getDatachannelUrl.returns('a datachannel url');
|
|
9811
9902
|
|
|
9812
9903
|
meeting.locusInfo = {url: 'a url', info: {datachannelUrl: 'a datachannel url'}};
|
|
9813
9904
|
|
|
@@ -9844,6 +9935,7 @@ describe('plugin-meetings', () => {
|
|
|
9844
9935
|
meeting.joinedWith = {state: 'JOINED'};
|
|
9845
9936
|
webex.internal.llm.isConnected.returns(true);
|
|
9846
9937
|
webex.internal.llm.getLocusUrl.returns('a url');
|
|
9938
|
+
webex.internal.llm.getDatachannelUrl.returns('a datachannel url');
|
|
9847
9939
|
|
|
9848
9940
|
meeting.locusInfo = {url: 'a different url', info: {datachannelUrl: 'a datachannel url'}};
|
|
9849
9941
|
|
|
@@ -9869,6 +9961,36 @@ describe('plugin-meetings', () => {
|
|
|
9869
9961
|
);
|
|
9870
9962
|
});
|
|
9871
9963
|
|
|
9964
|
+
it('disconnects if first if the data channel url has changed', async () => {
|
|
9965
|
+
meeting.joinedWith = {state: 'JOINED'};
|
|
9966
|
+
webex.internal.llm.isConnected.returns(true);
|
|
9967
|
+
webex.internal.llm.getLocusUrl.returns('a url');
|
|
9968
|
+
webex.internal.llm.getDatachannelUrl.returns('a datachannel url');
|
|
9969
|
+
|
|
9970
|
+
meeting.locusInfo = {url: 'a url', info: {datachannelUrl: 'a different datachannel url'}};
|
|
9971
|
+
|
|
9972
|
+
const result = await meeting.updateLLMConnection();
|
|
9973
|
+
|
|
9974
|
+
assert.calledWith(webex.internal.llm.disconnectLLM);
|
|
9975
|
+
assert.calledWith(
|
|
9976
|
+
webex.internal.llm.registerAndConnect,
|
|
9977
|
+
'a url',
|
|
9978
|
+
'a different datachannel url'
|
|
9979
|
+
);
|
|
9980
|
+
assert.equal(result, 'something');
|
|
9981
|
+
assert.calledWithExactly(
|
|
9982
|
+
meeting.webex.internal.llm.off,
|
|
9983
|
+
'event:relay.event',
|
|
9984
|
+
meeting.processRelayEvent
|
|
9985
|
+
);
|
|
9986
|
+
assert.calledTwice(meeting.webex.internal.llm.off);
|
|
9987
|
+
assert.calledOnceWithExactly(
|
|
9988
|
+
meeting.webex.internal.llm.on,
|
|
9989
|
+
'event:relay.event',
|
|
9990
|
+
meeting.processRelayEvent
|
|
9991
|
+
);
|
|
9992
|
+
});
|
|
9993
|
+
|
|
9872
9994
|
it('disconnects when the state is not JOINED', async () => {
|
|
9873
9995
|
meeting.joinedWith = {state: 'any other state'};
|
|
9874
9996
|
webex.internal.llm.isConnected.returns(true);
|
|
@@ -58,4 +58,130 @@ describe('Meeting metrics', () => {
|
|
|
58
58
|
});
|
|
59
59
|
});
|
|
60
60
|
});
|
|
61
|
+
|
|
62
|
+
describe('prepareMetricFields', () => {
|
|
63
|
+
it('handles empty objects correctly', () => {
|
|
64
|
+
const result = metrics.prepareMetricFields({});
|
|
65
|
+
|
|
66
|
+
assert.deepEqual(result, {});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('handles literal values correctly', () => {
|
|
70
|
+
assert.deepEqual(metrics.prepareMetricFields('any string'), {value: 'any string'});
|
|
71
|
+
assert.deepEqual(metrics.prepareMetricFields(999), {value: 999});
|
|
72
|
+
assert.deepEqual(metrics.prepareMetricFields(null), {value: null});
|
|
73
|
+
assert.deepEqual(metrics.prepareMetricFields(true), {value: true});
|
|
74
|
+
assert.deepEqual(metrics.prepareMetricFields(false), {value: false});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('handles simple objects correctly', () => {
|
|
78
|
+
const result = metrics.prepareMetricFields({
|
|
79
|
+
someStringProp: 'some string',
|
|
80
|
+
numberProp: 100,
|
|
81
|
+
booleanPropFalse: false,
|
|
82
|
+
booleanPropTrue: true,
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
assert.deepEqual(result, {
|
|
86
|
+
someStringProp: 'some string',
|
|
87
|
+
numberProp: 100,
|
|
88
|
+
booleanPropFalse: false,
|
|
89
|
+
booleanPropTrue: true,
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('handles nested objects correctly', () => {
|
|
94
|
+
const result = metrics.prepareMetricFields({
|
|
95
|
+
someStringProp: 'some string',
|
|
96
|
+
nestedObject: {
|
|
97
|
+
stringProp: 'another string',
|
|
98
|
+
numberProp: 1234,
|
|
99
|
+
deepObject: {
|
|
100
|
+
oneMoreString: 'deep nested string',
|
|
101
|
+
someBoolean: true,
|
|
102
|
+
oneMoreNumber: 10,
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
assert.deepEqual(result, {
|
|
108
|
+
someStringProp: 'some string',
|
|
109
|
+
nestedObject_stringProp: 'another string',
|
|
110
|
+
nestedObject_numberProp: 1234,
|
|
111
|
+
nestedObject_deepObject_oneMoreString: 'deep nested string',
|
|
112
|
+
nestedObject_deepObject_someBoolean: true,
|
|
113
|
+
nestedObject_deepObject_oneMoreNumber: 10,
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('handles arrays correctly', () => {
|
|
118
|
+
const result = metrics.prepareMetricFields(['a string', 10, true, false, {id: 'object id'}]);
|
|
119
|
+
|
|
120
|
+
assert.deepEqual(result, {
|
|
121
|
+
0: 'a string',
|
|
122
|
+
1: 10,
|
|
123
|
+
2: true,
|
|
124
|
+
3: false,
|
|
125
|
+
'4_id': 'object id',
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('handles arrays nested in objects correctly', () => {
|
|
130
|
+
const result = metrics.prepareMetricFields({
|
|
131
|
+
something: 10,
|
|
132
|
+
anObject: {
|
|
133
|
+
someArray: ['a string', 10, true, false, {id: 'object id'}],
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
assert.deepEqual(result, {
|
|
138
|
+
something: 10,
|
|
139
|
+
anObject_someArray_0: 'a string',
|
|
140
|
+
anObject_someArray_1: 10,
|
|
141
|
+
anObject_someArray_2: true,
|
|
142
|
+
anObject_someArray_3: false,
|
|
143
|
+
anObject_someArray_4_id: 'object id',
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('handles arrays nested in arrays correctly', () => {
|
|
148
|
+
const result = metrics.prepareMetricFields({
|
|
149
|
+
something: 10,
|
|
150
|
+
topLevelArray: [
|
|
151
|
+
[1, 2, 'three', {prop1: '1st prop of object 1', prop2: '2nd prop of object 1'}],
|
|
152
|
+
[10, 20, 'thirty', {prop1: '1st prop of object 2', prop2: '2nd prop of object 2'}],
|
|
153
|
+
],
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
assert.deepEqual(result, {
|
|
157
|
+
something: 10,
|
|
158
|
+
topLevelArray_0_0: 1,
|
|
159
|
+
topLevelArray_0_1: 2,
|
|
160
|
+
topLevelArray_0_2: 'three',
|
|
161
|
+
topLevelArray_0_3_prop1: '1st prop of object 1',
|
|
162
|
+
topLevelArray_0_3_prop2: '2nd prop of object 1',
|
|
163
|
+
topLevelArray_1_0: 10,
|
|
164
|
+
topLevelArray_1_1: 20,
|
|
165
|
+
topLevelArray_1_2: 'thirty',
|
|
166
|
+
topLevelArray_1_3_prop1: '1st prop of object 2',
|
|
167
|
+
topLevelArray_1_3_prop2: '2nd prop of object 2',
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('prepends the prefix', () => {
|
|
172
|
+
const result = metrics.prepareMetricFields({
|
|
173
|
+
someStringProp: 'a string',
|
|
174
|
+
numberProp: 111,
|
|
175
|
+
booleanPropFalse: false,
|
|
176
|
+
booleanPropTrue: true,
|
|
177
|
+
}, 'testPrefix');
|
|
178
|
+
|
|
179
|
+
assert.deepEqual(result, {
|
|
180
|
+
testPrefix_someStringProp: 'a string',
|
|
181
|
+
testPrefix_numberProp: 111,
|
|
182
|
+
testPrefix_booleanPropFalse: false,
|
|
183
|
+
testPrefix_booleanPropTrue: true,
|
|
184
|
+
});
|
|
185
|
+
})
|
|
186
|
+
});
|
|
61
187
|
});
|