@webex/webex-core 3.12.0-task-refactor.1 → 3.12.0-webex-services-ready.1
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/config.js +7 -0
- package/dist/config.js.map +1 -1
- package/dist/credentials-config.js +12 -0
- package/dist/credentials-config.js.map +1 -1
- package/dist/interceptors/redirect.js +1 -1
- package/dist/interceptors/redirect.js.map +1 -1
- package/dist/lib/batcher.js +23 -7
- package/dist/lib/batcher.js.map +1 -1
- package/dist/lib/credentials/credentials.js +48 -4
- package/dist/lib/credentials/credentials.js.map +1 -1
- package/dist/lib/credentials/token.js +1 -1
- package/dist/lib/services/service-url.js +11 -1
- package/dist/lib/services/service-url.js.map +1 -1
- package/dist/lib/services/services.js +501 -96
- package/dist/lib/services/services.js.map +1 -1
- package/dist/lib/services-v2/services-v2.js +426 -42
- package/dist/lib/services-v2/services-v2.js.map +1 -1
- package/dist/lib/services-v2/types.js.map +1 -1
- package/dist/plugins/logger.js +1 -1
- package/dist/webex-core.js +2 -2
- package/dist/webex-core.js.map +1 -1
- package/package.json +13 -13
- package/src/config.js +7 -0
- package/src/credentials-config.js +13 -0
- package/src/interceptors/redirect.js +4 -1
- package/src/lib/batcher.js +25 -10
- package/src/lib/credentials/credentials.js +50 -3
- package/src/lib/services/service-url.js +9 -1
- package/src/lib/services/services.js +368 -14
- package/src/lib/services-v2/services-v2.ts +353 -10
- package/src/lib/services-v2/types.ts +5 -0
- package/test/integration/spec/services/service-catalog.js +16 -11
- package/test/integration/spec/services/services.js +38 -11
- package/test/integration/spec/services-v2/services-v2.js +29 -8
- package/test/unit/spec/credentials/credentials.js +133 -2
- package/test/unit/spec/lib/batcher.js +56 -0
- package/test/unit/spec/services/service-url.js +110 -0
- package/test/unit/spec/services/services.js +680 -80
- package/test/unit/spec/services-v2/services-v2.ts +484 -62
- package/test/unit/spec/webex-core.js +2 -0
- package/test/unit/spec/webex-internal-core.js +2 -0
|
@@ -34,6 +34,53 @@ describe('webex-core', () => {
|
|
|
34
34
|
});
|
|
35
35
|
|
|
36
36
|
describe('#initialize', () => {
|
|
37
|
+
it('listens for "loaded" event instead of "ready" to avoid deadlock', () => {
|
|
38
|
+
services.listenToOnce = sinon.stub();
|
|
39
|
+
services.initialize();
|
|
40
|
+
|
|
41
|
+
// Second listenToOnce call should be for 'loaded' event
|
|
42
|
+
assert.equal(services.listenToOnce.getCall(1).args[1], 'loaded');
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('services.ready starts as false', () => {
|
|
46
|
+
assert.isFalse(services.ready);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('sets ready to true and triggers services:initialized when initialization succeeds with credentials', async () => {
|
|
50
|
+
services.listenToOnce = sinon.stub();
|
|
51
|
+
services.initServiceCatalogs = sinon.stub().returns(Promise.resolve());
|
|
52
|
+
services.trigger = sinon.stub();
|
|
53
|
+
services.webex.credentials = {
|
|
54
|
+
supertoken: {
|
|
55
|
+
access_token: 'token',
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
services.initialize();
|
|
60
|
+
|
|
61
|
+
// call the onLoaded callback
|
|
62
|
+
services.listenToOnce.getCall(1).args[2]();
|
|
63
|
+
await waitForAsync();
|
|
64
|
+
|
|
65
|
+
assert.isTrue(services.ready);
|
|
66
|
+
sinon.assert.calledWith(services.trigger, 'services:initialized');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('sets ready to true and triggers services:initialized when initialization succeeds without credentials', async () => {
|
|
70
|
+
services.listenToOnce = sinon.stub();
|
|
71
|
+
services.collectPreauthCatalog = sinon.stub().returns(Promise.resolve());
|
|
72
|
+
services.trigger = sinon.stub();
|
|
73
|
+
|
|
74
|
+
services.initialize();
|
|
75
|
+
|
|
76
|
+
// call the onLoaded callback
|
|
77
|
+
services.listenToOnce.getCall(1).args[2]();
|
|
78
|
+
await waitForAsync();
|
|
79
|
+
|
|
80
|
+
assert.isTrue(services.ready);
|
|
81
|
+
sinon.assert.calledWith(services.trigger, 'services:initialized');
|
|
82
|
+
});
|
|
83
|
+
|
|
37
84
|
it('initFailed is false when initialization succeeds and credentials are available', async () => {
|
|
38
85
|
services.listenToOnce = sinon.stub();
|
|
39
86
|
services.initServiceCatalogs = sinon.stub().returns(Promise.resolve());
|
|
@@ -45,7 +92,7 @@ describe('webex-core', () => {
|
|
|
45
92
|
|
|
46
93
|
services.initialize();
|
|
47
94
|
|
|
48
|
-
// call the
|
|
95
|
+
// call the onLoaded callback
|
|
49
96
|
services.listenToOnce.getCall(1).args[2]();
|
|
50
97
|
await waitForAsync();
|
|
51
98
|
|
|
@@ -58,7 +105,7 @@ describe('webex-core', () => {
|
|
|
58
105
|
|
|
59
106
|
services.initialize();
|
|
60
107
|
|
|
61
|
-
// call the
|
|
108
|
+
// call the onLoaded callback
|
|
62
109
|
services.listenToOnce.getCall(1).args[2]();
|
|
63
110
|
await waitForAsync();
|
|
64
111
|
|
|
@@ -67,9 +114,9 @@ describe('webex-core', () => {
|
|
|
67
114
|
|
|
68
115
|
it.each([
|
|
69
116
|
{error: new Error('failed'), expectedMessage: 'failed'},
|
|
70
|
-
{error: undefined, expectedMessage: undefined}
|
|
117
|
+
{error: undefined, expectedMessage: undefined},
|
|
71
118
|
])(
|
|
72
|
-
'sets initFailed to true when collectPreauthCatalog errors',
|
|
119
|
+
'sets initFailed to true when collectPreauthCatalog errors but still sets ready to true',
|
|
73
120
|
async ({error, expectedMessage}) => {
|
|
74
121
|
services.collectPreauthCatalog = sinon.stub().callsFake(() => {
|
|
75
122
|
return Promise.reject(error);
|
|
@@ -77,15 +124,18 @@ describe('webex-core', () => {
|
|
|
77
124
|
|
|
78
125
|
services.listenToOnce = sinon.stub();
|
|
79
126
|
services.logger.error = sinon.stub();
|
|
127
|
+
services.trigger = sinon.stub();
|
|
80
128
|
|
|
81
129
|
services.initialize();
|
|
82
130
|
|
|
83
|
-
// call the
|
|
131
|
+
// call the onLoaded callback
|
|
84
132
|
services.listenToOnce.getCall(1).args[2]();
|
|
85
133
|
|
|
86
134
|
await waitForAsync();
|
|
87
135
|
|
|
88
136
|
assert.isTrue(services.initFailed);
|
|
137
|
+
assert.isTrue(services.ready);
|
|
138
|
+
sinon.assert.calledWith(services.trigger, 'services:initialized');
|
|
89
139
|
sinon.assert.calledWith(
|
|
90
140
|
services.logger.error,
|
|
91
141
|
`services: failed to init initial services when no credentials available, ${expectedMessage}`
|
|
@@ -95,32 +145,132 @@ describe('webex-core', () => {
|
|
|
95
145
|
|
|
96
146
|
it.each([
|
|
97
147
|
{error: new Error('failed'), expectedMessage: 'failed'},
|
|
98
|
-
{error: undefined, expectedMessage: undefined}
|
|
99
|
-
])(
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
148
|
+
{error: undefined, expectedMessage: undefined},
|
|
149
|
+
])(
|
|
150
|
+
'sets initFailed to true when initServiceCatalogs errors but still sets ready to true',
|
|
151
|
+
async ({error, expectedMessage}) => {
|
|
152
|
+
services.initServiceCatalogs = sinon.stub().callsFake(() => {
|
|
153
|
+
return Promise.reject(error);
|
|
154
|
+
});
|
|
155
|
+
services.webex.credentials = {
|
|
156
|
+
supertoken: {
|
|
157
|
+
access_token: 'token',
|
|
158
|
+
},
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
services.listenToOnce = sinon.stub();
|
|
162
|
+
services.logger.error = sinon.stub();
|
|
163
|
+
services.trigger = sinon.stub();
|
|
164
|
+
|
|
165
|
+
services.initialize();
|
|
166
|
+
|
|
167
|
+
// call the onLoaded callback
|
|
168
|
+
services.listenToOnce.getCall(1).args[2]();
|
|
169
|
+
|
|
170
|
+
await waitForAsync();
|
|
171
|
+
|
|
172
|
+
assert.isTrue(services.initFailed);
|
|
173
|
+
assert.isTrue(services.ready);
|
|
174
|
+
sinon.assert.calledWith(services.trigger, 'services:initialized');
|
|
175
|
+
sinon.assert.calledWith(
|
|
176
|
+
services.logger.error,
|
|
177
|
+
`services: failed to init initial services when credentials available, ${expectedMessage}`
|
|
178
|
+
);
|
|
107
179
|
}
|
|
180
|
+
);
|
|
108
181
|
|
|
109
|
-
|
|
110
|
-
|
|
182
|
+
describe('when change:canAuthorize fires', () => {
|
|
183
|
+
it('calls initServiceCatalogs when canAuthorize becomes true and postauth catalog is not ready', async () => {
|
|
184
|
+
services.listenToOnce = sinon.stub();
|
|
185
|
+
services._loadCatalogFromCache = sinon.stub().returns(Promise.resolve(false));
|
|
186
|
+
services.collectPreauthCatalog = sinon.stub().returns(Promise.resolve());
|
|
187
|
+
services.initServiceCatalogs = sinon.stub().returns(Promise.resolve());
|
|
188
|
+
services.trigger = sinon.stub();
|
|
189
|
+
// Ensure no credentials so we go to the preauth path
|
|
190
|
+
services.webex.credentials = {};
|
|
111
191
|
|
|
112
|
-
|
|
192
|
+
services.initialize();
|
|
113
193
|
|
|
114
|
-
|
|
115
|
-
|
|
194
|
+
// Get the catalog after initialize creates it
|
|
195
|
+
const testCatalog = services._getCatalog();
|
|
196
|
+
testCatalog.status.postauth.ready = false;
|
|
116
197
|
|
|
117
|
-
|
|
198
|
+
// call the onLoaded callback (preauth path)
|
|
199
|
+
services.listenToOnce.getCall(1).args[2]();
|
|
200
|
+
await waitForAsync();
|
|
118
201
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
202
|
+
// Now set canAuthorize to true to simulate auth completing
|
|
203
|
+
services.webex.canAuthorize = true;
|
|
204
|
+
|
|
205
|
+
// call the change:canAuthorize callback (should be call 2)
|
|
206
|
+
services.listenToOnce.getCall(2).args[2]();
|
|
207
|
+
await waitForAsync();
|
|
208
|
+
|
|
209
|
+
sinon.assert.calledOnce(services.initServiceCatalogs);
|
|
210
|
+
assert.isTrue(testCatalog.isReady);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it('does not call initServiceCatalogs when postauth catalog is already ready', async () => {
|
|
214
|
+
services.listenToOnce = sinon.stub();
|
|
215
|
+
services._loadCatalogFromCache = sinon.stub().returns(Promise.resolve(false));
|
|
216
|
+
services.collectPreauthCatalog = sinon.stub().returns(Promise.resolve());
|
|
217
|
+
services.initServiceCatalogs = sinon.stub().returns(Promise.resolve());
|
|
218
|
+
services.trigger = sinon.stub();
|
|
219
|
+
// Ensure no credentials so we go to the preauth path
|
|
220
|
+
services.webex.credentials = {};
|
|
221
|
+
|
|
222
|
+
services.initialize();
|
|
223
|
+
|
|
224
|
+
// Get the catalog after initialize creates it
|
|
225
|
+
const testCatalog = services._getCatalog();
|
|
226
|
+
|
|
227
|
+
// call the onLoaded callback (preauth path)
|
|
228
|
+
services.listenToOnce.getCall(1).args[2]();
|
|
229
|
+
await waitForAsync();
|
|
230
|
+
|
|
231
|
+
// Set canAuthorize to true and postauth as ready BEFORE calling the callback
|
|
232
|
+
services.webex.canAuthorize = true;
|
|
233
|
+
testCatalog.status.postauth.ready = true;
|
|
234
|
+
|
|
235
|
+
// call the change:canAuthorize callback (should be call 2)
|
|
236
|
+
services.listenToOnce.getCall(2).args[2]();
|
|
237
|
+
await waitForAsync();
|
|
238
|
+
|
|
239
|
+
sinon.assert.notCalled(services.initServiceCatalogs);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
it('logs an error when initServiceCatalogs fails after auth', async () => {
|
|
243
|
+
services.listenToOnce = sinon.stub();
|
|
244
|
+
services._loadCatalogFromCache = sinon.stub().returns(Promise.resolve(false));
|
|
245
|
+
services.collectPreauthCatalog = sinon.stub().returns(Promise.resolve());
|
|
246
|
+
services.initServiceCatalogs = sinon.stub().rejects(new Error('auth failed'));
|
|
247
|
+
services.logger.error = sinon.stub();
|
|
248
|
+
services.trigger = sinon.stub();
|
|
249
|
+
// Ensure no credentials so we go to the preauth path
|
|
250
|
+
services.webex.credentials = {};
|
|
251
|
+
|
|
252
|
+
services.initialize();
|
|
253
|
+
|
|
254
|
+
// Get the catalog after initialize creates it
|
|
255
|
+
const testCatalog = services._getCatalog();
|
|
256
|
+
testCatalog.status.postauth.ready = false;
|
|
257
|
+
|
|
258
|
+
// call the onLoaded callback (preauth path)
|
|
259
|
+
services.listenToOnce.getCall(1).args[2]();
|
|
260
|
+
await waitForAsync();
|
|
261
|
+
|
|
262
|
+
// Now set canAuthorize to true to simulate auth completing
|
|
263
|
+
services.webex.canAuthorize = true;
|
|
264
|
+
|
|
265
|
+
// call the change:canAuthorize callback (should be call 2)
|
|
266
|
+
services.listenToOnce.getCall(2).args[2]();
|
|
267
|
+
await waitForAsync();
|
|
268
|
+
|
|
269
|
+
sinon.assert.calledWith(
|
|
270
|
+
services.logger.error,
|
|
271
|
+
'services: failed to init service catalogs after auth, auth failed'
|
|
272
|
+
);
|
|
273
|
+
});
|
|
124
274
|
});
|
|
125
275
|
});
|
|
126
276
|
|
|
@@ -159,7 +309,7 @@ describe('webex-core', () => {
|
|
|
159
309
|
|
|
160
310
|
services.collectPreauthCatalog = sinon.stub().callsFake(() => {
|
|
161
311
|
return Promise.resolve();
|
|
162
|
-
})
|
|
312
|
+
});
|
|
163
313
|
|
|
164
314
|
services.updateServices = sinon.stub().callsFake(() => {
|
|
165
315
|
return Promise.reject(error);
|
|
@@ -254,7 +404,7 @@ describe('webex-core', () => {
|
|
|
254
404
|
discovery: {
|
|
255
405
|
sqdiscovery: 'https://test.ciscospark.com/v1/region',
|
|
256
406
|
},
|
|
257
|
-
}
|
|
407
|
+
},
|
|
258
408
|
};
|
|
259
409
|
});
|
|
260
410
|
|
|
@@ -274,8 +424,8 @@ describe('webex-core', () => {
|
|
|
274
424
|
assert.calledWith(webex.request, {
|
|
275
425
|
uri: 'https://test.ciscospark.com/v1/region',
|
|
276
426
|
addAuthHeader: false,
|
|
277
|
-
headers: {
|
|
278
|
-
timeout: 5000
|
|
427
|
+
headers: {'spark-user-agent': null},
|
|
428
|
+
timeout: 5000,
|
|
279
429
|
});
|
|
280
430
|
});
|
|
281
431
|
});
|
|
@@ -332,7 +482,6 @@ describe('webex-core', () => {
|
|
|
332
482
|
});
|
|
333
483
|
|
|
334
484
|
describe('#_fetchNewServiceHostmap()', () => {
|
|
335
|
-
|
|
336
485
|
beforeEach(() => {
|
|
337
486
|
sinon.spy(webex.internal.newMetrics.callDiagnosticLatencies, 'measureLatency');
|
|
338
487
|
});
|
|
@@ -346,19 +495,20 @@ describe('webex-core', () => {
|
|
|
346
495
|
|
|
347
496
|
sinon.stub(services, '_formatReceivedHostmap').resolves(mapResponse);
|
|
348
497
|
sinon.stub(services, 'request').resolves({});
|
|
349
|
-
|
|
350
|
-
const mapResult = await services._fetchNewServiceHostmap({from: 'limited'});
|
|
351
498
|
|
|
352
|
-
|
|
499
|
+
const mapResult = await services._fetchNewServiceHostmap({from: 'limited'});
|
|
353
500
|
|
|
354
501
|
assert.calledOnceWithExactly(services.request, {
|
|
355
502
|
method: 'GET',
|
|
356
503
|
service: 'u2c',
|
|
357
504
|
resource: '/limited/catalog',
|
|
358
|
-
qs: {format: 'hostmap'}
|
|
359
|
-
}
|
|
505
|
+
qs: {format: 'hostmap'},
|
|
506
|
+
});
|
|
507
|
+
assert.calledOnceWithExactly(
|
|
508
|
+
webex.internal.newMetrics.callDiagnosticLatencies.measureLatency,
|
|
509
|
+
sinon.match.func,
|
|
510
|
+
'internal.get.u2c.time'
|
|
360
511
|
);
|
|
361
|
-
assert.calledOnceWithExactly(webex.internal.newMetrics.callDiagnosticLatencies.measureLatency, sinon.match.func, 'internal.get.u2c.time');
|
|
362
512
|
});
|
|
363
513
|
|
|
364
514
|
it('checks service request rejects', async () => {
|
|
@@ -366,7 +516,7 @@ describe('webex-core', () => {
|
|
|
366
516
|
|
|
367
517
|
sinon.spy(services, '_formatReceivedHostmap');
|
|
368
518
|
sinon.stub(services, 'request').rejects(error);
|
|
369
|
-
|
|
519
|
+
|
|
370
520
|
const promise = services._fetchNewServiceHostmap({from: 'limited'});
|
|
371
521
|
const rejectedValue = await assert.isRejected(promise);
|
|
372
522
|
|
|
@@ -378,10 +528,13 @@ describe('webex-core', () => {
|
|
|
378
528
|
method: 'GET',
|
|
379
529
|
service: 'u2c',
|
|
380
530
|
resource: '/limited/catalog',
|
|
381
|
-
qs: {format: 'hostmap'}
|
|
382
|
-
}
|
|
531
|
+
qs: {format: 'hostmap'},
|
|
532
|
+
});
|
|
533
|
+
assert.calledOnceWithExactly(
|
|
534
|
+
webex.internal.newMetrics.callDiagnosticLatencies.measureLatency,
|
|
535
|
+
sinon.match.func,
|
|
536
|
+
'internal.get.u2c.time'
|
|
383
537
|
);
|
|
384
|
-
assert.calledOnceWithExactly(webex.internal.newMetrics.callDiagnosticLatencies.measureLatency, sinon.match.func, 'internal.get.u2c.time');
|
|
385
538
|
});
|
|
386
539
|
});
|
|
387
540
|
|
|
@@ -412,7 +565,6 @@ describe('webex-core', () => {
|
|
|
412
565
|
});
|
|
413
566
|
|
|
414
567
|
it('returns the original uri if the hostmap has no hosts for the host', () => {
|
|
415
|
-
|
|
416
568
|
services._hostCatalog = {
|
|
417
569
|
'example.com': [],
|
|
418
570
|
};
|
|
@@ -576,8 +728,7 @@ describe('webex-core', () => {
|
|
|
576
728
|
id: '0:0:0:different-e-x',
|
|
577
729
|
},
|
|
578
730
|
],
|
|
579
|
-
'example-f.com': [
|
|
580
|
-
],
|
|
731
|
+
'example-f.com': [],
|
|
581
732
|
},
|
|
582
733
|
format: 'hostmap',
|
|
583
734
|
};
|
|
@@ -785,7 +936,7 @@ describe('webex-core', () => {
|
|
|
785
936
|
defaultUrl: 'https://example-g.com/api/v1',
|
|
786
937
|
hosts: [],
|
|
787
938
|
name: 'example-g',
|
|
788
|
-
}
|
|
939
|
+
},
|
|
789
940
|
]);
|
|
790
941
|
});
|
|
791
942
|
|
|
@@ -839,63 +990,92 @@ describe('webex-core', () => {
|
|
|
839
990
|
assert.equal(webex.config.credentials.authorizeUrl, authUrl);
|
|
840
991
|
});
|
|
841
992
|
});
|
|
842
|
-
|
|
993
|
+
|
|
843
994
|
describe('#getMobiusClusters', () => {
|
|
844
995
|
it('returns unique mobius host entries from hostCatalog', () => {
|
|
845
996
|
// Arrange: two hostCatalog keys, with duplicate mobius host across keys
|
|
846
997
|
services._hostCatalog = {
|
|
847
|
-
'mobius-
|
|
848
|
-
{
|
|
849
|
-
|
|
850
|
-
|
|
998
|
+
'mobius-us-east-2.prod.infra.webex.com': [
|
|
999
|
+
{
|
|
1000
|
+
host: 'mobius-us-east-2.prod.infra.webex.com',
|
|
1001
|
+
ttl: -1,
|
|
1002
|
+
priority: 5,
|
|
1003
|
+
id: 'urn:TEAM:xyz:mobius',
|
|
1004
|
+
},
|
|
1005
|
+
{
|
|
1006
|
+
host: 'mobius-eu-central-1.prod.infra.webex.com',
|
|
1007
|
+
ttl: -1,
|
|
1008
|
+
priority: 10,
|
|
1009
|
+
id: 'urn:TEAM:xyz:mobius',
|
|
1010
|
+
},
|
|
851
1011
|
],
|
|
852
|
-
|
|
853
|
-
|
|
1012
|
+
|
|
1013
|
+
'mobius-eu-central-1.prod.infra.webex.com': [
|
|
1014
|
+
{
|
|
1015
|
+
host: 'mobius-us-east-2.prod.infra.webex.com',
|
|
1016
|
+
ttl: -1,
|
|
1017
|
+
priority: 7,
|
|
1018
|
+
id: 'urn:TEAM:xyz:mobius',
|
|
1019
|
+
}, // duplicate host
|
|
1020
|
+
],
|
|
1021
|
+
'wdm-a.webex.com': [
|
|
1022
|
+
{host: 'wdm-a.webex.com', ttl: -1, priority: 5, id: 'urn:TEAM:xyz:wdm'},
|
|
854
1023
|
],
|
|
855
1024
|
};
|
|
856
|
-
|
|
1025
|
+
|
|
857
1026
|
// Act
|
|
858
1027
|
const clusters = services.getMobiusClusters();
|
|
859
|
-
|
|
1028
|
+
|
|
860
1029
|
// Assert
|
|
861
1030
|
// deduped; only mobius entries; keeps first seen mobius-a, then mobius-b
|
|
862
1031
|
assert.deepEqual(
|
|
863
1032
|
clusters.map(({host, id, ttl, priority}) => ({host, id, ttl, priority})),
|
|
864
1033
|
[
|
|
865
|
-
{
|
|
866
|
-
|
|
1034
|
+
{
|
|
1035
|
+
host: 'mobius-us-east-2.prod.infra.webex.com',
|
|
1036
|
+
id: 'urn:TEAM:xyz:mobius',
|
|
1037
|
+
ttl: -1,
|
|
1038
|
+
priority: 5,
|
|
1039
|
+
},
|
|
1040
|
+
{
|
|
1041
|
+
host: 'mobius-eu-central-1.prod.infra.webex.com',
|
|
1042
|
+
id: 'urn:TEAM:xyz:mobius',
|
|
1043
|
+
ttl: -1,
|
|
1044
|
+
priority: 10,
|
|
1045
|
+
},
|
|
867
1046
|
]
|
|
868
1047
|
);
|
|
869
1048
|
});
|
|
870
1049
|
});
|
|
1050
|
+
|
|
871
1051
|
describe('#isValidHost', () => {
|
|
872
1052
|
beforeEach(() => {
|
|
873
1053
|
// Setting up a mock host catalog
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
1054
|
+
services._hostCatalog = {
|
|
1055
|
+
'audit-ci-m.wbx2.com': [
|
|
1056
|
+
{
|
|
1057
|
+
host: 'audit-ci-m.wbx2.com',
|
|
1058
|
+
ttl: -1,
|
|
1059
|
+
priority: 5,
|
|
1060
|
+
id: 'urn:IDENTITY:PA61:adminAudit',
|
|
1061
|
+
},
|
|
1062
|
+
{
|
|
1063
|
+
host: 'audit-ci-m.wbx2.com',
|
|
1064
|
+
ttl: -1,
|
|
1065
|
+
priority: 5,
|
|
1066
|
+
id: 'urn:IDENTITY:PA61:adminAuditV2',
|
|
1067
|
+
},
|
|
1068
|
+
],
|
|
1069
|
+
'mercury-connection-partition0-r.wbx2.com': [
|
|
1070
|
+
{
|
|
1071
|
+
host: 'mercury-connection-partition0-r.wbx2.com',
|
|
1072
|
+
ttl: -1,
|
|
1073
|
+
priority: 5,
|
|
1074
|
+
id: 'urn:TEAM:us-west-2_r:mercuryConnectionPartition0',
|
|
1075
|
+
},
|
|
1076
|
+
],
|
|
1077
|
+
'empty.com': [],
|
|
1078
|
+
};
|
|
899
1079
|
});
|
|
900
1080
|
afterAll(() => {
|
|
901
1081
|
// Clean up the mock host catalog
|
|
@@ -919,6 +1099,426 @@ describe('webex-core', () => {
|
|
|
919
1099
|
assert.isFalse(services.isValidHost([]));
|
|
920
1100
|
});
|
|
921
1101
|
});
|
|
1102
|
+
|
|
1103
|
+
describe('#isIntegrationEnvironment', () => {
|
|
1104
|
+
it('returns true when u2c URL contains "intb"', () => {
|
|
1105
|
+
services.webex.config = {
|
|
1106
|
+
services: {
|
|
1107
|
+
discovery: {
|
|
1108
|
+
u2c: 'https://u2c-intb.ciscospark.com/u2c/api/v1',
|
|
1109
|
+
},
|
|
1110
|
+
},
|
|
1111
|
+
};
|
|
1112
|
+
assert.isTrue(services.isIntegrationEnvironment());
|
|
1113
|
+
});
|
|
1114
|
+
|
|
1115
|
+
it('returns false when u2c URL does not contain "intb" (production)', () => {
|
|
1116
|
+
services.webex.config = {
|
|
1117
|
+
services: {
|
|
1118
|
+
discovery: {
|
|
1119
|
+
u2c: 'https://u2c.wbx2.com/u2c/api/v1',
|
|
1120
|
+
},
|
|
1121
|
+
},
|
|
1122
|
+
};
|
|
1123
|
+
assert.isFalse(services.isIntegrationEnvironment());
|
|
1124
|
+
});
|
|
1125
|
+
|
|
1126
|
+
it('returns false when u2c URL is for FedRAMP', () => {
|
|
1127
|
+
services.webex.config = {
|
|
1128
|
+
services: {
|
|
1129
|
+
discovery: {
|
|
1130
|
+
u2c: 'https://u2c.gov.ciscospark.com/u2c/api/v1',
|
|
1131
|
+
},
|
|
1132
|
+
},
|
|
1133
|
+
};
|
|
1134
|
+
assert.isFalse(services.isIntegrationEnvironment());
|
|
1135
|
+
});
|
|
1136
|
+
|
|
1137
|
+
it('returns false when u2c URL is undefined', () => {
|
|
1138
|
+
services.webex.config = {
|
|
1139
|
+
services: {
|
|
1140
|
+
discovery: {
|
|
1141
|
+
u2c: undefined,
|
|
1142
|
+
},
|
|
1143
|
+
},
|
|
1144
|
+
};
|
|
1145
|
+
assert.isFalse(services.isIntegrationEnvironment());
|
|
1146
|
+
});
|
|
1147
|
+
|
|
1148
|
+
it('returns false when config is not available', () => {
|
|
1149
|
+
services.webex.config = undefined;
|
|
1150
|
+
assert.isFalse(services.isIntegrationEnvironment());
|
|
1151
|
+
});
|
|
1152
|
+
});
|
|
1153
|
+
|
|
1154
|
+
describe('U2C catalog cache behavior', () => {
|
|
1155
|
+
let webex;
|
|
1156
|
+
let services;
|
|
1157
|
+
let catalog;
|
|
1158
|
+
let localStorageBackup;
|
|
1159
|
+
let windowBackup;
|
|
1160
|
+
|
|
1161
|
+
const makeLocalStorageShim = () => {
|
|
1162
|
+
const store = new Map();
|
|
1163
|
+
return {
|
|
1164
|
+
getItem: (k) => (store.has(k) ? store.get(k) : null),
|
|
1165
|
+
setItem: (k, v) => store.set(k, v),
|
|
1166
|
+
removeItem: (k) => store.delete(k),
|
|
1167
|
+
_store: store,
|
|
1168
|
+
};
|
|
1169
|
+
};
|
|
1170
|
+
|
|
1171
|
+
beforeEach(() => {
|
|
1172
|
+
// Build a fresh webex instance
|
|
1173
|
+
webex = new MockWebex({
|
|
1174
|
+
children: {services: Services},
|
|
1175
|
+
config: {credentials: {federation: true}},
|
|
1176
|
+
});
|
|
1177
|
+
services = webex.internal.services;
|
|
1178
|
+
catalog = services._getCatalog();
|
|
1179
|
+
|
|
1180
|
+
// enable U2C caching feature flag in tests that rely on localStorage writes/reads
|
|
1181
|
+
services.webex.config = services.webex.config || {};
|
|
1182
|
+
services.webex.config.calling = {...(services.webex.config.calling || {}), cacheU2C: true};
|
|
1183
|
+
|
|
1184
|
+
// stub window.localStorage
|
|
1185
|
+
windowBackup = global.window;
|
|
1186
|
+
if (!global.window) global.window = {};
|
|
1187
|
+
localStorageBackup = global.window.localStorage;
|
|
1188
|
+
global.window.localStorage = makeLocalStorageShim();
|
|
1189
|
+
// Ensure code under test uses our shim via util method
|
|
1190
|
+
sinon.stub(services, '_getLocalStorageSafe').returns(global.window.localStorage);
|
|
1191
|
+
|
|
1192
|
+
// Stub the formatter so we don't need a full hostmap payload in tests
|
|
1193
|
+
sinon
|
|
1194
|
+
.stub(services, '_formatReceivedHostmap')
|
|
1195
|
+
.callsFake(() => [
|
|
1196
|
+
{name: 'hydra', defaultUrl: 'https://api.ciscospark.com/v1', hosts: []},
|
|
1197
|
+
]);
|
|
1198
|
+
});
|
|
1199
|
+
|
|
1200
|
+
afterEach(() => {
|
|
1201
|
+
global.window.localStorage = localStorageBackup || undefined;
|
|
1202
|
+
if (!windowBackup) {
|
|
1203
|
+
delete global.window;
|
|
1204
|
+
} else {
|
|
1205
|
+
global.window = windowBackup;
|
|
1206
|
+
}
|
|
1207
|
+
// Restore util stub if present
|
|
1208
|
+
if (services._getLocalStorageSafe && services._getLocalStorageSafe.restore) {
|
|
1209
|
+
services._getLocalStorageSafe.restore();
|
|
1210
|
+
}
|
|
1211
|
+
});
|
|
1212
|
+
|
|
1213
|
+
it('invokes initServiceCatalogs on ready, caches catalog, and stores in localStorage', async () => {
|
|
1214
|
+
// Arrange: authenticated credentials and spies
|
|
1215
|
+
services.webex.credentials = {
|
|
1216
|
+
getOrgId: sinon.stub().returns('urn:EXAMPLE:org'),
|
|
1217
|
+
canAuthorize: true,
|
|
1218
|
+
supertoken: {access_token: 'token'},
|
|
1219
|
+
};
|
|
1220
|
+
const initSpy = sinon.spy(services, 'initServiceCatalogs');
|
|
1221
|
+
const cacheSpy = sinon.spy(services, '_cacheCatalog');
|
|
1222
|
+
const setItemSpy = sinon.spy(global.window.localStorage, 'setItem');
|
|
1223
|
+
// Make fetch return a hostmap object and allow formatter to reduce it
|
|
1224
|
+
sinon
|
|
1225
|
+
.stub(services, 'request')
|
|
1226
|
+
.resolves({
|
|
1227
|
+
body: {
|
|
1228
|
+
services: [],
|
|
1229
|
+
activeServices: {},
|
|
1230
|
+
timestamp: Date.now().toString(),
|
|
1231
|
+
orgId: 'urn:EXAMPLE:org',
|
|
1232
|
+
format: 'U2CV2',
|
|
1233
|
+
},
|
|
1234
|
+
});
|
|
1235
|
+
// Cause loaded callback to run immediately
|
|
1236
|
+
services.listenToOnce = sinon.stub().callsFake((ctx, event, cb) => {
|
|
1237
|
+
if (event === 'loaded') cb();
|
|
1238
|
+
});
|
|
1239
|
+
|
|
1240
|
+
// Act
|
|
1241
|
+
services.initialize();
|
|
1242
|
+
await waitForAsync();
|
|
1243
|
+
|
|
1244
|
+
// Assert: initServiceCatalogs was called because there was no cache
|
|
1245
|
+
assert.isTrue(initSpy.called, 'expected initServiceCatalogs to be invoked on ready');
|
|
1246
|
+
// _cacheCatalog is called at least once (preauth/postauth flows)
|
|
1247
|
+
assert.isTrue(cacheSpy.called, 'expected _cacheCatalog to be called');
|
|
1248
|
+
assert.isTrue(setItemSpy.called, 'expected localStorage.setItem to be called');
|
|
1249
|
+
|
|
1250
|
+
// Cleanup spies
|
|
1251
|
+
services.request.restore();
|
|
1252
|
+
initSpy.restore();
|
|
1253
|
+
cacheSpy.restore();
|
|
1254
|
+
setItemSpy.restore();
|
|
1255
|
+
});
|
|
1256
|
+
|
|
1257
|
+
it('does not invoke initServiceCatalogs on ready when cache exists and uses cached catalog', async () => {
|
|
1258
|
+
// Arrange: put a valid cache
|
|
1259
|
+
const CATALOG_CACHE_KEY_V1 = 'services.v1.u2cHostMap';
|
|
1260
|
+
const cached = {
|
|
1261
|
+
orgId: 'urn:EXAMPLE:org',
|
|
1262
|
+
cachedAt: Date.now(),
|
|
1263
|
+
preauth: {serviceLinks: {}, hostCatalog: {}},
|
|
1264
|
+
postauth: {serviceLinks: {}, hostCatalog: {}},
|
|
1265
|
+
};
|
|
1266
|
+
global.window.localStorage.setItem(CATALOG_CACHE_KEY_V1, JSON.stringify(cached));
|
|
1267
|
+
|
|
1268
|
+
// authenticated credentials
|
|
1269
|
+
services.webex.credentials = {
|
|
1270
|
+
getOrgId: sinon.stub().returns('urn:EXAMPLE:org'),
|
|
1271
|
+
canAuthorize: true,
|
|
1272
|
+
supertoken: {access_token: 'token'},
|
|
1273
|
+
};
|
|
1274
|
+
|
|
1275
|
+
const initSpy = sinon.spy(services, 'initServiceCatalogs');
|
|
1276
|
+
const cacheSpy = sinon.spy(services, '_cacheCatalog');
|
|
1277
|
+
// Cause loaded callback to run immediately
|
|
1278
|
+
services.listenToOnce = sinon.stub().callsFake((ctx, event, cb) => {
|
|
1279
|
+
if (event === 'loaded') cb();
|
|
1280
|
+
});
|
|
1281
|
+
|
|
1282
|
+
// Act
|
|
1283
|
+
services.initialize();
|
|
1284
|
+
await waitForAsync();
|
|
1285
|
+
|
|
1286
|
+
// Assert: ready path found cache and skipped initServiceCatalogs
|
|
1287
|
+
assert.isFalse(
|
|
1288
|
+
initSpy.called,
|
|
1289
|
+
'expected initServiceCatalogs to be skipped with cache present'
|
|
1290
|
+
);
|
|
1291
|
+
assert.isTrue(
|
|
1292
|
+
services._getCatalog().status.preauth.ready,
|
|
1293
|
+
'preauth should be ready from cache'
|
|
1294
|
+
);
|
|
1295
|
+
assert.isTrue(
|
|
1296
|
+
services._getCatalog().status.postauth.ready,
|
|
1297
|
+
'postauth should be ready from cache'
|
|
1298
|
+
);
|
|
1299
|
+
assert.isFalse(cacheSpy.called, 'should not write cache during warm-up-only path');
|
|
1300
|
+
|
|
1301
|
+
// Cleanup
|
|
1302
|
+
initSpy.restore();
|
|
1303
|
+
cacheSpy.restore();
|
|
1304
|
+
});
|
|
1305
|
+
|
|
1306
|
+
it('expires cached catalog after TTL and clears the entry', async () => {
|
|
1307
|
+
const CATALOG_CACHE_KEY_V1 = 'services.v1.u2cHostMap';
|
|
1308
|
+
const staleCached = {
|
|
1309
|
+
orgId: 'urn:EXAMPLE:org',
|
|
1310
|
+
cachedAt: Date.now() - (24 * 60 * 60 * 1000 + 1000), // past TTL
|
|
1311
|
+
preauth: {serviceLinks: {}, hostCatalog: {}},
|
|
1312
|
+
postauth: {serviceLinks: {}, hostCatalog: {}},
|
|
1313
|
+
};
|
|
1314
|
+
|
|
1315
|
+
window.localStorage.setItem(CATALOG_CACHE_KEY_V1, JSON.stringify(staleCached));
|
|
1316
|
+
|
|
1317
|
+
const warmed = await services._loadCatalogFromCache();
|
|
1318
|
+
|
|
1319
|
+
assert.isFalse(warmed, 'stale cache must not warm');
|
|
1320
|
+
assert.isNull(
|
|
1321
|
+
window.localStorage.getItem(CATALOG_CACHE_KEY_V1),
|
|
1322
|
+
'expired cache must be cleared'
|
|
1323
|
+
);
|
|
1324
|
+
assert.isFalse(catalog.status.preauth.ready);
|
|
1325
|
+
assert.isFalse(catalog.status.postauth.ready);
|
|
1326
|
+
});
|
|
1327
|
+
|
|
1328
|
+
it('clearCatalogCache() removes the cached entry', async () => {
|
|
1329
|
+
const CATALOG_CACHE_KEY_V1 = 'services.v1.u2cHostMap';
|
|
1330
|
+
window.localStorage.setItem(CATALOG_CACHE_KEY_V1, JSON.stringify({cachedAt: Date.now()}));
|
|
1331
|
+
|
|
1332
|
+
await services.clearCatalogCache();
|
|
1333
|
+
|
|
1334
|
+
assert.isNull(window.localStorage.getItem(CATALOG_CACHE_KEY_V1), 'cache should be cleared');
|
|
1335
|
+
});
|
|
1336
|
+
|
|
1337
|
+
it('still fetches when forceRefresh=true even if ready', async () => {
|
|
1338
|
+
const CATALOG_CACHE_KEY_V1 = 'services.v1.u2cHostMap';
|
|
1339
|
+
window.localStorage.setItem(
|
|
1340
|
+
CATALOG_CACHE_KEY_V1,
|
|
1341
|
+
JSON.stringify({
|
|
1342
|
+
orgId: 'urn:EXAMPLE:org',
|
|
1343
|
+
cachedAt: Date.now(),
|
|
1344
|
+
preauth: {serviceLinks: {}, hostCatalog: {}},
|
|
1345
|
+
postauth: {serviceLinks: {}, hostCatalog: {}},
|
|
1346
|
+
})
|
|
1347
|
+
);
|
|
1348
|
+
|
|
1349
|
+
// warm from cache
|
|
1350
|
+
const warmed = await services._loadCatalogFromCache();
|
|
1351
|
+
assert.isTrue(warmed);
|
|
1352
|
+
assert.isTrue(catalog.status.preauth.ready);
|
|
1353
|
+
assert.isTrue(catalog.status.postauth.ready);
|
|
1354
|
+
|
|
1355
|
+
const fetchSpy = sinon.spy(services, '_fetchNewServiceHostmap');
|
|
1356
|
+
|
|
1357
|
+
// with forceRefresh we should fetch despite ready=true
|
|
1358
|
+
await services.updateServices({
|
|
1359
|
+
from: 'limited',
|
|
1360
|
+
query: {orgId: 'urn:EXAMPLE:org'},
|
|
1361
|
+
forceRefresh: true,
|
|
1362
|
+
});
|
|
1363
|
+
// pass an empty query to avoid spreading undefined in qs construction
|
|
1364
|
+
await services.updateServices({forceRefresh: true});
|
|
1365
|
+
|
|
1366
|
+
assert.isTrue(fetchSpy.called, 'forceRefresh should bypass cache short-circuit');
|
|
1367
|
+
fetchSpy.restore();
|
|
1368
|
+
});
|
|
1369
|
+
|
|
1370
|
+
it('stores selection metadata and env on cache write for preauth', async () => {
|
|
1371
|
+
const CATALOG_CACHE_KEY_V1 = 'services.v1.u2cHostMap';
|
|
1372
|
+
// arrange config for env fingerprint
|
|
1373
|
+
services.webex.config = services.webex.config || {};
|
|
1374
|
+
services.webex.config.services = services.webex.config.services || {discovery: {}};
|
|
1375
|
+
services.webex.config.services.discovery.u2c = 'https://u2c.wbx2.com/u2c/api/v1';
|
|
1376
|
+
services.webex.config.fedramp = false;
|
|
1377
|
+
|
|
1378
|
+
// write cache with meta
|
|
1379
|
+
await services._cacheCatalog(
|
|
1380
|
+
'preauth',
|
|
1381
|
+
{serviceLinks: {}, hostCatalog: {}},
|
|
1382
|
+
{selectionType: 'orgId', selectionValue: 'urn:EXAMPLE:org'}
|
|
1383
|
+
);
|
|
1384
|
+
|
|
1385
|
+
const raw = window.localStorage.getItem(CATALOG_CACHE_KEY_V1);
|
|
1386
|
+
assert.isString(raw);
|
|
1387
|
+
const parsed = JSON.parse(raw);
|
|
1388
|
+
assert.equal(parsed.orgId, undefined, 'orgId not set without credentials');
|
|
1389
|
+
assert.deepEqual(parsed.env, {
|
|
1390
|
+
fedramp: false,
|
|
1391
|
+
u2cDiscoveryUrl: 'https://u2c.wbx2.com/u2c/api/v1',
|
|
1392
|
+
});
|
|
1393
|
+
assert.isObject(parsed.preauth);
|
|
1394
|
+
assert.deepEqual(parsed.preauth.meta, {
|
|
1395
|
+
selectionType: 'orgId',
|
|
1396
|
+
selectionValue: 'urn:EXAMPLE:org',
|
|
1397
|
+
});
|
|
1398
|
+
});
|
|
1399
|
+
|
|
1400
|
+
it('warms preauth from cache when selection meta matches intended orgId', async () => {
|
|
1401
|
+
const CATALOG_CACHE_KEY_V1 = 'services.v1.u2cHostMap';
|
|
1402
|
+
// stub credentials
|
|
1403
|
+
services.webex.credentials = {
|
|
1404
|
+
canAuthorize: true,
|
|
1405
|
+
getOrgId: sinon.stub().returns('urn:EXAMPLE:org'),
|
|
1406
|
+
};
|
|
1407
|
+
// set current env to match cached env
|
|
1408
|
+
services.webex.config = services.webex.config || {};
|
|
1409
|
+
services.webex.config.services = services.webex.config.services || {discovery: {}};
|
|
1410
|
+
services.webex.config.services.discovery.u2c = 'https://u2c.wbx2.com/u2c/api/v1';
|
|
1411
|
+
services.webex.config.fedramp = false;
|
|
1412
|
+
// cache with matching orgId selection
|
|
1413
|
+
window.localStorage.setItem(
|
|
1414
|
+
CATALOG_CACHE_KEY_V1,
|
|
1415
|
+
JSON.stringify({
|
|
1416
|
+
cachedAt: Date.now(),
|
|
1417
|
+
env: {fedramp: false, u2cDiscoveryUrl: 'https://u2c.wbx2.com/u2c/api/v1'},
|
|
1418
|
+
preauth: {
|
|
1419
|
+
hostMap: {serviceLinks: {}, hostCatalog: {}},
|
|
1420
|
+
meta: {selectionType: 'orgId', selectionValue: 'urn:EXAMPLE:org'},
|
|
1421
|
+
},
|
|
1422
|
+
})
|
|
1423
|
+
);
|
|
1424
|
+
// formatter returns at least one entry to mark ready
|
|
1425
|
+
services._formatReceivedHostmap.restore && services._formatReceivedHostmap.restore();
|
|
1426
|
+
sinon
|
|
1427
|
+
.stub(services, '_formatReceivedHostmap')
|
|
1428
|
+
.callsFake(() => [
|
|
1429
|
+
{name: 'hydra', defaultUrl: 'https://api.ciscospark.com/v1', hosts: []},
|
|
1430
|
+
]);
|
|
1431
|
+
|
|
1432
|
+
const warmed = await services._loadCatalogFromCache();
|
|
1433
|
+
assert.isTrue(warmed);
|
|
1434
|
+
assert.isTrue(catalog.status.preauth.ready, 'preauth should be warmed on match');
|
|
1435
|
+
});
|
|
1436
|
+
|
|
1437
|
+
it('does not warm preauth when selection meta is proximity mode', async () => {
|
|
1438
|
+
const CATALOG_CACHE_KEY_V1 = 'services.v1.u2cHostMap';
|
|
1439
|
+
// cache with proximity mode selection
|
|
1440
|
+
window.localStorage.setItem(
|
|
1441
|
+
CATALOG_CACHE_KEY_V1,
|
|
1442
|
+
JSON.stringify({
|
|
1443
|
+
cachedAt: Date.now(),
|
|
1444
|
+
env: {fedramp: false, u2cDiscoveryUrl: 'https://u2c.wbx2.com/u2c/api/v1'},
|
|
1445
|
+
preauth: {
|
|
1446
|
+
hostMap: {serviceLinks: {}, hostCatalog: {}},
|
|
1447
|
+
meta: {selectionType: 'mode', selectionValue: 'DEFAULT_BY_PROXIMITY'},
|
|
1448
|
+
},
|
|
1449
|
+
})
|
|
1450
|
+
);
|
|
1451
|
+
services._formatReceivedHostmap.restore && services._formatReceivedHostmap.restore();
|
|
1452
|
+
sinon
|
|
1453
|
+
.stub(services, '_formatReceivedHostmap')
|
|
1454
|
+
.callsFake(() => [
|
|
1455
|
+
{name: 'hydra', defaultUrl: 'https://api.ciscospark.com/v1', hosts: []},
|
|
1456
|
+
]);
|
|
1457
|
+
|
|
1458
|
+
const warmed = await services._loadCatalogFromCache();
|
|
1459
|
+
// function returns true if overall cache path succeeded; we only verify group readiness
|
|
1460
|
+
assert.isFalse(catalog.status.preauth.ready, 'preauth should not warm for proximity mode');
|
|
1461
|
+
});
|
|
1462
|
+
|
|
1463
|
+
it('does not warm preauth when selection meta mismatches intended selection', async () => {
|
|
1464
|
+
const CATALOG_CACHE_KEY_V1 = 'services.v1.u2cHostMap';
|
|
1465
|
+
// authorized with org X
|
|
1466
|
+
services.webex.credentials = {
|
|
1467
|
+
canAuthorize: true,
|
|
1468
|
+
getOrgId: sinon.stub().returns('urn:EXAMPLE:org'),
|
|
1469
|
+
};
|
|
1470
|
+
// cache points to a different org
|
|
1471
|
+
window.localStorage.setItem(
|
|
1472
|
+
CATALOG_CACHE_KEY_V1,
|
|
1473
|
+
JSON.stringify({
|
|
1474
|
+
cachedAt: Date.now(),
|
|
1475
|
+
env: {fedramp: false, u2cDiscoveryUrl: 'https://u2c.wbx2.com/u2c/api/v1'},
|
|
1476
|
+
preauth: {
|
|
1477
|
+
hostMap: {serviceLinks: {}, hostCatalog: {}},
|
|
1478
|
+
meta: {selectionType: 'orgId', selectionValue: 'urn:DIFF:org'},
|
|
1479
|
+
},
|
|
1480
|
+
})
|
|
1481
|
+
);
|
|
1482
|
+
services._formatReceivedHostmap.restore && services._formatReceivedHostmap.restore();
|
|
1483
|
+
sinon
|
|
1484
|
+
.stub(services, '_formatReceivedHostmap')
|
|
1485
|
+
.callsFake(() => [
|
|
1486
|
+
{name: 'hydra', defaultUrl: 'https://api.ciscospark.com/v1', hosts: []},
|
|
1487
|
+
]);
|
|
1488
|
+
|
|
1489
|
+
await services._loadCatalogFromCache();
|
|
1490
|
+
assert.isFalse(
|
|
1491
|
+
catalog.status.preauth.ready,
|
|
1492
|
+
'preauth should not warm on selection mismatch'
|
|
1493
|
+
);
|
|
1494
|
+
});
|
|
1495
|
+
|
|
1496
|
+
it('skips warming when environment fingerprint mismatches', async () => {
|
|
1497
|
+
const CATALOG_CACHE_KEY_V1 = 'services.v1.u2cHostMap';
|
|
1498
|
+
// cached env differs from current env (different U2C URL)
|
|
1499
|
+
window.localStorage.setItem(
|
|
1500
|
+
CATALOG_CACHE_KEY_V1,
|
|
1501
|
+
JSON.stringify({
|
|
1502
|
+
cachedAt: Date.now(),
|
|
1503
|
+
env: {fedramp: false, u2cDiscoveryUrl: 'https://u2c.other.com/u2c/api/v1'},
|
|
1504
|
+
preauth: {
|
|
1505
|
+
hostMap: {serviceLinks: {}, hostCatalog: {}},
|
|
1506
|
+
meta: {selectionType: 'mode', selectionValue: 'DEFAULT_BY_PROXIMITY'},
|
|
1507
|
+
},
|
|
1508
|
+
})
|
|
1509
|
+
);
|
|
1510
|
+
// current env
|
|
1511
|
+
services.webex.config = services.webex.config || {};
|
|
1512
|
+
services.webex.config.services = services.webex.config.services || {discovery: {}};
|
|
1513
|
+
services.webex.config.services.discovery.u2c = 'https://u2c.wbx2.com/u2c/api/v1';
|
|
1514
|
+
services.webex.config.fedramp = false;
|
|
1515
|
+
|
|
1516
|
+
const warmed = await services._loadCatalogFromCache();
|
|
1517
|
+
assert.isFalse(warmed, 'env mismatch should skip warm and return false');
|
|
1518
|
+
assert.isFalse(catalog.status.preauth.ready);
|
|
1519
|
+
assert.isFalse(catalog.status.postauth.ready);
|
|
1520
|
+
});
|
|
1521
|
+
});
|
|
922
1522
|
});
|
|
923
1523
|
});
|
|
924
1524
|
/* eslint-enable no-underscore-dangle */
|