@webex/webex-core 3.12.0-llmrefactor.1 → 3.12.0-llmrefactor.10

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 (44) hide show
  1. package/dist/config.js +23 -0
  2. package/dist/config.js.map +1 -1
  3. package/dist/index.js +7 -0
  4. package/dist/index.js.map +1 -1
  5. package/dist/interceptors/catalog-url.js +87 -0
  6. package/dist/interceptors/catalog-url.js.map +1 -0
  7. package/dist/interceptors/payload-transformer.js +25 -4
  8. package/dist/interceptors/payload-transformer.js.map +1 -1
  9. package/dist/lib/batcher.js +1 -1
  10. package/dist/lib/credentials/credentials.js +56 -1
  11. package/dist/lib/credentials/credentials.js.map +1 -1
  12. package/dist/lib/credentials/token.js +1 -1
  13. package/dist/lib/services/service-catalog.js +102 -8
  14. package/dist/lib/services/service-catalog.js.map +1 -1
  15. package/dist/lib/services/services.js +74 -44
  16. package/dist/lib/services/services.js.map +1 -1
  17. package/dist/lib/services-v2/service-catalog.js +8 -1
  18. package/dist/lib/services-v2/service-catalog.js.map +1 -1
  19. package/dist/lib/services-v2/services-v2.js +76 -45
  20. package/dist/lib/services-v2/services-v2.js.map +1 -1
  21. package/dist/plugins/logger.js +1 -1
  22. package/dist/webex-core.js +11 -3
  23. package/dist/webex-core.js.map +1 -1
  24. package/package.json +12 -12
  25. package/src/config.js +25 -0
  26. package/src/index.js +1 -0
  27. package/src/interceptors/catalog-url.js +66 -0
  28. package/src/interceptors/payload-transformer.js +23 -1
  29. package/src/lib/credentials/credentials.js +43 -0
  30. package/src/lib/services/service-catalog.js +106 -6
  31. package/src/lib/services/services.js +65 -30
  32. package/src/lib/services-v2/service-catalog.ts +10 -1
  33. package/src/lib/services-v2/services-v2.ts +70 -30
  34. package/src/webex-core.js +8 -0
  35. package/test/integration/spec/services/services.js +148 -113
  36. package/test/integration/spec/services-v2/services-v2.js +134 -99
  37. package/test/unit/spec/credentials/credentials.js +114 -1
  38. package/test/unit/spec/interceptors/catalog-url.js +224 -0
  39. package/test/unit/spec/interceptors/payload-transformer.js +119 -1
  40. package/test/unit/spec/services/service-catalog.js +275 -0
  41. package/test/unit/spec/services/services.js +135 -2
  42. package/test/unit/spec/services-v2/service-catalog.ts +41 -0
  43. package/test/unit/spec/services-v2/services-v2.ts +158 -2
  44. package/test/unit/spec/webex-core.js +77 -1
@@ -151,9 +151,55 @@ describe('webex-core', () => {
151
151
  const [, event] = services.listenToOnce.getCall(1).args;
152
152
  assert.equal(event, 'ready', 'default path listens on webex ready');
153
153
  });
154
+
155
+ it('skips preauth catalog collection when skipPreauthCatalogOnUnauthenticated is true and no credentials', async () => {
156
+ services.webex.config = {
157
+ ...(services.webex.config || {}),
158
+ services: {skipPreauthCatalogOnUnauthenticated: true},
159
+ };
160
+ services.listenToOnce = sinon.stub();
161
+ services.collectPreauthCatalog = sinon.stub().returns(Promise.resolve());
162
+ services.logger.info = sinon.stub();
163
+
164
+ services.initialize();
165
+ services.listenToOnce.getCall(0).args[2]();
166
+ services.listenToOnce.getCall(1).args[2]();
167
+ await waitForAsync();
168
+
169
+ assert.isFalse(
170
+ services.collectPreauthCatalog.called,
171
+ 'must not collect preauth catalog while unauthenticated when option is enabled'
172
+ );
173
+ assert.isFalse(services.initFailed);
174
+ sinon.assert.calledWith(
175
+ services.logger.info,
176
+ 'services: skipping preauth catalog collection while unauthenticated as per the config'
177
+ );
178
+ });
179
+
180
+ it('still collects preauth catalog when skipPreauthCatalogOnUnauthenticated is false (default)', async () => {
181
+ services.webex.config = {
182
+ ...(services.webex.config || {}),
183
+ services: {skipPreauthCatalogOnUnauthenticated: false},
184
+ };
185
+ services.listenToOnce = sinon.stub();
186
+ services.collectPreauthCatalog = sinon.stub().returns(Promise.resolve());
187
+
188
+ services.initialize();
189
+ services.listenToOnce.getCall(0).args[2]();
190
+ services.listenToOnce.getCall(1).args[2]();
191
+ await waitForAsync();
192
+
193
+ assert.isTrue(
194
+ services.collectPreauthCatalog.called,
195
+ 'must collect preauth catalog when option is disabled'
196
+ );
197
+ });
154
198
  });
155
199
 
156
200
  describe('#initialize (waitForCatalogInit=true)', () => {
201
+ let clock;
202
+
157
203
  beforeEach(() => {
158
204
  services.webex.config = {
159
205
  ...(services.webex.config || {}),
@@ -164,6 +210,15 @@ describe('webex-core', () => {
164
210
  // true). We reset here so tests that call initialize() a second time
165
211
  // with the flag enabled observe the gated behavior from a clean state.
166
212
  services.ready = false;
213
+ // The gated path schedules a real 15s setTimeout via _makeInitTimeout()
214
+ // that is not cleared when the init race resolves. Fake only setTimeout/
215
+ // clearTimeout (leaving setImmediate real so waitForAsync still works)
216
+ // so restoring in afterEach clears the pending timer and Jest can exit.
217
+ clock = sinon.useFakeTimers({toFake: ['setTimeout', 'clearTimeout']});
218
+ });
219
+
220
+ afterEach(() => {
221
+ clock.restore();
167
222
  });
168
223
 
169
224
  it('leaves services.ready=false after initialize (does not flip until finalize)', () => {
@@ -243,7 +298,6 @@ describe('webex-core', () => {
243
298
  });
244
299
 
245
300
  it('flips services.ready=true when init times out', async () => {
246
- const clock = sinon.useFakeTimers();
247
301
  services.listenToOnce = sinon.stub();
248
302
  services.initServiceCatalogs = sinon.stub().returns(new Promise(() => {}));
249
303
  services.webex.credentials = {
@@ -256,7 +310,6 @@ describe('webex-core', () => {
256
310
  services.listenToOnce.getCall(1).args[2]();
257
311
 
258
312
  await clock.tickAsync(15_001);
259
- clock.restore();
260
313
 
261
314
  assert.isTrue(services.initFailed);
262
315
  assert.isTrue(services.ready);
@@ -376,6 +429,89 @@ describe('webex-core', () => {
376
429
  });
377
430
  });
378
431
 
432
+ describe('#initialize (waitForCatalogInit=true, skipPreauthCatalogOnUnauthenticated=true)', () => {
433
+ beforeEach(() => {
434
+ services.webex.config = {
435
+ ...(services.webex.config || {}),
436
+ services: {
437
+ waitForCatalogInit: true,
438
+ catalogInitTimeout: 15000,
439
+ skipPreauthCatalogOnUnauthenticated: true,
440
+ },
441
+ };
442
+ services.ready = false;
443
+ });
444
+
445
+ it('skips preauth catalog collection but still finalizes services.ready when unauthenticated', async () => {
446
+ services.listenToOnce = sinon.stub();
447
+ services.collectPreauthCatalog = sinon.stub().returns(Promise.resolve());
448
+ services.logger.info = sinon.stub();
449
+
450
+ services.initialize();
451
+ services.listenToOnce.getCall(0).args[2]();
452
+ services.listenToOnce.getCall(1).args[2]();
453
+ await waitForAsync();
454
+
455
+ assert.isFalse(
456
+ services.collectPreauthCatalog.called,
457
+ 'must not collect preauth catalog while unauthenticated when option is enabled'
458
+ );
459
+ assert.isTrue(services.ready, 'must still finalize ready so webex.ready is not stalled');
460
+ sinon.assert.calledWith(
461
+ services.logger.info,
462
+ 'services: skipping preauth catalog collection while unauthenticated as per the config'
463
+ );
464
+ });
465
+
466
+ it('still registers a change:canAuthorize listener when skipping preauth (fresh-login path)', async () => {
467
+ services.listenToOnce = sinon.stub();
468
+ services.collectPreauthCatalog = sinon.stub().returns(Promise.resolve());
469
+ services.initServiceCatalogs = sinon.stub().returns(Promise.resolve());
470
+
471
+ services.initialize();
472
+ const currentCatalog = services._getCatalog();
473
+ services.listenToOnce.getCall(0).args[2]();
474
+ services.listenToOnce.getCall(1).args[2]();
475
+ await waitForAsync();
476
+
477
+ const freshLoginCall = services.listenToOnce
478
+ .getCalls()
479
+ .find((call: any) => call.args[1] === 'change:canAuthorize');
480
+ assert.isDefined(freshLoginCall, 'expected change:canAuthorize listener to be registered');
481
+
482
+ services.webex.canAuthorize = true;
483
+ currentCatalog.status.postauth.ready = false;
484
+ freshLoginCall!.args[2]();
485
+
486
+ assert.isTrue(
487
+ services.initServiceCatalogs.called,
488
+ 'expected postauth catalog init after canAuthorize flips'
489
+ );
490
+ await waitForAsync();
491
+ });
492
+
493
+ it('does not schedule an init timeout when skipping preauth (no stray timer/rejection)', async () => {
494
+ const clock = sinon.useFakeTimers();
495
+ services.listenToOnce = sinon.stub();
496
+ services.collectPreauthCatalog = sinon.stub().returns(Promise.resolve());
497
+ services.logger.error = sinon.stub();
498
+
499
+ services.initialize();
500
+ services.listenToOnce.getCall(0).args[2]();
501
+ services.listenToOnce.getCall(1).args[2]();
502
+
503
+ // Advance well past the configured init timeout; since no timeout was
504
+ // scheduled in skip mode, no timeout error should ever be logged.
505
+ await clock.tickAsync(30_000);
506
+ clock.restore();
507
+
508
+ sinon.assert.neverCalledWithMatch(
509
+ services.logger.error,
510
+ /services: init timed out/
511
+ );
512
+ });
513
+ });
514
+
379
515
  describe('#_finalizeReady', () => {
380
516
  beforeEach(() => {
381
517
  // Outer beforeEach construction already ran initialize() (ungated,
@@ -760,6 +896,26 @@ describe('webex-core', () => {
760
896
  });
761
897
  });
762
898
 
899
+ describe('#getServiceFromUrl()', () => {
900
+ it('matches an exact service URL when the catalog URL has a trailing slash', () => {
901
+ const baseUrl = 'https://example.com/api/v1/';
902
+
903
+ catalog.updateServiceGroups('preauth', [
904
+ {
905
+ id: 'example',
906
+ serviceName: 'example',
907
+ serviceUrls: [{host: 'example.com', baseUrl, priority: 1}],
908
+ },
909
+ ]);
910
+
911
+ assert.deepEqual(services.getServiceFromUrl('https://example.com/api/v1'), {
912
+ name: 'example',
913
+ priorityUrl: baseUrl,
914
+ defaultUrl: baseUrl,
915
+ });
916
+ });
917
+ });
918
+
763
919
  describe('#_formatReceivedHostmap()', () => {
764
920
  let serviceHostmap;
765
921
  let formattedHM;
@@ -91,6 +91,30 @@ describe('Webex', () => {
91
91
  assert.isFalse(webex.canAuthorize);
92
92
  });
93
93
 
94
+ it('initializes network telemetry after applying the configured metrics options', async () => {
95
+ const configuredWebex = new WebexCore({
96
+ config: {
97
+ metrics: {
98
+ networkTelemetry: {
99
+ enabled: true,
100
+ intervalMs: 60 * 1_000,
101
+ },
102
+ },
103
+ },
104
+ });
105
+ const submitClientMetrics = sinon
106
+ .stub(configuredWebex.internal.metrics, 'submitClientMetrics')
107
+ .resolves();
108
+
109
+ assert.exists(configuredWebex.internal.metrics.networkTelemetry);
110
+ configuredWebex.trigger('request:start', {service: 'hydra', resource: 'rooms'});
111
+ await configuredWebex.internal.metrics.flushNetworkTelemetry();
112
+
113
+ assert.calledOnce(submitClientMetrics);
114
+
115
+ await configuredWebex.internal.metrics.stopNetworkTelemetry();
116
+ });
117
+
94
118
  [
95
119
  'data',
96
120
  'data.access_token',
@@ -179,7 +203,7 @@ describe('Webex', () => {
179
203
 
180
204
  describe('initializes with interceptors', () => {
181
205
  [
182
- // 4 pre, 4 post, 10 remaining default = 18
206
+ // 4 pre (CatalogUrlInterceptor is opt-in), 4 post, 10 remaining default = 18
183
207
  [
184
208
  'defaults to existing interceptors if undefined',
185
209
  undefined,
@@ -205,6 +229,58 @@ describe('Webex', () => {
205
229
  'RateLimitInterceptor',
206
230
  ],
207
231
  ],
232
+ [
233
+ 'does not include CatalogUrlInterceptor for a truthy non-boolean value',
234
+ {services: {validateCatalogUrls: 'true'}},
235
+ 18,
236
+ [
237
+ 'RequestTimingInterceptor',
238
+ 'RequestEventInterceptor',
239
+ 'WebexTrackingIdInterceptor',
240
+ 'RateLimitInterceptor',
241
+ 'ServiceInterceptor',
242
+ 'UserAgentInterceptor',
243
+ 'ProxyInterceptor',
244
+ 'WebexUserAgentInterceptor',
245
+ 'AuthInterceptor',
246
+ 'PayloadTransformerInterceptor',
247
+ 'RedirectInterceptor',
248
+ 'DefaultOptionsInterceptor',
249
+ 'HostMapInterceptor',
250
+ 'ServerErrorInterceptor',
251
+ 'HttpStatusInterceptor',
252
+ 'NetworkTimingInterceptor',
253
+ 'EmbargoInterceptor',
254
+ 'RateLimitInterceptor',
255
+ ],
256
+ ],
257
+ // CatalogUrlInterceptor is opt-in via services.validateCatalogUrls
258
+ [
259
+ 'includes CatalogUrlInterceptor when validateCatalogUrls is enabled',
260
+ {services: {validateCatalogUrls: true}},
261
+ 19,
262
+ [
263
+ 'RequestTimingInterceptor',
264
+ 'RequestEventInterceptor',
265
+ 'WebexTrackingIdInterceptor',
266
+ 'RateLimitInterceptor',
267
+ 'CatalogUrlInterceptor',
268
+ 'ServiceInterceptor',
269
+ 'UserAgentInterceptor',
270
+ 'ProxyInterceptor',
271
+ 'WebexUserAgentInterceptor',
272
+ 'AuthInterceptor',
273
+ 'PayloadTransformerInterceptor',
274
+ 'RedirectInterceptor',
275
+ 'DefaultOptionsInterceptor',
276
+ 'HostMapInterceptor',
277
+ 'ServerErrorInterceptor',
278
+ 'HttpStatusInterceptor',
279
+ 'NetworkTimingInterceptor',
280
+ 'EmbargoInterceptor',
281
+ 'RateLimitInterceptor',
282
+ ],
283
+ ],
208
284
  [
209
285
  'only adds PayloadTransformerInterceptor',
210
286
  {interceptors: {PayloadTransformerInterceptor: PayloadTransformerInterceptor.create}},