@webex/webex-core 3.12.0-next.9 → 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/credentials-config.js +12 -0
- package/dist/credentials-config.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/services.js +109 -29
- package/dist/lib/services/services.js.map +1 -1
- package/dist/lib/services-v2/services-v2.js +108 -26
- package/dist/lib/services-v2/services-v2.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/credentials-config.js +13 -0
- package/src/lib/batcher.js +25 -10
- package/src/lib/credentials/credentials.js +50 -3
- package/src/lib/services/services.js +86 -12
- package/src/lib/services-v2/services-v2.ts +85 -10
- package/test/integration/spec/services/service-catalog.js +6 -7
- package/test/integration/spec/services/services.js +17 -6
- package/test/integration/spec/services-v2/services-v2.js +17 -6
- package/test/unit/spec/credentials/credentials.js +133 -2
- package/test/unit/spec/lib/batcher.js +56 -0
- package/test/unit/spec/services/services.js +342 -122
- package/test/unit/spec/services-v2/services-v2.ts +236 -63
- 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,17 +495,20 @@ describe('webex-core', () => {
|
|
|
346
495
|
|
|
347
496
|
sinon.stub(services, '_formatReceivedHostmap').resolves(mapResponse);
|
|
348
497
|
sinon.stub(services, 'request').resolves({});
|
|
349
|
-
|
|
498
|
+
|
|
350
499
|
const mapResult = await services._fetchNewServiceHostmap({from: 'limited'});
|
|
351
500
|
|
|
352
501
|
assert.calledOnceWithExactly(services.request, {
|
|
353
502
|
method: 'GET',
|
|
354
503
|
service: 'u2c',
|
|
355
504
|
resource: '/limited/catalog',
|
|
356
|
-
qs: {format: 'hostmap'}
|
|
357
|
-
}
|
|
505
|
+
qs: {format: 'hostmap'},
|
|
506
|
+
});
|
|
507
|
+
assert.calledOnceWithExactly(
|
|
508
|
+
webex.internal.newMetrics.callDiagnosticLatencies.measureLatency,
|
|
509
|
+
sinon.match.func,
|
|
510
|
+
'internal.get.u2c.time'
|
|
358
511
|
);
|
|
359
|
-
assert.calledOnceWithExactly(webex.internal.newMetrics.callDiagnosticLatencies.measureLatency, sinon.match.func, 'internal.get.u2c.time');
|
|
360
512
|
});
|
|
361
513
|
|
|
362
514
|
it('checks service request rejects', async () => {
|
|
@@ -364,7 +516,7 @@ describe('webex-core', () => {
|
|
|
364
516
|
|
|
365
517
|
sinon.spy(services, '_formatReceivedHostmap');
|
|
366
518
|
sinon.stub(services, 'request').rejects(error);
|
|
367
|
-
|
|
519
|
+
|
|
368
520
|
const promise = services._fetchNewServiceHostmap({from: 'limited'});
|
|
369
521
|
const rejectedValue = await assert.isRejected(promise);
|
|
370
522
|
|
|
@@ -376,10 +528,13 @@ describe('webex-core', () => {
|
|
|
376
528
|
method: 'GET',
|
|
377
529
|
service: 'u2c',
|
|
378
530
|
resource: '/limited/catalog',
|
|
379
|
-
qs: {format: 'hostmap'}
|
|
380
|
-
}
|
|
531
|
+
qs: {format: 'hostmap'},
|
|
532
|
+
});
|
|
533
|
+
assert.calledOnceWithExactly(
|
|
534
|
+
webex.internal.newMetrics.callDiagnosticLatencies.measureLatency,
|
|
535
|
+
sinon.match.func,
|
|
536
|
+
'internal.get.u2c.time'
|
|
381
537
|
);
|
|
382
|
-
assert.calledOnceWithExactly(webex.internal.newMetrics.callDiagnosticLatencies.measureLatency, sinon.match.func, 'internal.get.u2c.time');
|
|
383
538
|
});
|
|
384
539
|
});
|
|
385
540
|
|
|
@@ -410,7 +565,6 @@ describe('webex-core', () => {
|
|
|
410
565
|
});
|
|
411
566
|
|
|
412
567
|
it('returns the original uri if the hostmap has no hosts for the host', () => {
|
|
413
|
-
|
|
414
568
|
services._hostCatalog = {
|
|
415
569
|
'example.com': [],
|
|
416
570
|
};
|
|
@@ -574,8 +728,7 @@ describe('webex-core', () => {
|
|
|
574
728
|
id: '0:0:0:different-e-x',
|
|
575
729
|
},
|
|
576
730
|
],
|
|
577
|
-
'example-f.com': [
|
|
578
|
-
],
|
|
731
|
+
'example-f.com': [],
|
|
579
732
|
},
|
|
580
733
|
format: 'hostmap',
|
|
581
734
|
};
|
|
@@ -783,7 +936,7 @@ describe('webex-core', () => {
|
|
|
783
936
|
defaultUrl: 'https://example-g.com/api/v1',
|
|
784
937
|
hosts: [],
|
|
785
938
|
name: 'example-g',
|
|
786
|
-
}
|
|
939
|
+
},
|
|
787
940
|
]);
|
|
788
941
|
});
|
|
789
942
|
|
|
@@ -837,34 +990,59 @@ describe('webex-core', () => {
|
|
|
837
990
|
assert.equal(webex.config.credentials.authorizeUrl, authUrl);
|
|
838
991
|
});
|
|
839
992
|
});
|
|
840
|
-
|
|
993
|
+
|
|
841
994
|
describe('#getMobiusClusters', () => {
|
|
842
995
|
it('returns unique mobius host entries from hostCatalog', () => {
|
|
843
996
|
// Arrange: two hostCatalog keys, with duplicate mobius host across keys
|
|
844
997
|
services._hostCatalog = {
|
|
845
998
|
'mobius-us-east-2.prod.infra.webex.com': [
|
|
846
|
-
{
|
|
847
|
-
|
|
848
|
-
|
|
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
|
+
},
|
|
1011
|
+
],
|
|
849
1012
|
|
|
850
1013
|
'mobius-eu-central-1.prod.infra.webex.com': [
|
|
851
|
-
{
|
|
852
|
-
|
|
853
|
-
|
|
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': [
|
|
854
1022
|
{host: 'wdm-a.webex.com', ttl: -1, priority: 5, id: 'urn:TEAM:xyz:wdm'},
|
|
855
|
-
]
|
|
1023
|
+
],
|
|
856
1024
|
};
|
|
857
|
-
|
|
1025
|
+
|
|
858
1026
|
// Act
|
|
859
1027
|
const clusters = services.getMobiusClusters();
|
|
860
|
-
|
|
1028
|
+
|
|
861
1029
|
// Assert
|
|
862
1030
|
// deduped; only mobius entries; keeps first seen mobius-a, then mobius-b
|
|
863
1031
|
assert.deepEqual(
|
|
864
1032
|
clusters.map(({host, id, ttl, priority}) => ({host, id, ttl, priority})),
|
|
865
1033
|
[
|
|
866
|
-
{
|
|
867
|
-
|
|
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
|
+
},
|
|
868
1046
|
]
|
|
869
1047
|
);
|
|
870
1048
|
});
|
|
@@ -873,31 +1051,31 @@ describe('webex-core', () => {
|
|
|
873
1051
|
describe('#isValidHost', () => {
|
|
874
1052
|
beforeEach(() => {
|
|
875
1053
|
// Setting up a mock host catalog
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
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
|
+
};
|
|
901
1079
|
});
|
|
902
1080
|
afterAll(() => {
|
|
903
1081
|
// Clean up the mock host catalog
|
|
@@ -979,7 +1157,7 @@ describe('webex-core', () => {
|
|
|
979
1157
|
let catalog;
|
|
980
1158
|
let localStorageBackup;
|
|
981
1159
|
let windowBackup;
|
|
982
|
-
|
|
1160
|
+
|
|
983
1161
|
const makeLocalStorageShim = () => {
|
|
984
1162
|
const store = new Map();
|
|
985
1163
|
return {
|
|
@@ -989,13 +1167,16 @@ describe('webex-core', () => {
|
|
|
989
1167
|
_store: store,
|
|
990
1168
|
};
|
|
991
1169
|
};
|
|
992
|
-
|
|
1170
|
+
|
|
993
1171
|
beforeEach(() => {
|
|
994
1172
|
// Build a fresh webex instance
|
|
995
|
-
webex = new MockWebex({
|
|
1173
|
+
webex = new MockWebex({
|
|
1174
|
+
children: {services: Services},
|
|
1175
|
+
config: {credentials: {federation: true}},
|
|
1176
|
+
});
|
|
996
1177
|
services = webex.internal.services;
|
|
997
1178
|
catalog = services._getCatalog();
|
|
998
|
-
|
|
1179
|
+
|
|
999
1180
|
// enable U2C caching feature flag in tests that rely on localStorage writes/reads
|
|
1000
1181
|
services.webex.config = services.webex.config || {};
|
|
1001
1182
|
services.webex.config.calling = {...(services.webex.config.calling || {}), cacheU2C: true};
|
|
@@ -1007,13 +1188,15 @@ describe('webex-core', () => {
|
|
|
1007
1188
|
global.window.localStorage = makeLocalStorageShim();
|
|
1008
1189
|
// Ensure code under test uses our shim via util method
|
|
1009
1190
|
sinon.stub(services, '_getLocalStorageSafe').returns(global.window.localStorage);
|
|
1010
|
-
|
|
1191
|
+
|
|
1011
1192
|
// Stub the formatter so we don't need a full hostmap payload in tests
|
|
1012
|
-
sinon
|
|
1013
|
-
|
|
1014
|
-
|
|
1193
|
+
sinon
|
|
1194
|
+
.stub(services, '_formatReceivedHostmap')
|
|
1195
|
+
.callsFake(() => [
|
|
1196
|
+
{name: 'hydra', defaultUrl: 'https://api.ciscospark.com/v1', hosts: []},
|
|
1197
|
+
]);
|
|
1015
1198
|
});
|
|
1016
|
-
|
|
1199
|
+
|
|
1017
1200
|
afterEach(() => {
|
|
1018
1201
|
global.window.localStorage = localStorageBackup || undefined;
|
|
1019
1202
|
if (!windowBackup) {
|
|
@@ -1026,7 +1209,7 @@ describe('webex-core', () => {
|
|
|
1026
1209
|
services._getLocalStorageSafe.restore();
|
|
1027
1210
|
}
|
|
1028
1211
|
});
|
|
1029
|
-
|
|
1212
|
+
|
|
1030
1213
|
it('invokes initServiceCatalogs on ready, caches catalog, and stores in localStorage', async () => {
|
|
1031
1214
|
// Arrange: authenticated credentials and spies
|
|
1032
1215
|
services.webex.credentials = {
|
|
@@ -1038,10 +1221,20 @@ describe('webex-core', () => {
|
|
|
1038
1221
|
const cacheSpy = sinon.spy(services, '_cacheCatalog');
|
|
1039
1222
|
const setItemSpy = sinon.spy(global.window.localStorage, 'setItem');
|
|
1040
1223
|
// Make fetch return a hostmap object and allow formatter to reduce it
|
|
1041
|
-
sinon
|
|
1042
|
-
|
|
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
|
|
1043
1236
|
services.listenToOnce = sinon.stub().callsFake((ctx, event, cb) => {
|
|
1044
|
-
if (event === '
|
|
1237
|
+
if (event === 'loaded') cb();
|
|
1045
1238
|
});
|
|
1046
1239
|
|
|
1047
1240
|
// Act
|
|
@@ -1081,9 +1274,9 @@ describe('webex-core', () => {
|
|
|
1081
1274
|
|
|
1082
1275
|
const initSpy = sinon.spy(services, 'initServiceCatalogs');
|
|
1083
1276
|
const cacheSpy = sinon.spy(services, '_cacheCatalog');
|
|
1084
|
-
// Cause
|
|
1277
|
+
// Cause loaded callback to run immediately
|
|
1085
1278
|
services.listenToOnce = sinon.stub().callsFake((ctx, event, cb) => {
|
|
1086
|
-
if (event === '
|
|
1279
|
+
if (event === 'loaded') cb();
|
|
1087
1280
|
});
|
|
1088
1281
|
|
|
1089
1282
|
// Act
|
|
@@ -1091,9 +1284,18 @@ describe('webex-core', () => {
|
|
|
1091
1284
|
await waitForAsync();
|
|
1092
1285
|
|
|
1093
1286
|
// Assert: ready path found cache and skipped initServiceCatalogs
|
|
1094
|
-
assert.isFalse(
|
|
1095
|
-
|
|
1096
|
-
|
|
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
|
+
);
|
|
1097
1299
|
assert.isFalse(cacheSpy.called, 'should not write cache during warm-up-only path');
|
|
1098
1300
|
|
|
1099
1301
|
// Cleanup
|
|
@@ -1109,26 +1311,29 @@ describe('webex-core', () => {
|
|
|
1109
1311
|
preauth: {serviceLinks: {}, hostCatalog: {}},
|
|
1110
1312
|
postauth: {serviceLinks: {}, hostCatalog: {}},
|
|
1111
1313
|
};
|
|
1112
|
-
|
|
1314
|
+
|
|
1113
1315
|
window.localStorage.setItem(CATALOG_CACHE_KEY_V1, JSON.stringify(staleCached));
|
|
1114
|
-
|
|
1316
|
+
|
|
1115
1317
|
const warmed = await services._loadCatalogFromCache();
|
|
1116
|
-
|
|
1318
|
+
|
|
1117
1319
|
assert.isFalse(warmed, 'stale cache must not warm');
|
|
1118
|
-
assert.isNull(
|
|
1320
|
+
assert.isNull(
|
|
1321
|
+
window.localStorage.getItem(CATALOG_CACHE_KEY_V1),
|
|
1322
|
+
'expired cache must be cleared'
|
|
1323
|
+
);
|
|
1119
1324
|
assert.isFalse(catalog.status.preauth.ready);
|
|
1120
1325
|
assert.isFalse(catalog.status.postauth.ready);
|
|
1121
1326
|
});
|
|
1122
|
-
|
|
1327
|
+
|
|
1123
1328
|
it('clearCatalogCache() removes the cached entry', async () => {
|
|
1124
1329
|
const CATALOG_CACHE_KEY_V1 = 'services.v1.u2cHostMap';
|
|
1125
1330
|
window.localStorage.setItem(CATALOG_CACHE_KEY_V1, JSON.stringify({cachedAt: Date.now()}));
|
|
1126
|
-
|
|
1331
|
+
|
|
1127
1332
|
await services.clearCatalogCache();
|
|
1128
|
-
|
|
1333
|
+
|
|
1129
1334
|
assert.isNull(window.localStorage.getItem(CATALOG_CACHE_KEY_V1), 'cache should be cleared');
|
|
1130
1335
|
});
|
|
1131
|
-
|
|
1336
|
+
|
|
1132
1337
|
it('still fetches when forceRefresh=true even if ready', async () => {
|
|
1133
1338
|
const CATALOG_CACHE_KEY_V1 = 'services.v1.u2cHostMap';
|
|
1134
1339
|
window.localStorage.setItem(
|
|
@@ -1140,20 +1345,24 @@ describe('webex-core', () => {
|
|
|
1140
1345
|
postauth: {serviceLinks: {}, hostCatalog: {}},
|
|
1141
1346
|
})
|
|
1142
1347
|
);
|
|
1143
|
-
|
|
1348
|
+
|
|
1144
1349
|
// warm from cache
|
|
1145
1350
|
const warmed = await services._loadCatalogFromCache();
|
|
1146
1351
|
assert.isTrue(warmed);
|
|
1147
1352
|
assert.isTrue(catalog.status.preauth.ready);
|
|
1148
1353
|
assert.isTrue(catalog.status.postauth.ready);
|
|
1149
|
-
|
|
1354
|
+
|
|
1150
1355
|
const fetchSpy = sinon.spy(services, '_fetchNewServiceHostmap');
|
|
1151
|
-
|
|
1356
|
+
|
|
1152
1357
|
// with forceRefresh we should fetch despite ready=true
|
|
1153
|
-
await services.updateServices({
|
|
1358
|
+
await services.updateServices({
|
|
1359
|
+
from: 'limited',
|
|
1360
|
+
query: {orgId: 'urn:EXAMPLE:org'},
|
|
1361
|
+
forceRefresh: true,
|
|
1362
|
+
});
|
|
1154
1363
|
// pass an empty query to avoid spreading undefined in qs construction
|
|
1155
1364
|
await services.updateServices({forceRefresh: true});
|
|
1156
|
-
|
|
1365
|
+
|
|
1157
1366
|
assert.isTrue(fetchSpy.called, 'forceRefresh should bypass cache short-circuit');
|
|
1158
1367
|
fetchSpy.restore();
|
|
1159
1368
|
});
|
|
@@ -1214,9 +1423,11 @@ describe('webex-core', () => {
|
|
|
1214
1423
|
);
|
|
1215
1424
|
// formatter returns at least one entry to mark ready
|
|
1216
1425
|
services._formatReceivedHostmap.restore && services._formatReceivedHostmap.restore();
|
|
1217
|
-
sinon
|
|
1218
|
-
|
|
1219
|
-
|
|
1426
|
+
sinon
|
|
1427
|
+
.stub(services, '_formatReceivedHostmap')
|
|
1428
|
+
.callsFake(() => [
|
|
1429
|
+
{name: 'hydra', defaultUrl: 'https://api.ciscospark.com/v1', hosts: []},
|
|
1430
|
+
]);
|
|
1220
1431
|
|
|
1221
1432
|
const warmed = await services._loadCatalogFromCache();
|
|
1222
1433
|
assert.isTrue(warmed);
|
|
@@ -1238,9 +1449,11 @@ describe('webex-core', () => {
|
|
|
1238
1449
|
})
|
|
1239
1450
|
);
|
|
1240
1451
|
services._formatReceivedHostmap.restore && services._formatReceivedHostmap.restore();
|
|
1241
|
-
sinon
|
|
1242
|
-
|
|
1243
|
-
|
|
1452
|
+
sinon
|
|
1453
|
+
.stub(services, '_formatReceivedHostmap')
|
|
1454
|
+
.callsFake(() => [
|
|
1455
|
+
{name: 'hydra', defaultUrl: 'https://api.ciscospark.com/v1', hosts: []},
|
|
1456
|
+
]);
|
|
1244
1457
|
|
|
1245
1458
|
const warmed = await services._loadCatalogFromCache();
|
|
1246
1459
|
// function returns true if overall cache path succeeded; we only verify group readiness
|
|
@@ -1267,12 +1480,17 @@ describe('webex-core', () => {
|
|
|
1267
1480
|
})
|
|
1268
1481
|
);
|
|
1269
1482
|
services._formatReceivedHostmap.restore && services._formatReceivedHostmap.restore();
|
|
1270
|
-
sinon
|
|
1271
|
-
|
|
1272
|
-
|
|
1483
|
+
sinon
|
|
1484
|
+
.stub(services, '_formatReceivedHostmap')
|
|
1485
|
+
.callsFake(() => [
|
|
1486
|
+
{name: 'hydra', defaultUrl: 'https://api.ciscospark.com/v1', hosts: []},
|
|
1487
|
+
]);
|
|
1273
1488
|
|
|
1274
1489
|
await services._loadCatalogFromCache();
|
|
1275
|
-
assert.isFalse(
|
|
1490
|
+
assert.isFalse(
|
|
1491
|
+
catalog.status.preauth.ready,
|
|
1492
|
+
'preauth should not warm on selection mismatch'
|
|
1493
|
+
);
|
|
1276
1494
|
});
|
|
1277
1495
|
|
|
1278
1496
|
it('skips warming when environment fingerprint mismatches', async () => {
|
|
@@ -1283,7 +1501,10 @@ describe('webex-core', () => {
|
|
|
1283
1501
|
JSON.stringify({
|
|
1284
1502
|
cachedAt: Date.now(),
|
|
1285
1503
|
env: {fedramp: false, u2cDiscoveryUrl: 'https://u2c.other.com/u2c/api/v1'},
|
|
1286
|
-
preauth: {
|
|
1504
|
+
preauth: {
|
|
1505
|
+
hostMap: {serviceLinks: {}, hostCatalog: {}},
|
|
1506
|
+
meta: {selectionType: 'mode', selectionValue: 'DEFAULT_BY_PROXIMITY'},
|
|
1507
|
+
},
|
|
1287
1508
|
})
|
|
1288
1509
|
);
|
|
1289
1510
|
// current env
|
|
@@ -1298,7 +1519,6 @@ describe('webex-core', () => {
|
|
|
1298
1519
|
assert.isFalse(catalog.status.postauth.ready);
|
|
1299
1520
|
});
|
|
1300
1521
|
});
|
|
1301
|
-
|
|
1302
1522
|
});
|
|
1303
1523
|
});
|
|
1304
1524
|
/* eslint-enable no-underscore-dangle */
|