@webex/webex-core 3.8.1 → 3.9.0-multi-llms.2

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