@webex/webex-core 3.11.0-webex-services-ready.1 → 3.12.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.
@@ -34,53 +34,6 @@ describe('webex-core', () => {
34
34
  });
35
35
 
36
36
  describe('#initialize', () => {
37
- it('listens for "loaded" event instead of "ready" to avoid deadlock', () => {
38
- services.listenToOnce = sinon.stub();
39
- services.initialize();
40
-
41
- // Second listenToOnce call should be for 'loaded' event
42
- assert.equal(services.listenToOnce.getCall(1).args[1], 'loaded');
43
- });
44
-
45
- it('services.ready starts as false', () => {
46
- assert.isFalse(services.ready);
47
- });
48
-
49
- it('sets ready to true and triggers services:initialized when initialization succeeds with credentials', async () => {
50
- services.listenToOnce = sinon.stub();
51
- services.initServiceCatalogs = sinon.stub().returns(Promise.resolve());
52
- services.trigger = sinon.stub();
53
- services.webex.credentials = {
54
- supertoken: {
55
- access_token: 'token',
56
- },
57
- };
58
-
59
- services.initialize();
60
-
61
- // call the onLoaded callback
62
- services.listenToOnce.getCall(1).args[2]();
63
- await waitForAsync();
64
-
65
- assert.isTrue(services.ready);
66
- sinon.assert.calledWith(services.trigger, 'services:initialized');
67
- });
68
-
69
- it('sets ready to true and triggers services:initialized when initialization succeeds without credentials', async () => {
70
- services.listenToOnce = sinon.stub();
71
- services.collectPreauthCatalog = sinon.stub().returns(Promise.resolve());
72
- services.trigger = sinon.stub();
73
-
74
- services.initialize();
75
-
76
- // call the onLoaded callback
77
- services.listenToOnce.getCall(1).args[2]();
78
- await waitForAsync();
79
-
80
- assert.isTrue(services.ready);
81
- sinon.assert.calledWith(services.trigger, 'services:initialized');
82
- });
83
-
84
37
  it('initFailed is false when initialization succeeds and credentials are available', async () => {
85
38
  services.listenToOnce = sinon.stub();
86
39
  services.initServiceCatalogs = sinon.stub().returns(Promise.resolve());
@@ -92,7 +45,7 @@ describe('webex-core', () => {
92
45
 
93
46
  services.initialize();
94
47
 
95
- // call the onLoaded callback
48
+ // call the onReady callback
96
49
  services.listenToOnce.getCall(1).args[2]();
97
50
  await waitForAsync();
98
51
 
@@ -105,7 +58,7 @@ describe('webex-core', () => {
105
58
 
106
59
  services.initialize();
107
60
 
108
- // call the onLoaded callback
61
+ // call the onReady callback
109
62
  services.listenToOnce.getCall(1).args[2]();
110
63
  await waitForAsync();
111
64
 
@@ -114,9 +67,9 @@ describe('webex-core', () => {
114
67
 
115
68
  it.each([
116
69
  {error: new Error('failed'), expectedMessage: 'failed'},
117
- {error: undefined, expectedMessage: undefined},
70
+ {error: undefined, expectedMessage: undefined}
118
71
  ])(
119
- 'sets initFailed to true when collectPreauthCatalog errors but still sets ready to true',
72
+ 'sets initFailed to true when collectPreauthCatalog errors',
120
73
  async ({error, expectedMessage}) => {
121
74
  services.collectPreauthCatalog = sinon.stub().callsFake(() => {
122
75
  return Promise.reject(error);
@@ -124,18 +77,15 @@ describe('webex-core', () => {
124
77
 
125
78
  services.listenToOnce = sinon.stub();
126
79
  services.logger.error = sinon.stub();
127
- services.trigger = sinon.stub();
128
80
 
129
81
  services.initialize();
130
82
 
131
- // call the onLoaded callback
83
+ // call the onReady callback
132
84
  services.listenToOnce.getCall(1).args[2]();
133
85
 
134
86
  await waitForAsync();
135
87
 
136
88
  assert.isTrue(services.initFailed);
137
- assert.isTrue(services.ready);
138
- sinon.assert.calledWith(services.trigger, 'services:initialized');
139
89
  sinon.assert.calledWith(
140
90
  services.logger.error,
141
91
  `services: failed to init initial services when no credentials available, ${expectedMessage}`
@@ -145,132 +95,32 @@ describe('webex-core', () => {
145
95
 
146
96
  it.each([
147
97
  {error: new Error('failed'), expectedMessage: 'failed'},
148
- {error: undefined, expectedMessage: undefined},
149
- ])(
150
- 'sets initFailed to true when initServiceCatalogs errors but still sets ready to true',
151
- async ({error, expectedMessage}) => {
152
- services.initServiceCatalogs = sinon.stub().callsFake(() => {
153
- return Promise.reject(error);
154
- });
155
- services.webex.credentials = {
156
- supertoken: {
157
- access_token: 'token',
158
- },
159
- };
160
-
161
- services.listenToOnce = sinon.stub();
162
- services.logger.error = sinon.stub();
163
- services.trigger = sinon.stub();
164
-
165
- services.initialize();
166
-
167
- // call the onLoaded callback
168
- services.listenToOnce.getCall(1).args[2]();
169
-
170
- await waitForAsync();
171
-
172
- assert.isTrue(services.initFailed);
173
- assert.isTrue(services.ready);
174
- sinon.assert.calledWith(services.trigger, 'services:initialized');
175
- sinon.assert.calledWith(
176
- services.logger.error,
177
- `services: failed to init initial services when credentials available, ${expectedMessage}`
178
- );
179
- }
180
- );
181
-
182
- describe('when change:canAuthorize fires', () => {
183
- it('calls initServiceCatalogs when canAuthorize becomes true and postauth catalog is not ready', async () => {
184
- services.listenToOnce = sinon.stub();
185
- services._loadCatalogFromCache = sinon.stub().returns(Promise.resolve(false));
186
- services.collectPreauthCatalog = sinon.stub().returns(Promise.resolve());
187
- services.initServiceCatalogs = sinon.stub().returns(Promise.resolve());
188
- services.trigger = sinon.stub();
189
- // Ensure no credentials so we go to the preauth path
190
- services.webex.credentials = {};
191
-
192
- services.initialize();
193
-
194
- // Get the catalog after initialize creates it
195
- const testCatalog = services._getCatalog();
196
- testCatalog.status.postauth.ready = false;
197
-
198
- // call the onLoaded callback (preauth path)
199
- services.listenToOnce.getCall(1).args[2]();
200
- await waitForAsync();
201
-
202
- // Now set canAuthorize to true to simulate auth completing
203
- services.webex.canAuthorize = true;
204
-
205
- // call the change:canAuthorize callback (should be call 2)
206
- services.listenToOnce.getCall(2).args[2]();
207
- await waitForAsync();
208
-
209
- sinon.assert.calledOnce(services.initServiceCatalogs);
210
- assert.isTrue(testCatalog.isReady);
211
- });
212
-
213
- it('does not call initServiceCatalogs when postauth catalog is already ready', async () => {
214
- services.listenToOnce = sinon.stub();
215
- services._loadCatalogFromCache = sinon.stub().returns(Promise.resolve(false));
216
- services.collectPreauthCatalog = sinon.stub().returns(Promise.resolve());
217
- services.initServiceCatalogs = sinon.stub().returns(Promise.resolve());
218
- services.trigger = sinon.stub();
219
- // Ensure no credentials so we go to the preauth path
220
- services.webex.credentials = {};
221
-
222
- services.initialize();
223
-
224
- // Get the catalog after initialize creates it
225
- const testCatalog = services._getCatalog();
226
-
227
- // call the onLoaded callback (preauth path)
228
- services.listenToOnce.getCall(1).args[2]();
229
- await waitForAsync();
230
-
231
- // Set canAuthorize to true and postauth as ready BEFORE calling the callback
232
- services.webex.canAuthorize = true;
233
- testCatalog.status.postauth.ready = true;
234
-
235
- // call the change:canAuthorize callback (should be call 2)
236
- services.listenToOnce.getCall(2).args[2]();
237
- await waitForAsync();
238
-
239
- sinon.assert.notCalled(services.initServiceCatalogs);
98
+ {error: undefined, expectedMessage: undefined}
99
+ ])('sets initFailed to true when initServiceCatalogs errors', async ({error, expectedMessage}) => {
100
+ services.initServiceCatalogs = sinon.stub().callsFake(() => {
101
+ return Promise.reject(error);
240
102
  });
103
+ services.webex.credentials = {
104
+ supertoken: {
105
+ access_token: 'token'
106
+ }
107
+ }
241
108
 
242
- it('logs an error when initServiceCatalogs fails after auth', async () => {
243
- services.listenToOnce = sinon.stub();
244
- services._loadCatalogFromCache = sinon.stub().returns(Promise.resolve(false));
245
- services.collectPreauthCatalog = sinon.stub().returns(Promise.resolve());
246
- services.initServiceCatalogs = sinon.stub().rejects(new Error('auth failed'));
247
- services.logger.error = sinon.stub();
248
- services.trigger = sinon.stub();
249
- // Ensure no credentials so we go to the preauth path
250
- services.webex.credentials = {};
251
-
252
- services.initialize();
253
-
254
- // Get the catalog after initialize creates it
255
- const testCatalog = services._getCatalog();
256
- testCatalog.status.postauth.ready = false;
109
+ services.listenToOnce = sinon.stub();
110
+ services.logger.error = sinon.stub();
257
111
 
258
- // call the onLoaded callback (preauth path)
259
- services.listenToOnce.getCall(1).args[2]();
260
- await waitForAsync();
112
+ services.initialize();
261
113
 
262
- // Now set canAuthorize to true to simulate auth completing
263
- services.webex.canAuthorize = true;
114
+ // call the onReady callback
115
+ services.listenToOnce.getCall(1).args[2]();
264
116
 
265
- // call the change:canAuthorize callback (should be call 2)
266
- services.listenToOnce.getCall(2).args[2]();
267
- await waitForAsync();
117
+ await waitForAsync();
268
118
 
269
- sinon.assert.calledWith(
270
- services.logger.error,
271
- 'services: failed to init service catalogs after auth, auth failed'
272
- );
273
- });
119
+ assert.isTrue(services.initFailed);
120
+ sinon.assert.calledWith(
121
+ services.logger.error,
122
+ `services: failed to init initial services when credentials available, ${expectedMessage}`
123
+ );
274
124
  });
275
125
  });
276
126
 
@@ -309,7 +159,7 @@ describe('webex-core', () => {
309
159
 
310
160
  services.collectPreauthCatalog = sinon.stub().callsFake(() => {
311
161
  return Promise.resolve();
312
- });
162
+ })
313
163
 
314
164
  services.updateServices = sinon.stub().callsFake(() => {
315
165
  return Promise.reject(error);
@@ -404,7 +254,7 @@ describe('webex-core', () => {
404
254
  discovery: {
405
255
  sqdiscovery: 'https://test.ciscospark.com/v1/region',
406
256
  },
407
- },
257
+ }
408
258
  };
409
259
  });
410
260
 
@@ -424,8 +274,8 @@ describe('webex-core', () => {
424
274
  assert.calledWith(webex.request, {
425
275
  uri: 'https://test.ciscospark.com/v1/region',
426
276
  addAuthHeader: false,
427
- headers: {'spark-user-agent': null},
428
- timeout: 5000,
277
+ headers: { 'spark-user-agent': null },
278
+ timeout: 5000
429
279
  });
430
280
  });
431
281
  });
@@ -482,6 +332,7 @@ describe('webex-core', () => {
482
332
  });
483
333
 
484
334
  describe('#_fetchNewServiceHostmap()', () => {
335
+
485
336
  beforeEach(() => {
486
337
  sinon.spy(webex.internal.newMetrics.callDiagnosticLatencies, 'measureLatency');
487
338
  });
@@ -495,20 +346,17 @@ describe('webex-core', () => {
495
346
 
496
347
  sinon.stub(services, '_formatReceivedHostmap').resolves(mapResponse);
497
348
  sinon.stub(services, 'request').resolves({});
498
-
349
+
499
350
  const mapResult = await services._fetchNewServiceHostmap({from: 'limited'});
500
351
 
501
352
  assert.calledOnceWithExactly(services.request, {
502
353
  method: 'GET',
503
354
  service: 'u2c',
504
355
  resource: '/limited/catalog',
505
- qs: {format: 'hostmap'},
506
- });
507
- assert.calledOnceWithExactly(
508
- webex.internal.newMetrics.callDiagnosticLatencies.measureLatency,
509
- sinon.match.func,
510
- 'internal.get.u2c.time'
356
+ qs: {format: 'hostmap'}
357
+ }
511
358
  );
359
+ assert.calledOnceWithExactly(webex.internal.newMetrics.callDiagnosticLatencies.measureLatency, sinon.match.func, 'internal.get.u2c.time');
512
360
  });
513
361
 
514
362
  it('checks service request rejects', async () => {
@@ -516,7 +364,7 @@ describe('webex-core', () => {
516
364
 
517
365
  sinon.spy(services, '_formatReceivedHostmap');
518
366
  sinon.stub(services, 'request').rejects(error);
519
-
367
+
520
368
  const promise = services._fetchNewServiceHostmap({from: 'limited'});
521
369
  const rejectedValue = await assert.isRejected(promise);
522
370
 
@@ -528,13 +376,10 @@ describe('webex-core', () => {
528
376
  method: 'GET',
529
377
  service: 'u2c',
530
378
  resource: '/limited/catalog',
531
- qs: {format: 'hostmap'},
532
- });
533
- assert.calledOnceWithExactly(
534
- webex.internal.newMetrics.callDiagnosticLatencies.measureLatency,
535
- sinon.match.func,
536
- 'internal.get.u2c.time'
379
+ qs: {format: 'hostmap'}
380
+ }
537
381
  );
382
+ assert.calledOnceWithExactly(webex.internal.newMetrics.callDiagnosticLatencies.measureLatency, sinon.match.func, 'internal.get.u2c.time');
538
383
  });
539
384
  });
540
385
 
@@ -565,6 +410,7 @@ describe('webex-core', () => {
565
410
  });
566
411
 
567
412
  it('returns the original uri if the hostmap has no hosts for the host', () => {
413
+
568
414
  services._hostCatalog = {
569
415
  'example.com': [],
570
416
  };
@@ -728,7 +574,8 @@ describe('webex-core', () => {
728
574
  id: '0:0:0:different-e-x',
729
575
  },
730
576
  ],
731
- 'example-f.com': [],
577
+ 'example-f.com': [
578
+ ],
732
579
  },
733
580
  format: 'hostmap',
734
581
  };
@@ -936,7 +783,7 @@ describe('webex-core', () => {
936
783
  defaultUrl: 'https://example-g.com/api/v1',
937
784
  hosts: [],
938
785
  name: 'example-g',
939
- },
786
+ }
940
787
  ]);
941
788
  });
942
789
 
@@ -990,59 +837,34 @@ describe('webex-core', () => {
990
837
  assert.equal(webex.config.credentials.authorizeUrl, authUrl);
991
838
  });
992
839
  });
993
-
840
+
994
841
  describe('#getMobiusClusters', () => {
995
842
  it('returns unique mobius host entries from hostCatalog', () => {
996
843
  // Arrange: two hostCatalog keys, with duplicate mobius host across keys
997
844
  services._hostCatalog = {
998
845
  'mobius-us-east-2.prod.infra.webex.com': [
999
- {
1000
- host: 'mobius-us-east-2.prod.infra.webex.com',
1001
- ttl: -1,
1002
- priority: 5,
1003
- id: 'urn:TEAM:xyz:mobius',
1004
- },
1005
- {
1006
- host: 'mobius-eu-central-1.prod.infra.webex.com',
1007
- ttl: -1,
1008
- priority: 10,
1009
- id: 'urn:TEAM:xyz:mobius',
1010
- },
1011
- ],
846
+ {host: 'mobius-us-east-2.prod.infra.webex.com', ttl: -1, priority: 5, id: 'urn:TEAM:xyz:mobius'},
847
+ {host: 'mobius-eu-central-1.prod.infra.webex.com', ttl: -1, priority: 10, id: 'urn:TEAM:xyz:mobius'},
848
+ ],
1012
849
 
1013
850
  'mobius-eu-central-1.prod.infra.webex.com': [
1014
- {
1015
- host: 'mobius-us-east-2.prod.infra.webex.com',
1016
- ttl: -1,
1017
- priority: 7,
1018
- id: 'urn:TEAM:xyz:mobius',
1019
- }, // duplicate host
1020
- ],
1021
- 'wdm-a.webex.com': [
851
+ {host: 'mobius-us-east-2.prod.infra.webex.com', ttl: -1, priority: 7, id: 'urn:TEAM:xyz:mobius'}, // duplicate host
852
+ ],
853
+ 'wdm-a.webex.com' : [
1022
854
  {host: 'wdm-a.webex.com', ttl: -1, priority: 5, id: 'urn:TEAM:xyz:wdm'},
1023
- ],
855
+ ]
1024
856
  };
1025
-
857
+
1026
858
  // Act
1027
859
  const clusters = services.getMobiusClusters();
1028
-
860
+
1029
861
  // Assert
1030
862
  // deduped; only mobius entries; keeps first seen mobius-a, then mobius-b
1031
863
  assert.deepEqual(
1032
864
  clusters.map(({host, id, ttl, priority}) => ({host, id, ttl, priority})),
1033
865
  [
1034
- {
1035
- host: 'mobius-us-east-2.prod.infra.webex.com',
1036
- id: 'urn:TEAM:xyz:mobius',
1037
- ttl: -1,
1038
- priority: 5,
1039
- },
1040
- {
1041
- host: 'mobius-eu-central-1.prod.infra.webex.com',
1042
- id: 'urn:TEAM:xyz:mobius',
1043
- ttl: -1,
1044
- priority: 10,
1045
- },
866
+ {host: 'mobius-us-east-2.prod.infra.webex.com', id: 'urn:TEAM:xyz:mobius', ttl: -1, priority: 5},
867
+ {host: 'mobius-eu-central-1.prod.infra.webex.com', id: 'urn:TEAM:xyz:mobius', ttl: -1, priority: 10},
1046
868
  ]
1047
869
  );
1048
870
  });
@@ -1051,31 +873,31 @@ describe('webex-core', () => {
1051
873
  describe('#isValidHost', () => {
1052
874
  beforeEach(() => {
1053
875
  // Setting up a mock host catalog
1054
- services._hostCatalog = {
1055
- 'audit-ci-m.wbx2.com': [
1056
- {
1057
- host: 'audit-ci-m.wbx2.com',
1058
- ttl: -1,
1059
- priority: 5,
1060
- id: 'urn:IDENTITY:PA61:adminAudit',
1061
- },
1062
- {
1063
- host: 'audit-ci-m.wbx2.com',
1064
- ttl: -1,
1065
- priority: 5,
1066
- id: 'urn:IDENTITY:PA61:adminAuditV2',
1067
- },
1068
- ],
1069
- 'mercury-connection-partition0-r.wbx2.com': [
1070
- {
1071
- host: 'mercury-connection-partition0-r.wbx2.com',
1072
- ttl: -1,
1073
- priority: 5,
1074
- id: 'urn:TEAM:us-west-2_r:mercuryConnectionPartition0',
1075
- },
1076
- ],
1077
- 'empty.com': [],
1078
- };
876
+ services._hostCatalog = {
877
+ "audit-ci-m.wbx2.com": [
878
+ {
879
+ "host": "audit-ci-m.wbx2.com",
880
+ "ttl": -1,
881
+ "priority": 5,
882
+ "id": "urn:IDENTITY:PA61:adminAudit"
883
+ },
884
+ {
885
+ "host": "audit-ci-m.wbx2.com",
886
+ "ttl": -1,
887
+ "priority": 5,
888
+ "id": "urn:IDENTITY:PA61:adminAuditV2"
889
+ }
890
+ ],
891
+ "mercury-connection-partition0-r.wbx2.com": [
892
+ {
893
+ "host": "mercury-connection-partition0-r.wbx2.com",
894
+ "ttl": -1,
895
+ "priority": 5,
896
+ "id": "urn:TEAM:us-west-2_r:mercuryConnectionPartition0"
897
+ }
898
+ ],
899
+ "empty.com": []
900
+ };
1079
901
  });
1080
902
  afterAll(() => {
1081
903
  // Clean up the mock host catalog
@@ -1100,13 +922,64 @@ describe('webex-core', () => {
1100
922
  });
1101
923
  });
1102
924
 
925
+ describe('#isIntegrationEnvironment', () => {
926
+ it('returns true when u2c URL contains "intb"', () => {
927
+ services.webex.config = {
928
+ services: {
929
+ discovery: {
930
+ u2c: 'https://u2c-intb.ciscospark.com/u2c/api/v1',
931
+ },
932
+ },
933
+ };
934
+ assert.isTrue(services.isIntegrationEnvironment());
935
+ });
936
+
937
+ it('returns false when u2c URL does not contain "intb" (production)', () => {
938
+ services.webex.config = {
939
+ services: {
940
+ discovery: {
941
+ u2c: 'https://u2c.wbx2.com/u2c/api/v1',
942
+ },
943
+ },
944
+ };
945
+ assert.isFalse(services.isIntegrationEnvironment());
946
+ });
947
+
948
+ it('returns false when u2c URL is for FedRAMP', () => {
949
+ services.webex.config = {
950
+ services: {
951
+ discovery: {
952
+ u2c: 'https://u2c.gov.ciscospark.com/u2c/api/v1',
953
+ },
954
+ },
955
+ };
956
+ assert.isFalse(services.isIntegrationEnvironment());
957
+ });
958
+
959
+ it('returns false when u2c URL is undefined', () => {
960
+ services.webex.config = {
961
+ services: {
962
+ discovery: {
963
+ u2c: undefined,
964
+ },
965
+ },
966
+ };
967
+ assert.isFalse(services.isIntegrationEnvironment());
968
+ });
969
+
970
+ it('returns false when config is not available', () => {
971
+ services.webex.config = undefined;
972
+ assert.isFalse(services.isIntegrationEnvironment());
973
+ });
974
+ });
975
+
1103
976
  describe('U2C catalog cache behavior', () => {
1104
977
  let webex;
1105
978
  let services;
1106
979
  let catalog;
1107
980
  let localStorageBackup;
1108
981
  let windowBackup;
1109
-
982
+
1110
983
  const makeLocalStorageShim = () => {
1111
984
  const store = new Map();
1112
985
  return {
@@ -1116,16 +989,13 @@ describe('webex-core', () => {
1116
989
  _store: store,
1117
990
  };
1118
991
  };
1119
-
992
+
1120
993
  beforeEach(() => {
1121
994
  // Build a fresh webex instance
1122
- webex = new MockWebex({
1123
- children: {services: Services},
1124
- config: {credentials: {federation: true}},
1125
- });
995
+ webex = new MockWebex({children: {services: Services}, config: {credentials: {federation: true}}});
1126
996
  services = webex.internal.services;
1127
997
  catalog = services._getCatalog();
1128
-
998
+
1129
999
  // enable U2C caching feature flag in tests that rely on localStorage writes/reads
1130
1000
  services.webex.config = services.webex.config || {};
1131
1001
  services.webex.config.calling = {...(services.webex.config.calling || {}), cacheU2C: true};
@@ -1137,15 +1007,13 @@ describe('webex-core', () => {
1137
1007
  global.window.localStorage = makeLocalStorageShim();
1138
1008
  // Ensure code under test uses our shim via util method
1139
1009
  sinon.stub(services, '_getLocalStorageSafe').returns(global.window.localStorage);
1140
-
1010
+
1141
1011
  // Stub the formatter so we don't need a full hostmap payload in tests
1142
- sinon
1143
- .stub(services, '_formatReceivedHostmap')
1144
- .callsFake(() => [
1145
- {name: 'hydra', defaultUrl: 'https://api.ciscospark.com/v1', hosts: []},
1146
- ]);
1012
+ sinon.stub(services, '_formatReceivedHostmap').callsFake(() => [
1013
+ {name: 'hydra', defaultUrl: 'https://api.ciscospark.com/v1', hosts: []},
1014
+ ]);
1147
1015
  });
1148
-
1016
+
1149
1017
  afterEach(() => {
1150
1018
  global.window.localStorage = localStorageBackup || undefined;
1151
1019
  if (!windowBackup) {
@@ -1158,7 +1026,7 @@ describe('webex-core', () => {
1158
1026
  services._getLocalStorageSafe.restore();
1159
1027
  }
1160
1028
  });
1161
-
1029
+
1162
1030
  it('invokes initServiceCatalogs on ready, caches catalog, and stores in localStorage', async () => {
1163
1031
  // Arrange: authenticated credentials and spies
1164
1032
  services.webex.credentials = {
@@ -1170,20 +1038,10 @@ describe('webex-core', () => {
1170
1038
  const cacheSpy = sinon.spy(services, '_cacheCatalog');
1171
1039
  const setItemSpy = sinon.spy(global.window.localStorage, 'setItem');
1172
1040
  // Make fetch return a hostmap object and allow formatter to reduce it
1173
- sinon
1174
- .stub(services, 'request')
1175
- .resolves({
1176
- body: {
1177
- services: [],
1178
- activeServices: {},
1179
- timestamp: Date.now().toString(),
1180
- orgId: 'urn:EXAMPLE:org',
1181
- format: 'U2CV2',
1182
- },
1183
- });
1184
- // Cause loaded callback to run immediately
1041
+ sinon.stub(services, 'request').resolves({body: {services: [], activeServices: {}, timestamp: Date.now().toString(), orgId: 'urn:EXAMPLE:org', format: 'U2CV2'}});
1042
+ // Cause ready callback to run immediately
1185
1043
  services.listenToOnce = sinon.stub().callsFake((ctx, event, cb) => {
1186
- if (event === 'loaded') cb();
1044
+ if (event === 'ready') cb();
1187
1045
  });
1188
1046
 
1189
1047
  // Act
@@ -1223,9 +1081,9 @@ describe('webex-core', () => {
1223
1081
 
1224
1082
  const initSpy = sinon.spy(services, 'initServiceCatalogs');
1225
1083
  const cacheSpy = sinon.spy(services, '_cacheCatalog');
1226
- // Cause loaded callback to run immediately
1084
+ // Cause ready callback to run immediately
1227
1085
  services.listenToOnce = sinon.stub().callsFake((ctx, event, cb) => {
1228
- if (event === 'loaded') cb();
1086
+ if (event === 'ready') cb();
1229
1087
  });
1230
1088
 
1231
1089
  // Act
@@ -1233,18 +1091,9 @@ describe('webex-core', () => {
1233
1091
  await waitForAsync();
1234
1092
 
1235
1093
  // Assert: ready path found cache and skipped initServiceCatalogs
1236
- assert.isFalse(
1237
- initSpy.called,
1238
- 'expected initServiceCatalogs to be skipped with cache present'
1239
- );
1240
- assert.isTrue(
1241
- services._getCatalog().status.preauth.ready,
1242
- 'preauth should be ready from cache'
1243
- );
1244
- assert.isTrue(
1245
- services._getCatalog().status.postauth.ready,
1246
- 'postauth should be ready from cache'
1247
- );
1094
+ assert.isFalse(initSpy.called, 'expected initServiceCatalogs to be skipped with cache present');
1095
+ assert.isTrue(services._getCatalog().status.preauth.ready, 'preauth should be ready from cache');
1096
+ assert.isTrue(services._getCatalog().status.postauth.ready, 'postauth should be ready from cache');
1248
1097
  assert.isFalse(cacheSpy.called, 'should not write cache during warm-up-only path');
1249
1098
 
1250
1099
  // Cleanup
@@ -1260,29 +1109,26 @@ describe('webex-core', () => {
1260
1109
  preauth: {serviceLinks: {}, hostCatalog: {}},
1261
1110
  postauth: {serviceLinks: {}, hostCatalog: {}},
1262
1111
  };
1263
-
1112
+
1264
1113
  window.localStorage.setItem(CATALOG_CACHE_KEY_V1, JSON.stringify(staleCached));
1265
-
1114
+
1266
1115
  const warmed = await services._loadCatalogFromCache();
1267
-
1116
+
1268
1117
  assert.isFalse(warmed, 'stale cache must not warm');
1269
- assert.isNull(
1270
- window.localStorage.getItem(CATALOG_CACHE_KEY_V1),
1271
- 'expired cache must be cleared'
1272
- );
1118
+ assert.isNull(window.localStorage.getItem(CATALOG_CACHE_KEY_V1), 'expired cache must be cleared');
1273
1119
  assert.isFalse(catalog.status.preauth.ready);
1274
1120
  assert.isFalse(catalog.status.postauth.ready);
1275
1121
  });
1276
-
1122
+
1277
1123
  it('clearCatalogCache() removes the cached entry', async () => {
1278
1124
  const CATALOG_CACHE_KEY_V1 = 'services.v1.u2cHostMap';
1279
1125
  window.localStorage.setItem(CATALOG_CACHE_KEY_V1, JSON.stringify({cachedAt: Date.now()}));
1280
-
1126
+
1281
1127
  await services.clearCatalogCache();
1282
-
1128
+
1283
1129
  assert.isNull(window.localStorage.getItem(CATALOG_CACHE_KEY_V1), 'cache should be cleared');
1284
1130
  });
1285
-
1131
+
1286
1132
  it('still fetches when forceRefresh=true even if ready', async () => {
1287
1133
  const CATALOG_CACHE_KEY_V1 = 'services.v1.u2cHostMap';
1288
1134
  window.localStorage.setItem(
@@ -1294,24 +1140,20 @@ describe('webex-core', () => {
1294
1140
  postauth: {serviceLinks: {}, hostCatalog: {}},
1295
1141
  })
1296
1142
  );
1297
-
1143
+
1298
1144
  // warm from cache
1299
1145
  const warmed = await services._loadCatalogFromCache();
1300
1146
  assert.isTrue(warmed);
1301
1147
  assert.isTrue(catalog.status.preauth.ready);
1302
1148
  assert.isTrue(catalog.status.postauth.ready);
1303
-
1149
+
1304
1150
  const fetchSpy = sinon.spy(services, '_fetchNewServiceHostmap');
1305
-
1151
+
1306
1152
  // with forceRefresh we should fetch despite ready=true
1307
- await services.updateServices({
1308
- from: 'limited',
1309
- query: {orgId: 'urn:EXAMPLE:org'},
1310
- forceRefresh: true,
1311
- });
1153
+ await services.updateServices({from: 'limited', query: {orgId: 'urn:EXAMPLE:org'}, forceRefresh: true});
1312
1154
  // pass an empty query to avoid spreading undefined in qs construction
1313
1155
  await services.updateServices({forceRefresh: true});
1314
-
1156
+
1315
1157
  assert.isTrue(fetchSpy.called, 'forceRefresh should bypass cache short-circuit');
1316
1158
  fetchSpy.restore();
1317
1159
  });
@@ -1372,11 +1214,9 @@ describe('webex-core', () => {
1372
1214
  );
1373
1215
  // formatter returns at least one entry to mark ready
1374
1216
  services._formatReceivedHostmap.restore && services._formatReceivedHostmap.restore();
1375
- sinon
1376
- .stub(services, '_formatReceivedHostmap')
1377
- .callsFake(() => [
1378
- {name: 'hydra', defaultUrl: 'https://api.ciscospark.com/v1', hosts: []},
1379
- ]);
1217
+ sinon.stub(services, '_formatReceivedHostmap').callsFake(() => [
1218
+ {name: 'hydra', defaultUrl: 'https://api.ciscospark.com/v1', hosts: []},
1219
+ ]);
1380
1220
 
1381
1221
  const warmed = await services._loadCatalogFromCache();
1382
1222
  assert.isTrue(warmed);
@@ -1398,11 +1238,9 @@ describe('webex-core', () => {
1398
1238
  })
1399
1239
  );
1400
1240
  services._formatReceivedHostmap.restore && services._formatReceivedHostmap.restore();
1401
- sinon
1402
- .stub(services, '_formatReceivedHostmap')
1403
- .callsFake(() => [
1404
- {name: 'hydra', defaultUrl: 'https://api.ciscospark.com/v1', hosts: []},
1405
- ]);
1241
+ sinon.stub(services, '_formatReceivedHostmap').callsFake(() => [
1242
+ {name: 'hydra', defaultUrl: 'https://api.ciscospark.com/v1', hosts: []},
1243
+ ]);
1406
1244
 
1407
1245
  const warmed = await services._loadCatalogFromCache();
1408
1246
  // function returns true if overall cache path succeeded; we only verify group readiness
@@ -1429,17 +1267,12 @@ describe('webex-core', () => {
1429
1267
  })
1430
1268
  );
1431
1269
  services._formatReceivedHostmap.restore && services._formatReceivedHostmap.restore();
1432
- sinon
1433
- .stub(services, '_formatReceivedHostmap')
1434
- .callsFake(() => [
1435
- {name: 'hydra', defaultUrl: 'https://api.ciscospark.com/v1', hosts: []},
1436
- ]);
1270
+ sinon.stub(services, '_formatReceivedHostmap').callsFake(() => [
1271
+ {name: 'hydra', defaultUrl: 'https://api.ciscospark.com/v1', hosts: []},
1272
+ ]);
1437
1273
 
1438
1274
  await services._loadCatalogFromCache();
1439
- assert.isFalse(
1440
- catalog.status.preauth.ready,
1441
- 'preauth should not warm on selection mismatch'
1442
- );
1275
+ assert.isFalse(catalog.status.preauth.ready, 'preauth should not warm on selection mismatch');
1443
1276
  });
1444
1277
 
1445
1278
  it('skips warming when environment fingerprint mismatches', async () => {
@@ -1450,10 +1283,7 @@ describe('webex-core', () => {
1450
1283
  JSON.stringify({
1451
1284
  cachedAt: Date.now(),
1452
1285
  env: {fedramp: false, u2cDiscoveryUrl: 'https://u2c.other.com/u2c/api/v1'},
1453
- preauth: {
1454
- hostMap: {serviceLinks: {}, hostCatalog: {}},
1455
- meta: {selectionType: 'mode', selectionValue: 'DEFAULT_BY_PROXIMITY'},
1456
- },
1286
+ preauth: {hostMap: {serviceLinks: {}, hostCatalog: {}}, meta: {selectionType: 'mode', selectionValue: 'DEFAULT_BY_PROXIMITY'}},
1457
1287
  })
1458
1288
  );
1459
1289
  // current env
@@ -1468,6 +1298,7 @@ describe('webex-core', () => {
1468
1298
  assert.isFalse(catalog.status.postauth.ready);
1469
1299
  });
1470
1300
  });
1301
+
1471
1302
  });
1472
1303
  });
1473
1304
  /* eslint-enable no-underscore-dangle */