@webex/webex-core 3.8.1 → 3.9.0

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.
Files changed (85) hide show
  1. package/README.md +87 -27
  2. package/dist/index.js +9 -33
  3. package/dist/index.js.map +1 -1
  4. package/dist/lib/batcher.js +1 -1
  5. package/dist/lib/constants.js +10 -1
  6. package/dist/lib/constants.js.map +1 -1
  7. package/dist/lib/credentials/credentials.js +1 -1
  8. package/dist/lib/credentials/token.js +1 -1
  9. package/dist/lib/{services-v2/interceptors → interceptors}/server-error.js +1 -1
  10. package/dist/lib/interceptors/server-error.js.map +1 -0
  11. package/dist/lib/services/index.js +2 -29
  12. package/dist/lib/services/index.js.map +1 -1
  13. package/dist/lib/services/service-host.js +1 -1
  14. package/dist/lib/services/service-host.js.map +1 -1
  15. package/dist/lib/services/service-registry.js +1 -1
  16. package/dist/lib/services/service-registry.js.map +1 -1
  17. package/dist/lib/services/service-state.js +1 -1
  18. package/dist/lib/services/service-state.js.map +1 -1
  19. package/dist/lib/services/services.js +3 -3
  20. package/dist/lib/services/services.js.map +1 -1
  21. package/dist/lib/services-v2/index.js +0 -29
  22. package/dist/lib/services-v2/index.js.map +1 -1
  23. package/dist/lib/services-v2/metrics.js.map +1 -1
  24. package/dist/lib/services-v2/service-catalog.js +11 -10
  25. package/dist/lib/services-v2/service-catalog.js.map +1 -1
  26. package/dist/lib/services-v2/services-v2.js +56 -67
  27. package/dist/lib/services-v2/services-v2.js.map +1 -1
  28. package/dist/lib/services-v2/types.js.map +1 -1
  29. package/dist/plugins/logger.js +1 -1
  30. package/dist/webex-core.js +3 -3
  31. package/dist/webex-core.js.map +1 -1
  32. package/package.json +13 -13
  33. package/src/index.js +5 -14
  34. package/src/lib/constants.js +29 -1
  35. package/src/lib/{services/interceptors → interceptors}/server-error.js +1 -1
  36. package/src/lib/services/index.js +2 -7
  37. package/src/lib/services/service-host.js +1 -1
  38. package/src/lib/services/service-registry.js +1 -1
  39. package/src/lib/services/service-state.js +1 -1
  40. package/src/lib/services/services.js +2 -2
  41. package/src/lib/services-v2/index.ts +0 -16
  42. package/src/lib/services-v2/service-catalog.ts +20 -19
  43. package/src/lib/services-v2/{services-v2.js → services-v2.ts} +116 -94
  44. package/src/lib/services-v2/types.ts +62 -2
  45. package/src/webex-core.js +1 -1
  46. package/test/fixtures/host-catalog-v2.ts +30 -122
  47. package/test/integration/spec/services/services.js +11 -0
  48. package/test/integration/spec/services-v2/service-catalog.js +664 -0
  49. package/test/integration/spec/services-v2/services-v2.js +1061 -0
  50. package/test/unit/spec/services-v2/service-detail.ts +1 -1
  51. package/test/unit/spec/services-v2/services-v2.ts +390 -436
  52. package/dist/lib/services/constants.js +0 -17
  53. package/dist/lib/services/constants.js.map +0 -1
  54. package/dist/lib/services/interceptors/hostmap.js +0 -64
  55. package/dist/lib/services/interceptors/hostmap.js.map +0 -1
  56. package/dist/lib/services/interceptors/server-error.js +0 -77
  57. package/dist/lib/services/interceptors/server-error.js.map +0 -1
  58. package/dist/lib/services/interceptors/service.js +0 -137
  59. package/dist/lib/services/interceptors/service.js.map +0 -1
  60. package/dist/lib/services-v2/constants.js +0 -17
  61. package/dist/lib/services-v2/constants.js.map +0 -1
  62. package/dist/lib/services-v2/interceptors/server-error.js.map +0 -1
  63. package/dist/lib/services-v2/service-host.js +0 -300
  64. package/dist/lib/services-v2/service-host.js.map +0 -1
  65. package/dist/lib/services-v2/service-registry.js +0 -534
  66. package/dist/lib/services-v2/service-registry.js.map +0 -1
  67. package/dist/lib/services-v2/service-state.js +0 -97
  68. package/dist/lib/services-v2/service-state.js.map +0 -1
  69. package/dist/lib/services-v2/service-url.js +0 -119
  70. package/dist/lib/services-v2/service-url.js.map +0 -1
  71. package/src/lib/services/constants.js +0 -21
  72. package/src/lib/services/interceptors/hostmap.js +0 -36
  73. package/src/lib/services/interceptors/service.js +0 -101
  74. package/src/lib/services-v2/constants.ts +0 -21
  75. package/src/lib/services-v2/interceptors/server-error.js +0 -48
  76. /package/dist/lib/{services-v2/interceptors → interceptors}/hostmap.js +0 -0
  77. /package/dist/lib/{services-v2/interceptors → interceptors}/hostmap.js.map +0 -0
  78. /package/dist/lib/{services-v2/interceptors → interceptors}/service.js +0 -0
  79. /package/dist/lib/{services-v2/interceptors → interceptors}/service.js.map +0 -0
  80. /package/dist/lib/{services/metrics.js → metrics.js} +0 -0
  81. /package/dist/lib/{services/metrics.js.map → metrics.js.map} +0 -0
  82. /package/src/lib/{services-v2/interceptors → interceptors}/hostmap.js +0 -0
  83. /package/src/lib/{services-v2/interceptors → interceptors}/service.js +0 -0
  84. /package/src/lib/{services-v2/metrics.js → metrics.js} +0 -0
  85. /package/src/lib/{services/metrics.js → services-v2/metrics.ts} +0 -0
@@ -0,0 +1,1061 @@
1
+ // /*!
2
+ // * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.
3
+ // */
4
+
5
+ import '@webex/internal-plugin-device';
6
+
7
+ import {assert} from '@webex/test-helper-chai';
8
+ import {flaky} from '@webex/test-helper-mocha';
9
+ import WebexCore, {
10
+ ServiceCatalogV2,
11
+ ServiceDetail,
12
+ serviceConstants,
13
+ registerInternalPlugin,
14
+ Services,
15
+ ServiceInterceptor,
16
+ ServerErrorInterceptor,
17
+ ServicesV2,
18
+ } from '@webex/webex-core';
19
+ import testUsers from '@webex/test-helper-test-users';
20
+ import uuid from 'uuid';
21
+ import sinon from 'sinon';
22
+ import {
23
+ formattedServiceHostmapEntryConv,
24
+ formattedServiceHostmapEntryMercury,
25
+ formattedServiceHostmapEntryTest,
26
+ } from '../../../fixtures/host-catalog-v2';
27
+
28
+ // /* eslint-disable no-underscore-dangle */
29
+ describe('webex-core', () => {
30
+ describe('ServicesV2', () => {
31
+ let webexUser;
32
+ let webexUserEU;
33
+ let webex;
34
+ let webexEU;
35
+ let services;
36
+ let servicesEU;
37
+ let catalog;
38
+ let catalogEU;
39
+
40
+ before('create users', () =>
41
+ Promise.all([
42
+ testUsers.create({count: 1}),
43
+ testUsers.create({
44
+ count: 1,
45
+ config: {
46
+ orgId: process.env.EU_PRIMARY_ORG_ID,
47
+ },
48
+ }),
49
+ ]).then(
50
+ ([[user], [userEU]]) =>
51
+ new Promise((resolve) => {
52
+ setTimeout(() => {
53
+ webexUser = user;
54
+ webexUserEU = userEU;
55
+ resolve();
56
+ }, 1000);
57
+ })
58
+ )
59
+ );
60
+
61
+ beforeEach(() => {
62
+ registerInternalPlugin('services', ServicesV2, {
63
+ interceptors: {
64
+ ServiceInterceptor: ServiceInterceptor.create,
65
+ ServerErrorInterceptor: ServerErrorInterceptor.create,
66
+ },
67
+ replace: true,
68
+ });
69
+ webex = new WebexCore({credentials: {supertoken: webexUser.token}});
70
+ webexEU = new WebexCore({credentials: {supertoken: webexUserEU.token}});
71
+ services = webex.internal.services;
72
+ servicesEU = webexEU.internal.services;
73
+ catalog = services._getCatalog();
74
+ catalogEU = servicesEU._getCatalog();
75
+
76
+ return Promise.all([
77
+ services.waitForCatalog('postauth', 10),
78
+ servicesEU.waitForCatalog('postauth', 10),
79
+ ]).then(() =>
80
+ services.updateServices({
81
+ from: 'limited',
82
+ query: {userId: webexUser.id},
83
+ })
84
+ );
85
+ });
86
+
87
+ afterEach(() => {
88
+ registerInternalPlugin('services', Services, {
89
+ interceptors: {
90
+ ServiceInterceptor: ServiceInterceptor.create,
91
+ ServerErrorInterceptor: ServerErrorInterceptor.create,
92
+ },
93
+ replace: true,
94
+ });
95
+ services = webex.internal.services;
96
+ servicesEU = webexEU.internal.services;
97
+ catalog = services._getCatalog();
98
+ catalogEU = servicesEU._getCatalog();
99
+ });
100
+
101
+ describe('#_getCatalog()', () => {
102
+ it('returns a catalog', () => {
103
+ const localCatalog = services._getCatalog();
104
+
105
+ assert.equal(localCatalog.namespace, 'ServiceCatalog');
106
+ });
107
+ });
108
+
109
+ describe('#get()', () => {
110
+ let testDetailTemplate;
111
+ let testDetail;
112
+
113
+ beforeEach(() => {
114
+ testDetailTemplate = formattedServiceHostmapEntryConv;
115
+ testDetail = new ServiceDetail(testDetailTemplate);
116
+ catalog._loadServiceDetails('preauth', [testDetail]);
117
+ services._activeServices = {
118
+ [testDetailTemplate.serviceName]: testDetailTemplate.id,
119
+ };
120
+ });
121
+
122
+ afterEach(() => {
123
+ catalog._unloadServiceDetails('preauth', [testDetail]);
124
+ });
125
+
126
+ it('returns a valid string when name is specified', () => {
127
+ const url = services.get(testDetailTemplate.serviceName);
128
+
129
+ assert.typeOf(url, 'string');
130
+ assert.equal(url, testDetail.get());
131
+ });
132
+
133
+ it("returns undefined if url doesn't exist", () => {
134
+ const s = services.get('invalidUrl');
135
+
136
+ assert.typeOf(s, 'undefined');
137
+ });
138
+
139
+ it('gets a service from a specific serviceGroup', () => {
140
+ assert.isDefined(services.get(testDetailTemplate.serviceName, 'preauth'));
141
+ });
142
+
143
+ it("fails to get a service if serviceGroup isn't accurate", () => {
144
+ assert.isUndefined(services.get(testDetailTemplate.serviceName, 'discovery'));
145
+ });
146
+ });
147
+
148
+ describe('#getClusterId()', () => {
149
+ let testDetailTemplate;
150
+ let testDetail;
151
+
152
+ beforeEach(() => {
153
+ testDetailTemplate = formattedServiceHostmapEntryConv;
154
+ testDetail = new ServiceDetail(testDetailTemplate);
155
+ catalog._loadServiceDetails('preauth', [testDetail]);
156
+ });
157
+
158
+ it('returns a clusterId when found with url', () => {
159
+ assert.equal(services.getClusterId(testDetail.get()), testDetail.id);
160
+ });
161
+
162
+ it('returns a clusterId when found with resource-appended url', () => {
163
+ assert.equal(
164
+ services.getClusterId(`${testDetail.get()}example/resource/value`),
165
+ testDetail.id
166
+ );
167
+ });
168
+
169
+ it("returns undefined when the url doesn't exist in catalog", () => {
170
+ assert.isUndefined(services.getClusterId('http://not-a-known-url.com/'));
171
+ });
172
+
173
+ it("returns undefined when the string isn't a url", () => {
174
+ assert.isUndefined(services.getClusterId('not a url'));
175
+ });
176
+ });
177
+
178
+ describe('#getServiceFromClusterId()', () => {
179
+ let testDetailTemplate;
180
+ let testDetail;
181
+
182
+ beforeEach(() => {
183
+ testDetailTemplate = formattedServiceHostmapEntryConv;
184
+ testDetail = new ServiceDetail(testDetailTemplate);
185
+ catalog._loadServiceDetails('preauth', [testDetail]);
186
+ });
187
+
188
+ it('finds a valid service url from only a clusterId', () => {
189
+ const serviceFound = services.getServiceFromClusterId({
190
+ clusterId: testDetailTemplate.id,
191
+ });
192
+
193
+ assert.equal(serviceFound.name, testDetail.serviceName);
194
+ assert.equal(serviceFound.url, testDetail.get());
195
+ });
196
+
197
+ it('finds a valid service when a service group is defined', () => {
198
+ const serviceFound = catalog.findServiceFromClusterId({
199
+ clusterId: testDetailTemplate.id,
200
+ serviceGroup: 'preauth',
201
+ });
202
+
203
+ assert.equal(serviceFound.name, testDetail.serviceName);
204
+ assert.equal(serviceFound.url, testDetail.get());
205
+ });
206
+
207
+ it("fails to find a valid service when it's not in a group", () => {
208
+ assert.isUndefined(
209
+ services.getServiceFromClusterId({
210
+ clusterId: testDetailTemplate.id,
211
+ serviceGroup: 'signin',
212
+ })
213
+ );
214
+ });
215
+
216
+ it("returns undefined when service doesn't exist", () => {
217
+ assert.isUndefined(services.getServiceFromClusterId({clusterId: 'not a clusterId'}));
218
+ });
219
+ });
220
+
221
+ describe('#getServiceFromUrl()', () => {
222
+ let testDetailTemplate;
223
+ let testDetail;
224
+
225
+ beforeEach(() => {
226
+ testDetailTemplate = formattedServiceHostmapEntryConv;
227
+ testDetail = new ServiceDetail(testDetailTemplate);
228
+ catalog._loadServiceDetails('preauth', [testDetail]);
229
+ });
230
+
231
+ afterEach(() => {
232
+ catalog._unloadServiceDetails('preauth', [testDetail]);
233
+ });
234
+
235
+ it('gets a valid service object from an existing service', () => {
236
+ const serviceObject = services.getServiceFromUrl(testDetail.get());
237
+
238
+ assert.isDefined(serviceObject);
239
+ assert.hasAllKeys(serviceObject, ['name', 'defaultUrl', 'priorityUrl']);
240
+
241
+ assert.equal(testDetailTemplate.serviceName, serviceObject.name);
242
+ assert.equal(testDetail.get(true), serviceObject.defaultUrl);
243
+ assert.equal(testDetail.get(true), serviceObject.priorityUrl);
244
+ });
245
+
246
+ it("returns undefined when the service url doesn't exist", () => {
247
+ const serviceObject = services.getServiceFromUrl('http://www.not-real.com/');
248
+
249
+ assert.isUndefined(serviceObject);
250
+ });
251
+ });
252
+
253
+ describe('#initConfig()', () => {
254
+ it('should set the discovery catalog based on the provided links', () => {
255
+ const key = 'test';
256
+ const url = 'http://www.test.com/';
257
+
258
+ webex.config.services.discovery[key] = url;
259
+
260
+ services.initConfig();
261
+
262
+ assert.equal(services.get(key), url);
263
+ });
264
+
265
+ it('should set the override catalog based on the provided links', () => {
266
+ const key = 'testOverride';
267
+ const url = 'http://www.test-override.com/';
268
+
269
+ webex.config.services.override = {};
270
+ webex.config.services.override[key] = url;
271
+
272
+ services.initConfig();
273
+
274
+ assert.equal(services.get(key), url);
275
+ });
276
+
277
+ it('should set validate domains to true when provided true', () => {
278
+ webex.config.services.validateDomains = true;
279
+
280
+ services.initConfig();
281
+
282
+ assert.isTrue(services.validateDomains);
283
+ });
284
+
285
+ it('should set validate domains to false when provided false', () => {
286
+ webex.config.services.validateDomains = false;
287
+
288
+ services.initConfig();
289
+
290
+ assert.isFalse(services.validateDomains);
291
+ });
292
+
293
+ it('should set the allowed domains based on the provided domains', () => {
294
+ const allowedDomains = ['domain'];
295
+
296
+ webex.config.services.allowedDomains = allowedDomains;
297
+
298
+ services.initConfig();
299
+
300
+ const expectedResult = [...allowedDomains, ...serviceConstants.COMMERCIAL_ALLOWED_DOMAINS];
301
+
302
+ assert.deepEqual(expectedResult, services._getCatalog().allowedDomains);
303
+ });
304
+ });
305
+
306
+ describe('#initialize()', () => {
307
+ it('should create a catalog', () =>
308
+ assert.instanceOf(services._getCatalog(), ServiceCatalogV2));
309
+
310
+ it('should call services#initConfig() when webex config changes', () => {
311
+ services.initConfig = sinon.spy();
312
+ services.initialize();
313
+ webex.trigger('change:config');
314
+ assert.called(services.initConfig);
315
+ assert.isTrue(catalog.isReady);
316
+ });
317
+
318
+ it('should call services#initServiceCatalogs() on webex ready', () => {
319
+ services.initServiceCatalogs = sinon.stub().resolves();
320
+ services.initialize();
321
+ webex.trigger('ready');
322
+ assert.called(services.initServiceCatalogs);
323
+ assert.isTrue(catalog.isReady);
324
+ });
325
+
326
+ it('should collect different catalogs based on OrgId region', () =>
327
+ assert.notDeepEqual(catalog._getAllServiceDetails(), catalogEU._getAllServiceDetails()));
328
+
329
+ it('should not attempt to collect catalogs without authorization', (done) => {
330
+ const otherWebex = new WebexCore();
331
+ const initServiceCatalogs = sinon.stub(otherWebex.internal.services, 'initServiceCatalogs');
332
+
333
+ setTimeout(() => {
334
+ assert.notCalled(initServiceCatalogs);
335
+ assert.isFalse(otherWebex.internal.services._getCatalog().isReady);
336
+ otherWebex.internal.services.initServiceCatalogs.restore();
337
+ done();
338
+ }, 2000);
339
+ });
340
+ });
341
+
342
+ describe('#initServiceCatalogs()', () => {
343
+ it('should reject if a OrgId cannot be retrieved', () => {
344
+ webex.credentials.getOrgId = sinon.stub().throws();
345
+
346
+ return assert.isRejected(services.initServiceCatalogs());
347
+ });
348
+
349
+ it('should call services#collectPreauthCatalog with the OrgId', () => {
350
+ services.collectPreauthCatalog = sinon.stub().resolves();
351
+
352
+ return services.initServiceCatalogs().then(() =>
353
+ assert.calledWith(
354
+ services.collectPreauthCatalog,
355
+ sinon.match({
356
+ orgId: webex.credentials.getOrgId(),
357
+ })
358
+ )
359
+ );
360
+ });
361
+
362
+ it('should not call services#updateServices() when not authed', () => {
363
+ services.updateServices = sinon.stub().resolves();
364
+
365
+ // Since credentials uses AmpState, we have to set the derived
366
+ // properties of the dependent properties to undefined.
367
+ webex.credentials.supertoken.access_token = undefined;
368
+ webex.credentials.supertoken.refresh_token = undefined;
369
+
370
+ webex.credentials.getOrgId = sinon.stub().returns(webexUser.orgId);
371
+
372
+ return (
373
+ services
374
+ .initServiceCatalogs()
375
+ // services#updateServices() gets called once by the limited catalog
376
+ // retrieval and should not be called again when not authorized.
377
+ .then(() => assert.calledOnce(services.updateServices))
378
+ );
379
+ });
380
+
381
+ it('should call services#updateServices() when authed', () => {
382
+ services.updateServices = sinon.stub().resolves();
383
+
384
+ return (
385
+ services
386
+ .initServiceCatalogs()
387
+ // services#updateServices() gets called once by the limited catalog
388
+ // retrieval and should get called again when authorized.
389
+ .then(() => assert.calledTwice(services.updateServices))
390
+ );
391
+ });
392
+ });
393
+
394
+ describe('#isAllowedDomainUrl()', () => {
395
+ let list;
396
+
397
+ beforeEach(() => {
398
+ catalog.setAllowedDomains(['some-domain-a', 'some-domain-b']);
399
+
400
+ list = catalog.getAllowedDomains();
401
+ });
402
+
403
+ it('returns a boolean', () => {
404
+ assert.isBoolean(services.isAllowedDomainUrl('https://not-a-domain/resource'));
405
+ });
406
+
407
+ it('returns true if the url contains an allowed domain', () => {
408
+ assert.isTrue(services.isAllowedDomainUrl(`https://${list[0]}/resource`));
409
+ });
410
+
411
+ it('returns false if the url does not contain an allowed domain', () => {
412
+ assert.isFalse(services.isAllowedDomainUrl('https://bad-domain/resource'));
413
+ });
414
+ });
415
+
416
+ describe('#convertUrlToPriorityUrl', () => {
417
+ let testDetail;
418
+ let testDetailTemplate;
419
+
420
+ beforeEach(() => {
421
+ testDetailTemplate = formattedServiceHostmapEntryConv;
422
+ testDetail = new ServiceDetail(testDetailTemplate);
423
+ catalog._loadServiceDetails('preauth', [testDetail]);
424
+ });
425
+
426
+ it('converts the url to a priority host url', () => {
427
+ const resource = 'path/to/resource';
428
+ const url = `${testDetailTemplate.serviceUrls[1].baseUrl}/${resource}`;
429
+
430
+ const convertUrl = services.convertUrlToPriorityHostUrl(url);
431
+
432
+ assert.isDefined(convertUrl);
433
+ assert.isTrue(convertUrl.includes(testDetail.get()));
434
+ });
435
+
436
+ it('converts the url to a priority host url when paths are different', () => {
437
+ const detail = new ServiceDetail(formattedServiceHostmapEntryTest);
438
+ catalog._loadServiceDetails('preauth', [detail]);
439
+
440
+ const resource = 'path/to/resource';
441
+ const url = `${detail.serviceUrls[1].baseUrl}/${resource}`;
442
+
443
+ const convertUrl = services.convertUrlToPriorityHostUrl(url);
444
+
445
+ assert.isDefined(convertUrl);
446
+ assert.isTrue(convertUrl.includes(detail.get()));
447
+ assert.equal(convertUrl, `${detail.get()}/${resource}`);
448
+ catalog._unloadServiceDetails('preauth', [detail]);
449
+ });
450
+
451
+ it('throws an exception if not a valid service', () => {
452
+ assert.throws(services.convertUrlToPriorityHostUrl, Error);
453
+
454
+ assert.throws(
455
+ services.convertUrlToPriorityHostUrl.bind(services, 'not-a-valid-service'),
456
+ Error
457
+ );
458
+ });
459
+
460
+ afterEach(() => {
461
+ catalog._unloadServiceDetails('preauth', [testDetail]);
462
+ });
463
+ });
464
+
465
+ describe('#markFailedUrl()', () => {
466
+ let testDetailTemplate;
467
+ let testDetail;
468
+
469
+ beforeEach(() => {
470
+ catalog.clean();
471
+
472
+ testDetailTemplate = formattedServiceHostmapEntryConv;
473
+ testDetail = new ServiceDetail(testDetailTemplate);
474
+ catalog._loadServiceDetails('preauth', [testDetail]);
475
+ });
476
+
477
+ afterEach(() => {
478
+ catalog._unloadServiceDetails('preauth', [testDetail]);
479
+ });
480
+
481
+ it('marks a host as failed', () => {
482
+ const priorityServiceUrl = catalog._getServiceDetail(testDetailTemplate.id);
483
+ const priorityUrl = priorityServiceUrl._getPriorityHostUrl();
484
+
485
+ services.markFailedUrl(priorityUrl);
486
+
487
+ const failedHost = priorityServiceUrl.serviceUrls.find((host) => host.failed);
488
+
489
+ assert.isTrue(priorityUrl.includes(failedHost.host));
490
+ });
491
+
492
+ it('returns the next priority url', () => {
493
+ const priorityUrl = services.get(testDetailTemplate.id);
494
+
495
+ const nextPriorityUrl = services.markFailedUrl(priorityUrl);
496
+
497
+ assert.notEqual(priorityUrl, nextPriorityUrl);
498
+ });
499
+
500
+ it('should reset hosts once all hosts have been marked failed', () => {
501
+ const priorityServiceUrl = catalog._getServiceDetail(testDetailTemplate.id);
502
+ const firstPriorityUrl = priorityServiceUrl._getPriorityHostUrl();
503
+
504
+ priorityServiceUrl.serviceUrls.forEach(() => {
505
+ const priorityUrl = priorityServiceUrl._getPriorityHostUrl();
506
+
507
+ services.markFailedUrl(priorityUrl);
508
+ });
509
+
510
+ const lastPriorityUrl = priorityServiceUrl._getPriorityHostUrl();
511
+
512
+ assert.equal(firstPriorityUrl, lastPriorityUrl);
513
+ });
514
+ });
515
+
516
+ describe('#updateServices()', () => {
517
+ it('returns a Promise that and resolves on success', (done) => {
518
+ const servicesPromise = services.updateServices();
519
+
520
+ assert.typeOf(servicesPromise, 'Promise');
521
+
522
+ servicesPromise.then(() => {
523
+ services._services.forEach((service) => {
524
+ assert.typeOf(service.serviceName, 'string');
525
+ assert.typeOf(service.id, 'string');
526
+ assert.typeOf(service.serviceUrls, 'array');
527
+ });
528
+
529
+ done();
530
+ });
531
+ });
532
+
533
+ it('updates the services list', (done) => {
534
+ catalog.serviceGroups.postauth = [];
535
+
536
+ services.updateServices().then(() => {
537
+ assert.isAbove(catalog.serviceGroups.postauth.length, 0);
538
+ done();
539
+ });
540
+ });
541
+
542
+ it('updates query.email to be emailhash-ed using SHA256', (done) => {
543
+ const updateStub = sinon.stub(catalog, 'updateServiceGroups').returnsThis();
544
+ const fetchStub = sinon.stub(services, '_fetchNewServiceHostmap').resolves();
545
+
546
+ services
547
+ .updateServices({
548
+ from: 'limited',
549
+ query: {email: webexUser.email},
550
+ })
551
+ .then(() => {
552
+ assert.calledWith(
553
+ services._fetchNewServiceHostmap,
554
+ sinon.match.has('query', {emailhash: sinon.match(/\b[A-Fa-f0-9]{64}\b/)})
555
+ );
556
+ done();
557
+ })
558
+ .finally(() => {
559
+ updateStub.restore();
560
+ fetchStub.restore();
561
+ });
562
+ });
563
+
564
+ it('updates the limited catalog when email is provided', (done) => {
565
+ catalog.serviceGroups.preauth = [];
566
+
567
+ services
568
+ .updateServices({
569
+ from: 'limited',
570
+ query: {email: webexUser.email},
571
+ })
572
+ .then(() => {
573
+ assert.isAbove(catalog.serviceGroups.preauth.length, 0);
574
+ done();
575
+ });
576
+ });
577
+
578
+ it('updates the limited catalog when userId is provided', (done) => {
579
+ catalog.serviceGroups.preauth = [];
580
+
581
+ services
582
+ .updateServices({
583
+ from: 'limited',
584
+ query: {userId: webexUser.id},
585
+ })
586
+ .then(() => {
587
+ assert.isAbove(catalog.serviceGroups.preauth.length, 0);
588
+ done();
589
+ });
590
+ });
591
+
592
+ it('updates the limited catalog when orgId is provided', (done) => {
593
+ catalog.serviceGroups.preauth = [];
594
+
595
+ services
596
+ .updateServices({
597
+ from: 'limited',
598
+ query: {orgId: webexUser.orgId},
599
+ })
600
+ .then(() => {
601
+ assert.isAbove(catalog.serviceGroups.preauth.length, 0);
602
+ done();
603
+ });
604
+ });
605
+ it('updates the limited catalog when query param mode is provided', (done) => {
606
+ catalog.serviceGroups.preauth = [];
607
+
608
+ services
609
+ .updateServices({
610
+ from: 'limited',
611
+ query: {mode: 'DEFAULT_BY_PROXIMITY'},
612
+ })
613
+ .then(() => {
614
+ assert.isAbove(catalog.serviceGroups.preauth.length, 0);
615
+ done();
616
+ });
617
+ });
618
+ it('does not update the limited catalog when nothing is provided', () => {
619
+ catalog.serviceGroups.preauth = [];
620
+
621
+ return services
622
+ .updateServices({from: 'limited'})
623
+ .then(() => {
624
+ assert(false, 'resolved, should have thrown');
625
+ })
626
+ .catch(() => {
627
+ assert(true);
628
+ });
629
+ });
630
+
631
+ it('updates limited catalog and calls _fetchNewServiceHostmap with forceRefresh = true', (done) => {
632
+ const forceRefresh = true;
633
+ const fetchNewServiceHostmapSpy = sinon.spy(services, '_fetchNewServiceHostmap');
634
+
635
+ services
636
+ .updateServices({
637
+ from: 'limited',
638
+ query: {email: webexUser.email},
639
+ forceRefresh,
640
+ })
641
+ .then(() => {
642
+ assert.calledOnce(fetchNewServiceHostmapSpy);
643
+ assert.calledWith(
644
+ fetchNewServiceHostmapSpy,
645
+ sinon.match.has(
646
+ 'from',
647
+ 'limited',
648
+ 'query',
649
+ {emailhash: sinon.match(/\b[A-Fa-f0-9]{64}\b/)},
650
+ 'forceFresh',
651
+ forceRefresh
652
+ )
653
+ );
654
+
655
+ fetchNewServiceHostmapSpy.returnValues[0].then((res) => {
656
+ assert.isAbove(res.length, 0);
657
+ });
658
+ done();
659
+ });
660
+ });
661
+ });
662
+
663
+ describe('#fetchClientRegionInfo()', () => {
664
+ it('returns client region info', () =>
665
+ services.fetchClientRegionInfo().then((r) => {
666
+ assert.isDefined(r.regionCode);
667
+ assert.isDefined(r.clientAddress);
668
+ }));
669
+ });
670
+
671
+ describe('#validateUser()', () => {
672
+ const unauthWebex = new WebexCore();
673
+ const unauthServices = unauthWebex.internal.services;
674
+ let sandbox = null;
675
+
676
+ const getActivationRequest = (requestStub) => {
677
+ const requests = requestStub.args.filter(
678
+ ([request]) => request.service === 'license' && request.resource === 'users/activations'
679
+ );
680
+
681
+ assert.strictEqual(requests.length, 1);
682
+
683
+ return requests[0][0];
684
+ };
685
+
686
+ beforeEach(() => {
687
+ sandbox = sinon.createSandbox();
688
+ });
689
+
690
+ afterEach(() => {
691
+ sandbox.restore();
692
+ sandbox = null;
693
+ });
694
+
695
+ it('returns a rejected promise when no email is specified', () =>
696
+ unauthServices
697
+ .validateUser({})
698
+ .then(() => {
699
+ assert(false, 'resolved, should have thrown');
700
+ })
701
+ .catch(() => {
702
+ assert(true);
703
+ }));
704
+
705
+ it('validates an authorized user and webex instance', () =>
706
+ services.validateUser({email: webexUser.email}).then((r) => {
707
+ assert.hasAllKeys(r, ['activated', 'exists', 'user', 'details']);
708
+ assert.equal(r.activated, true);
709
+ assert.equal(r.exists, true);
710
+ }));
711
+
712
+ it('validates an authorized EU user and webex instance', () =>
713
+ servicesEU.validateUser({email: webexUserEU.email}).then((r) => {
714
+ assert.hasAllKeys(r, ['activated', 'exists', 'user', 'details']);
715
+ assert.equal(r.activated, true);
716
+ assert.equal(r.exists, true);
717
+ }));
718
+
719
+ it("returns a rejected promise if the provided email isn't valid", () =>
720
+ unauthServices
721
+ .validateUser({email: 'not an email'})
722
+ .then(() => {
723
+ assert(false, 'resolved, should have thrown');
724
+ })
725
+ .catch(() => {
726
+ assert(true);
727
+ }));
728
+
729
+ it('validates a non-existing user', () =>
730
+ unauthServices
731
+ .validateUser({email: `Collabctg+webex-js-sdk-${uuid.v4()}@gmail.com`})
732
+ .then((r) => {
733
+ assert.hasAllKeys(r, ['activated', 'exists', 'user', 'details']);
734
+ assert.equal(r.activated, false);
735
+ assert.equal(r.exists, false);
736
+ }));
737
+
738
+ it('validates new user with activationOptions suppressEmail false', () =>
739
+ unauthServices
740
+ .validateUser({
741
+ email: `Collabctg+webex-js-sdk-${uuid.v4()}@gmail.com`,
742
+ activationOptions: {suppressEmail: false},
743
+ })
744
+ .then((r) => {
745
+ assert.hasAllKeys(r, ['activated', 'exists', 'user', 'details']);
746
+ assert.equal(r.activated, false);
747
+ assert.equal(r.exists, false);
748
+ assert.equal(r.user.verificationEmailTriggered, true);
749
+ }));
750
+
751
+ it.skip('validates new user with activationOptions suppressEmail true', () =>
752
+ unauthServices
753
+ .validateUser({
754
+ email: `Collabctg+webex-js-sdk-${uuid.v4()}@gmail.com`,
755
+ activationOptions: {suppressEmail: true},
756
+ })
757
+ .then((r) => {
758
+ assert.hasAllKeys(r, ['activated', 'exists', 'user', 'details']);
759
+ assert.equal(r.activated, false);
760
+ assert.equal(r.exists, false);
761
+ assert.equal(r.user.verificationEmailTriggered, false);
762
+ }));
763
+
764
+ it('validates an inactive user', () => {
765
+ const inactive = 'webex.web.client+nonactivated@gmail.com';
766
+
767
+ return unauthServices
768
+ .validateUser({email: inactive, activationOptions: {suppressEmail: true}})
769
+ .then((r) => {
770
+ assert.hasAllKeys(r, ['activated', 'exists', 'user', 'details']);
771
+ assert.equal(r.activated, false, 'activated');
772
+ assert.equal(r.exists, true, 'exists');
773
+ })
774
+ .catch(() => {
775
+ assert(true);
776
+ });
777
+ });
778
+
779
+ it('validates an existing user', () =>
780
+ unauthServices.validateUser({email: webexUser.email}).then((r) => {
781
+ assert.hasAllKeys(r, ['activated', 'exists', 'user', 'details']);
782
+ assert.equal(r.activated, true);
783
+ assert.equal(r.exists, true);
784
+ }));
785
+
786
+ it('validates an existing EU user', () =>
787
+ unauthServices.validateUser({email: webexUserEU.email}).then((r) => {
788
+ assert.hasAllKeys(r, ['activated', 'exists', 'user', 'details']);
789
+ assert.equal(r.activated, true);
790
+ assert.equal(r.exists, true);
791
+ }));
792
+
793
+ it('sends the prelogin user id as undefined when not specified', () => {
794
+ const requestStub = sandbox.spy(unauthServices, 'request');
795
+
796
+ return unauthServices
797
+ .validateUser({
798
+ email: `Collabctg+webex-js-sdk-${uuid.v4()}@gmail.com`,
799
+ activationOptions: {suppressEmail: true},
800
+ })
801
+ .then(() => {
802
+ assert.isUndefined(getActivationRequest(requestStub).headers['x-prelogin-userid']);
803
+ });
804
+ });
805
+
806
+ it('sends the prelogin user id as provided when specified', () => {
807
+ const requestStub = sandbox.spy(unauthServices, 'request');
808
+ const preloginUserId = uuid.v4();
809
+
810
+ return unauthServices
811
+ .validateUser({
812
+ email: `Collabctg+webex-js-sdk-${uuid.v4()}@gmail.com`,
813
+ activationOptions: {suppressEmail: true},
814
+ preloginUserId,
815
+ })
816
+ .then(() => {
817
+ assert.strictEqual(
818
+ getActivationRequest(requestStub).headers['x-prelogin-userid'],
819
+ preloginUserId
820
+ );
821
+ });
822
+ });
823
+ });
824
+
825
+ describe('#waitForService()', () => {
826
+ let name;
827
+ let url;
828
+
829
+ describe('when the service exists', () => {
830
+ beforeEach(() => {
831
+ name = Object.keys(services._activeServices)[0];
832
+ const clusterId = services._activeServices[name];
833
+ url = catalog.get(clusterId);
834
+ });
835
+
836
+ describe('when using the name parameter property', () => {
837
+ it('should resolve to the appropriate url', () =>
838
+ services.waitForService({name}).then((foundUrl) => assert.equal(foundUrl, url)));
839
+ });
840
+
841
+ describe('when using the url parameter property', () => {
842
+ it('should resolve to the appropriate url', () =>
843
+ services.waitForService({url}).then((foundUrl) => assert.equal(foundUrl, url)));
844
+ });
845
+
846
+ describe('when using the url and name parameter properties', () => {
847
+ it('should resolve to the appropriate url', () =>
848
+ services.waitForService({name, url}).then((foundUrl) => assert.equal(foundUrl, url)));
849
+ });
850
+ });
851
+
852
+ describe('when the service does not exist', () => {
853
+ let timeout;
854
+
855
+ beforeEach(() => {
856
+ name = 'not a service';
857
+ url = 'http://not-a-service.com/resource';
858
+ timeout = 1;
859
+ });
860
+
861
+ describe('when using the url parameter property', () => {
862
+ it('should return a resolve promise', () =>
863
+ // const waitForService = services.waitForService({url, timeout});
864
+
865
+ services.waitForService({url, timeout}).then((foundUrl) => {
866
+ assert.equal(foundUrl, url);
867
+ assert.isTrue(catalog.isReady);
868
+ }));
869
+ });
870
+
871
+ describe('when using the name parameter property', () => {
872
+ afterEach(() => {
873
+ webex.internal.metrics.submitClientMetrics.restore();
874
+ });
875
+
876
+ it('should return a rejected promise', () => {
877
+ const submitMetrics = sinon.stub(webex.internal.metrics, 'submitClientMetrics');
878
+ const waitForService = services.waitForService({name, timeout});
879
+
880
+ assert.called(submitMetrics);
881
+ assert.isRejected(waitForService);
882
+ assert.isTrue(catalog.isReady);
883
+ });
884
+ });
885
+
886
+ describe('when using the name and url parameter properties', () => {
887
+ it('should return a rejected promise', () => {
888
+ const waitForService = services.waitForService({
889
+ name,
890
+ url,
891
+ timeout,
892
+ });
893
+
894
+ assert.isRejected(waitForService);
895
+ assert.isTrue(catalog.isReady);
896
+ });
897
+ });
898
+
899
+ describe('when the service will exist', () => {
900
+ beforeEach(() => {
901
+ name = 'metrics';
902
+ url = services.get(name, true);
903
+ catalog.clean();
904
+ catalog.isReady = false;
905
+ });
906
+
907
+ describe('when only the preauth (limited) catalog becomes available', () => {
908
+ describe('when using the name parameter property', () => {
909
+ it('should resolve to the appropriate url', () =>
910
+ Promise.all([
911
+ services.waitForService({name}),
912
+ services.collectPreauthCatalog(),
913
+ ]).then(([foundUrl]) => assert.equal(foundUrl, url)));
914
+ });
915
+
916
+ describe('when using the url parameter property', () => {
917
+ it('should resolve to the appropriate url', () =>
918
+ Promise.all([
919
+ services.waitForService({url}),
920
+ services.collectPreauthCatalog(),
921
+ ]).then(([foundUrl]) => assert.equal(foundUrl, url)));
922
+ });
923
+
924
+ describe('when using the name and url parameter property', () => {
925
+ it('should resolve to the appropriate url', () =>
926
+ Promise.all([
927
+ services.waitForService({name, url}),
928
+ services.collectPreauthCatalog(),
929
+ ]).then(([foundUrl]) => assert.equal(foundUrl, url)));
930
+ });
931
+ });
932
+
933
+ describe('when all catalogs become available', () => {
934
+ describe('when using the name parameter property', () => {
935
+ it('should resolve to the appropriate url', () =>
936
+ Promise.all([services.waitForService({name}), services.initServiceCatalogs()]).then(
937
+ ([foundUrl]) => assert.equal(foundUrl, url)
938
+ ));
939
+ });
940
+
941
+ describe('when using the url parameter property', () => {
942
+ it('should resolve to the appropriate url', () =>
943
+ Promise.all([services.waitForService({url}), services.initServiceCatalogs()]).then(
944
+ ([foundUrl]) => assert.equal(foundUrl, url)
945
+ ));
946
+ });
947
+
948
+ describe('when using the name and url parameter property', () => {
949
+ it('should resolve to the appropriate url', () =>
950
+ Promise.all([
951
+ services.waitForService({name, url}),
952
+ services.initServiceCatalogs(),
953
+ ]).then(([foundUrl]) => assert.equal(foundUrl, url)));
954
+ });
955
+ });
956
+ });
957
+ });
958
+ });
959
+
960
+ describe('#collectPreauthCatalog()', () => {
961
+ const unauthWebex = new WebexCore({config: {credentials: {federation: true}}});
962
+ const unauthServices = unauthWebex.internal.services;
963
+ const forceRefresh = true;
964
+
965
+ it('updates the preauth catalog with email along with additional timestamp to address cache control', (done) => {
966
+ const updateServiceSpy = sinon.spy(unauthServices, 'updateServices');
967
+ const fetchNewServiceHostmapSpy = sinon.spy(unauthServices, '_fetchNewServiceHostmap');
968
+
969
+ unauthServices.collectPreauthCatalog({email: webexUser.email}, forceRefresh).then(() => {
970
+ assert.calledOnce(updateServiceSpy);
971
+ assert.calledWith(
972
+ updateServiceSpy,
973
+ sinon.match.has(
974
+ 'from',
975
+ 'limited',
976
+ 'query',
977
+ {emailhash: sinon.match(/\b[A-Fa-f0-9]{64}\b/)},
978
+ 'forceRefresh',
979
+ forceRefresh
980
+ )
981
+ );
982
+
983
+ assert.calledOnce(fetchNewServiceHostmapSpy);
984
+ assert.calledWith(
985
+ fetchNewServiceHostmapSpy,
986
+ sinon.match.has(
987
+ 'from',
988
+ 'limited',
989
+ 'query',
990
+ {emailhash: sinon.match(/\b[A-Fa-f0-9]{64}\b/)},
991
+ 'forceRefresh',
992
+ forceRefresh
993
+ )
994
+ );
995
+
996
+ fetchNewServiceHostmapSpy.returnValues[0].then((res) => {
997
+ assert.isAbove(res.length, 0);
998
+ });
999
+ done();
1000
+ });
1001
+ });
1002
+ });
1003
+
1004
+ describe('#collectSigninCatalog()', () => {
1005
+ const unauthWebex = new WebexCore({config: {credentials: {federation: true}}});
1006
+ const unauthServices = unauthWebex.internal.services;
1007
+
1008
+ it('requires an email as the parameter', () =>
1009
+ unauthServices.collectSigninCatalog().catch((e) => {
1010
+ assert(true, e);
1011
+ }));
1012
+
1013
+ it('requires a token as the parameter', () =>
1014
+ unauthServices.collectSigninCatalog({email: 'email@website.com'}).catch((e) => {
1015
+ assert(true, e);
1016
+ }));
1017
+ });
1018
+
1019
+ flaky(describe, process.env.SKIP_FLAKY_TESTS)('#_fetchNewServiceHostmap()', () => {
1020
+ let fullRemoteHM;
1021
+ let limitedRemoteHM;
1022
+
1023
+ before('collect remote catalogs', () =>
1024
+ Promise.all([
1025
+ services._fetchNewServiceHostmap(),
1026
+ services._fetchNewServiceHostmap({
1027
+ from: 'limited',
1028
+ query: {userId: webexUser.id},
1029
+ }),
1030
+ ]).then(([fRHM, lRHM]) => {
1031
+ fullRemoteHM = fRHM;
1032
+ limitedRemoteHM = lRHM;
1033
+ })
1034
+ );
1035
+
1036
+ it('resolves to an authed u2c hostmap when no params specified', () => {
1037
+ assert.typeOf(fullRemoteHM, 'array');
1038
+ assert.isAbove(fullRemoteHM.length, 0);
1039
+ });
1040
+
1041
+ it('resolves to a limited u2c hostmap when params specified', () => {
1042
+ assert.typeOf(limitedRemoteHM, 'array');
1043
+ assert.isAbove(limitedRemoteHM.length, 0);
1044
+ });
1045
+
1046
+ it('rejects if the params provided are invalid', () =>
1047
+ services
1048
+ ._fetchNewServiceHostmap({
1049
+ from: 'limited',
1050
+ query: {userId: 'notValid'},
1051
+ })
1052
+ .then(() => {
1053
+ assert.isTrue(false, 'should have rejected');
1054
+ })
1055
+ .catch((e) => {
1056
+ assert.typeOf(e, 'Error');
1057
+ }));
1058
+ });
1059
+ });
1060
+ });
1061
+ // /* eslint-enable no-underscore-dangle */