@webex/webex-core 3.12.0-task-refactor.1 → 3.12.0-webex-services-ready.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/config.js +7 -0
- package/dist/config.js.map +1 -1
- package/dist/credentials-config.js +12 -0
- package/dist/credentials-config.js.map +1 -1
- package/dist/interceptors/redirect.js +1 -1
- package/dist/interceptors/redirect.js.map +1 -1
- package/dist/lib/batcher.js +23 -7
- package/dist/lib/batcher.js.map +1 -1
- package/dist/lib/credentials/credentials.js +48 -4
- package/dist/lib/credentials/credentials.js.map +1 -1
- package/dist/lib/credentials/token.js +1 -1
- package/dist/lib/services/service-url.js +11 -1
- package/dist/lib/services/service-url.js.map +1 -1
- package/dist/lib/services/services.js +501 -96
- package/dist/lib/services/services.js.map +1 -1
- package/dist/lib/services-v2/services-v2.js +426 -42
- package/dist/lib/services-v2/services-v2.js.map +1 -1
- package/dist/lib/services-v2/types.js.map +1 -1
- package/dist/plugins/logger.js +1 -1
- package/dist/webex-core.js +2 -2
- package/dist/webex-core.js.map +1 -1
- package/package.json +13 -13
- package/src/config.js +7 -0
- package/src/credentials-config.js +13 -0
- package/src/interceptors/redirect.js +4 -1
- package/src/lib/batcher.js +25 -10
- package/src/lib/credentials/credentials.js +50 -3
- package/src/lib/services/service-url.js +9 -1
- package/src/lib/services/services.js +368 -14
- package/src/lib/services-v2/services-v2.ts +353 -10
- package/src/lib/services-v2/types.ts +5 -0
- package/test/integration/spec/services/service-catalog.js +16 -11
- package/test/integration/spec/services/services.js +38 -11
- package/test/integration/spec/services-v2/services-v2.js +29 -8
- package/test/unit/spec/credentials/credentials.js +133 -2
- package/test/unit/spec/lib/batcher.js +56 -0
- package/test/unit/spec/services/service-url.js +110 -0
- package/test/unit/spec/services/services.js +680 -80
- package/test/unit/spec/services-v2/services-v2.ts +484 -62
- package/test/unit/spec/webex-core.js +2 -0
- package/test/unit/spec/webex-internal-core.js +2 -0
|
@@ -34,6 +34,53 @@ describe('webex-core', () => {
|
|
|
34
34
|
});
|
|
35
35
|
|
|
36
36
|
describe('#initialize', () => {
|
|
37
|
+
it('listens for "loaded" event instead of "ready" to avoid deadlock', () => {
|
|
38
|
+
services.listenToOnce = sinon.stub();
|
|
39
|
+
services.initialize();
|
|
40
|
+
|
|
41
|
+
// Second listenToOnce call should be for 'loaded' event
|
|
42
|
+
assert.equal(services.listenToOnce.getCall(1).args[1], 'loaded');
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('services.ready starts as false', () => {
|
|
46
|
+
assert.isFalse(services.ready);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('sets ready to true and triggers services:initialized when initialization succeeds with credentials', async () => {
|
|
50
|
+
services.listenToOnce = sinon.stub();
|
|
51
|
+
services.initServiceCatalogs = sinon.stub().returns(Promise.resolve());
|
|
52
|
+
services.trigger = sinon.stub();
|
|
53
|
+
services.webex.credentials = {
|
|
54
|
+
supertoken: {
|
|
55
|
+
access_token: 'token',
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
services.initialize();
|
|
60
|
+
|
|
61
|
+
// call the onLoaded callback
|
|
62
|
+
services.listenToOnce.getCall(1).args[2]();
|
|
63
|
+
await waitForAsync();
|
|
64
|
+
|
|
65
|
+
assert.isTrue(services.ready);
|
|
66
|
+
sinon.assert.calledWith(services.trigger, 'services:initialized');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('sets ready to true and triggers services:initialized when initialization succeeds without credentials', async () => {
|
|
70
|
+
services.listenToOnce = sinon.stub();
|
|
71
|
+
services.collectPreauthCatalog = sinon.stub().returns(Promise.resolve());
|
|
72
|
+
services.trigger = sinon.stub();
|
|
73
|
+
|
|
74
|
+
services.initialize();
|
|
75
|
+
|
|
76
|
+
// call the onLoaded callback
|
|
77
|
+
services.listenToOnce.getCall(1).args[2]();
|
|
78
|
+
await waitForAsync();
|
|
79
|
+
|
|
80
|
+
assert.isTrue(services.ready);
|
|
81
|
+
sinon.assert.calledWith(services.trigger, 'services:initialized');
|
|
82
|
+
});
|
|
83
|
+
|
|
37
84
|
it('initFailed is false when initialization succeeds and credentials are available', async () => {
|
|
38
85
|
services.listenToOnce = sinon.stub();
|
|
39
86
|
services.initServiceCatalogs = sinon.stub().returns(Promise.resolve());
|
|
@@ -45,7 +92,7 @@ describe('webex-core', () => {
|
|
|
45
92
|
|
|
46
93
|
services.initialize();
|
|
47
94
|
|
|
48
|
-
// call the
|
|
95
|
+
// call the onLoaded callback
|
|
49
96
|
services.listenToOnce.getCall(1).args[2]();
|
|
50
97
|
await waitForAsync();
|
|
51
98
|
|
|
@@ -58,7 +105,7 @@ describe('webex-core', () => {
|
|
|
58
105
|
|
|
59
106
|
services.initialize();
|
|
60
107
|
|
|
61
|
-
// call the
|
|
108
|
+
// call the onLoaded callback
|
|
62
109
|
services.listenToOnce.getCall(1).args[2]();
|
|
63
110
|
await waitForAsync();
|
|
64
111
|
|
|
@@ -69,7 +116,7 @@ describe('webex-core', () => {
|
|
|
69
116
|
{error: new Error('failed'), expectedMessage: 'failed'},
|
|
70
117
|
{error: undefined, expectedMessage: undefined},
|
|
71
118
|
])(
|
|
72
|
-
'sets initFailed to true when collectPreauthCatalog errors',
|
|
119
|
+
'sets initFailed to true when collectPreauthCatalog errors but still sets ready to true',
|
|
73
120
|
async ({error, expectedMessage}) => {
|
|
74
121
|
services.collectPreauthCatalog = sinon.stub().callsFake(() => {
|
|
75
122
|
return Promise.reject(error);
|
|
@@ -77,15 +124,18 @@ describe('webex-core', () => {
|
|
|
77
124
|
|
|
78
125
|
services.listenToOnce = sinon.stub();
|
|
79
126
|
services.logger.error = sinon.stub();
|
|
127
|
+
services.trigger = sinon.stub();
|
|
80
128
|
|
|
81
129
|
services.initialize();
|
|
82
130
|
|
|
83
|
-
// call the
|
|
131
|
+
// call the onLoaded callback
|
|
84
132
|
services.listenToOnce.getCall(1).args[2]();
|
|
85
133
|
|
|
86
134
|
await waitForAsync();
|
|
87
135
|
|
|
88
136
|
assert.isTrue(services.initFailed);
|
|
137
|
+
assert.isTrue(services.ready);
|
|
138
|
+
sinon.assert.calledWith(services.trigger, 'services:initialized');
|
|
89
139
|
sinon.assert.calledWith(
|
|
90
140
|
services.logger.error,
|
|
91
141
|
`services: failed to init initial services when no credentials available, ${expectedMessage}`
|
|
@@ -97,7 +147,7 @@ describe('webex-core', () => {
|
|
|
97
147
|
{error: new Error('failed'), expectedMessage: 'failed'},
|
|
98
148
|
{error: undefined, expectedMessage: undefined},
|
|
99
149
|
])(
|
|
100
|
-
'sets initFailed to true when initServiceCatalogs errors',
|
|
150
|
+
'sets initFailed to true when initServiceCatalogs errors but still sets ready to true',
|
|
101
151
|
async ({error, expectedMessage}) => {
|
|
102
152
|
services.initServiceCatalogs = sinon.stub().callsFake(() => {
|
|
103
153
|
return Promise.reject(error);
|
|
@@ -110,21 +160,118 @@ describe('webex-core', () => {
|
|
|
110
160
|
|
|
111
161
|
services.listenToOnce = sinon.stub();
|
|
112
162
|
services.logger.error = sinon.stub();
|
|
163
|
+
services.trigger = sinon.stub();
|
|
113
164
|
|
|
114
165
|
services.initialize();
|
|
115
166
|
|
|
116
|
-
// call the
|
|
167
|
+
// call the onLoaded callback
|
|
117
168
|
services.listenToOnce.getCall(1).args[2]();
|
|
118
169
|
|
|
119
170
|
await waitForAsync();
|
|
120
171
|
|
|
121
172
|
assert.isTrue(services.initFailed);
|
|
173
|
+
assert.isTrue(services.ready);
|
|
174
|
+
sinon.assert.calledWith(services.trigger, 'services:initialized');
|
|
122
175
|
sinon.assert.calledWith(
|
|
123
176
|
services.logger.error,
|
|
124
177
|
`services: failed to init initial services when credentials available, ${expectedMessage}`
|
|
125
178
|
);
|
|
126
179
|
}
|
|
127
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);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
it('logs an error when initServiceCatalogs fails after auth', async () => {
|
|
243
|
+
services.listenToOnce = sinon.stub();
|
|
244
|
+
services._loadCatalogFromCache = sinon.stub().returns(Promise.resolve(false));
|
|
245
|
+
services.collectPreauthCatalog = sinon.stub().returns(Promise.resolve());
|
|
246
|
+
services.initServiceCatalogs = sinon.stub().rejects(new Error('auth failed'));
|
|
247
|
+
services.logger.error = sinon.stub();
|
|
248
|
+
services.trigger = sinon.stub();
|
|
249
|
+
// Ensure no credentials so we go to the preauth path
|
|
250
|
+
services.webex.credentials = {};
|
|
251
|
+
|
|
252
|
+
services.initialize();
|
|
253
|
+
|
|
254
|
+
// Get the catalog after initialize creates it
|
|
255
|
+
const testCatalog = services._getCatalog();
|
|
256
|
+
testCatalog.status.postauth.ready = false;
|
|
257
|
+
|
|
258
|
+
// call the onLoaded callback (preauth path)
|
|
259
|
+
services.listenToOnce.getCall(1).args[2]();
|
|
260
|
+
await waitForAsync();
|
|
261
|
+
|
|
262
|
+
// Now set canAuthorize to true to simulate auth completing
|
|
263
|
+
services.webex.canAuthorize = true;
|
|
264
|
+
|
|
265
|
+
// call the change:canAuthorize callback (should be call 2)
|
|
266
|
+
services.listenToOnce.getCall(2).args[2]();
|
|
267
|
+
await waitForAsync();
|
|
268
|
+
|
|
269
|
+
sinon.assert.calledWith(
|
|
270
|
+
services.logger.error,
|
|
271
|
+
'services: failed to init service catalogs after auth, auth failed'
|
|
272
|
+
);
|
|
273
|
+
});
|
|
274
|
+
});
|
|
128
275
|
});
|
|
129
276
|
|
|
130
277
|
describe('#initServiceCatalogs', () => {
|
|
@@ -278,7 +425,7 @@ describe('webex-core', () => {
|
|
|
278
425
|
|
|
279
426
|
services.initServiceCatalogs = sinon.stub().returns(Promise.resolve());
|
|
280
427
|
services.webex.credentials = {
|
|
281
|
-
getOrgId: sinon.stub().returns('')
|
|
428
|
+
getOrgId: sinon.stub().returns(''),
|
|
282
429
|
};
|
|
283
430
|
catalog.status = {};
|
|
284
431
|
});
|
|
@@ -319,7 +466,7 @@ describe('webex-core', () => {
|
|
|
319
466
|
const serviceGroup = 'postauth';
|
|
320
467
|
const hostmap = {services: [{hostmap: 'hostmap'}]};
|
|
321
468
|
|
|
322
|
-
services._formatReceivedHostmap = sinon.stub().returns({services
|
|
469
|
+
services._formatReceivedHostmap = sinon.stub().returns({services: [{some: 'hostmap'}]});
|
|
323
470
|
|
|
324
471
|
catalog.updateServiceGroups = sinon.stub().returns(Promise.resolve([{some: 'value'}]));
|
|
325
472
|
|
|
@@ -335,7 +482,7 @@ describe('webex-core', () => {
|
|
|
335
482
|
const serviceGroup = 'postauth';
|
|
336
483
|
const hostmap = {};
|
|
337
484
|
|
|
338
|
-
services._formatReceivedHostmap = sinon.stub().returns({services
|
|
485
|
+
services._formatReceivedHostmap = sinon.stub().returns({services: undefined});
|
|
339
486
|
|
|
340
487
|
catalog.updateServiceGroups = sinon.stub().returns(Promise.resolve([{some: 'value'}]));
|
|
341
488
|
|
|
@@ -574,13 +721,13 @@ describe('webex-core', () => {
|
|
|
574
721
|
});
|
|
575
722
|
|
|
576
723
|
describe('#invalidateCache', () => {
|
|
577
|
-
beforeEach(
|
|
724
|
+
beforeEach(() => {
|
|
578
725
|
services.initServiceCatalogs = sinon.stub().returns(Promise.resolve());
|
|
579
726
|
services.webex.credentials = {
|
|
580
|
-
getOrgId: sinon.stub().returns('')
|
|
727
|
+
getOrgId: sinon.stub().returns(''),
|
|
581
728
|
};
|
|
582
729
|
catalog.status = {};
|
|
583
|
-
})
|
|
730
|
+
});
|
|
584
731
|
it('should log the timestamp parameter', async () => {
|
|
585
732
|
const timestamp = '1234567890';
|
|
586
733
|
services.logger.info = sinon.stub();
|
|
@@ -588,7 +735,11 @@ describe('webex-core', () => {
|
|
|
588
735
|
|
|
589
736
|
await services.invalidateCache(timestamp);
|
|
590
737
|
|
|
591
|
-
assert.calledWith(
|
|
738
|
+
assert.calledWith(
|
|
739
|
+
services.logger.info,
|
|
740
|
+
'services: invalidate cache, timestamp:',
|
|
741
|
+
timestamp
|
|
742
|
+
);
|
|
592
743
|
});
|
|
593
744
|
|
|
594
745
|
it('should call initServiceCatalogs when invalidate timestamp is newer than catalog timestamp', async () => {
|
|
@@ -715,30 +866,30 @@ describe('webex-core', () => {
|
|
|
715
866
|
// Arrange: seed internal _services with mobius (including duplicate baseUrl)
|
|
716
867
|
services._services = [
|
|
717
868
|
{
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
{
|
|
722
|
-
{
|
|
723
|
-
{
|
|
869
|
+
id: 'urn:TEAM:us-east-2_a:mobius',
|
|
870
|
+
serviceName: 'mobius',
|
|
871
|
+
serviceUrls: [
|
|
872
|
+
{baseUrl: 'https://mobius-us-east-2.prod.infra.webex.com/api/v1', priority: 5},
|
|
873
|
+
{baseUrl: 'https://mobius-eu-central-1.prod.infra.webex.com/api/v1', priority: 10},
|
|
874
|
+
{baseUrl: 'https://mobius-ap-southeast-2.prod.infra.webex.com/api/v1', priority: 15}, // duplicate
|
|
724
875
|
],
|
|
725
876
|
},
|
|
726
877
|
{
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
878
|
+
id: 'urn:TEAM:ap-southeast-2_m:mobius',
|
|
879
|
+
serviceName: 'mobius',
|
|
880
|
+
serviceUrls: [
|
|
881
|
+
{
|
|
882
|
+
baseUrl: 'https://mobius-me-central-1.prod.infra.webex.com/api/v1',
|
|
883
|
+
priority: 5,
|
|
884
|
+
},
|
|
885
|
+
{
|
|
886
|
+
baseUrl: 'https://mobius-eu-central-1.prod.infra.webex.com/api/v1',
|
|
887
|
+
priority: 10,
|
|
888
|
+
},
|
|
889
|
+
{
|
|
890
|
+
baseUrl: 'https://mobius-ap-southeast-2.prod.infra.webex.com/api/v1',
|
|
891
|
+
priority: 15,
|
|
892
|
+
},
|
|
742
893
|
],
|
|
743
894
|
},
|
|
744
895
|
// Non-mobius service should be ignored by getMobiusClusters
|
|
@@ -756,41 +907,64 @@ describe('webex-core', () => {
|
|
|
756
907
|
assert.deepEqual(
|
|
757
908
|
clusters.map(({host, id, ttl, priority}) => ({host, id, ttl, priority})),
|
|
758
909
|
[
|
|
759
|
-
{
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
910
|
+
{
|
|
911
|
+
host: 'mobius-us-east-2.prod.infra.webex.com',
|
|
912
|
+
id: 'urn:TEAM:us-east-2_a:mobius',
|
|
913
|
+
ttl: 0,
|
|
914
|
+
priority: 5,
|
|
915
|
+
},
|
|
916
|
+
{
|
|
917
|
+
host: 'mobius-eu-central-1.prod.infra.webex.com',
|
|
918
|
+
id: 'urn:TEAM:us-east-2_a:mobius',
|
|
919
|
+
ttl: 0,
|
|
920
|
+
priority: 10,
|
|
921
|
+
},
|
|
922
|
+
{
|
|
923
|
+
host: 'mobius-ap-southeast-2.prod.infra.webex.com',
|
|
924
|
+
id: 'urn:TEAM:us-east-2_a:mobius',
|
|
925
|
+
ttl: 0,
|
|
926
|
+
priority: 15,
|
|
927
|
+
},
|
|
928
|
+
{
|
|
929
|
+
host: 'mobius-me-central-1.prod.infra.webex.com',
|
|
930
|
+
id: 'urn:TEAM:ap-southeast-2_m:mobius',
|
|
931
|
+
ttl: 0,
|
|
932
|
+
priority: 5,
|
|
933
|
+
},
|
|
763
934
|
]
|
|
764
935
|
);
|
|
765
936
|
});
|
|
766
937
|
});
|
|
938
|
+
|
|
767
939
|
describe('#isValidHost', () => {
|
|
768
940
|
beforeEach(() => {
|
|
769
941
|
// Setting up a mock services list
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
942
|
+
services._services = [
|
|
943
|
+
{
|
|
944
|
+
id: 'urn:IDENTITY:PC75:adminAudit',
|
|
945
|
+
serviceName: 'adminAudit',
|
|
946
|
+
serviceUrls: [
|
|
947
|
+
{
|
|
948
|
+
baseUrl: 'https://audit-ci-r.wbx2.com/audit-ci/api/v2',
|
|
949
|
+
priority: 5,
|
|
950
|
+
},
|
|
951
|
+
{
|
|
952
|
+
baseUrl: 'https://audit-ci-t.wbx2.com/audit-ci/api/v2',
|
|
953
|
+
priority: 10,
|
|
954
|
+
},
|
|
955
|
+
],
|
|
956
|
+
},
|
|
957
|
+
{
|
|
958
|
+
id: 'urn:IDENTITY:PC75:cdf',
|
|
959
|
+
serviceName: 'cdf',
|
|
960
|
+
serviceUrls: [
|
|
961
|
+
{
|
|
962
|
+
baseUrl: 'https://wapdavis.webex.com/davis/api/v1',
|
|
963
|
+
priority: 5,
|
|
964
|
+
},
|
|
965
|
+
],
|
|
966
|
+
},
|
|
967
|
+
];
|
|
794
968
|
});
|
|
795
969
|
afterAll(() => {
|
|
796
970
|
// Clean up the mock services list
|
|
@@ -813,5 +987,253 @@ describe('webex-core', () => {
|
|
|
813
987
|
assert.isFalse(services.isValidHost([]));
|
|
814
988
|
});
|
|
815
989
|
});
|
|
990
|
+
|
|
991
|
+
describe('#isIntegrationEnvironment', () => {
|
|
992
|
+
it('returns true when u2c URL contains "intb"', () => {
|
|
993
|
+
services.webex.config = {
|
|
994
|
+
services: {
|
|
995
|
+
discovery: {
|
|
996
|
+
u2c: 'https://u2c-intb.ciscospark.com/u2c/api/v1',
|
|
997
|
+
},
|
|
998
|
+
},
|
|
999
|
+
};
|
|
1000
|
+
assert.isTrue(services.isIntegrationEnvironment());
|
|
1001
|
+
});
|
|
1002
|
+
|
|
1003
|
+
it('returns false when u2c URL does not contain "intb" (production)', () => {
|
|
1004
|
+
services.webex.config = {
|
|
1005
|
+
services: {
|
|
1006
|
+
discovery: {
|
|
1007
|
+
u2c: 'https://u2c.wbx2.com/u2c/api/v1',
|
|
1008
|
+
},
|
|
1009
|
+
},
|
|
1010
|
+
};
|
|
1011
|
+
assert.isFalse(services.isIntegrationEnvironment());
|
|
1012
|
+
});
|
|
1013
|
+
|
|
1014
|
+
it('returns false when u2c URL is for FedRAMP', () => {
|
|
1015
|
+
services.webex.config = {
|
|
1016
|
+
services: {
|
|
1017
|
+
discovery: {
|
|
1018
|
+
u2c: 'https://u2c.gov.ciscospark.com/u2c/api/v1',
|
|
1019
|
+
},
|
|
1020
|
+
},
|
|
1021
|
+
};
|
|
1022
|
+
assert.isFalse(services.isIntegrationEnvironment());
|
|
1023
|
+
});
|
|
1024
|
+
|
|
1025
|
+
it('returns false when u2c URL is undefined', () => {
|
|
1026
|
+
services.webex.config = {
|
|
1027
|
+
services: {
|
|
1028
|
+
discovery: {
|
|
1029
|
+
u2c: undefined,
|
|
1030
|
+
},
|
|
1031
|
+
},
|
|
1032
|
+
};
|
|
1033
|
+
assert.isFalse(services.isIntegrationEnvironment());
|
|
1034
|
+
});
|
|
1035
|
+
|
|
1036
|
+
it('returns false when config is not available', () => {
|
|
1037
|
+
services.webex.config = undefined;
|
|
1038
|
+
assert.isFalse(services.isIntegrationEnvironment());
|
|
1039
|
+
});
|
|
1040
|
+
});
|
|
1041
|
+
|
|
1042
|
+
describe('U2C catalog cache behavior (v2)', () => {
|
|
1043
|
+
const CATALOG_CACHE_KEY_V2 = 'services.v2.u2cHostMap';
|
|
1044
|
+
let windowBackup;
|
|
1045
|
+
let localStorageBackup;
|
|
1046
|
+
|
|
1047
|
+
const makeLocalStorageShim = () => {
|
|
1048
|
+
const store = new Map<string, string>();
|
|
1049
|
+
return {
|
|
1050
|
+
getItem: (k: string) => (store.has(k) ? store.get(k) : null),
|
|
1051
|
+
setItem: (k: string, v: string) => store.set(k, v),
|
|
1052
|
+
removeItem: (k: string) => store.delete(k),
|
|
1053
|
+
_store: store,
|
|
1054
|
+
};
|
|
1055
|
+
};
|
|
1056
|
+
|
|
1057
|
+
beforeEach(() => {
|
|
1058
|
+
// Stub window.localStorage
|
|
1059
|
+
windowBackup = global.window;
|
|
1060
|
+
if (!global.window) global.window = {} as Window & typeof globalThis;
|
|
1061
|
+
localStorageBackup = global.window.localStorage;
|
|
1062
|
+
global.window.localStorage = makeLocalStorageShim();
|
|
1063
|
+
// Enable U2C caching feature flag for tests that depend on cache writes/reads
|
|
1064
|
+
services.webex.config = services.webex.config || {};
|
|
1065
|
+
services.webex.config.calling = {...(services.webex.config.calling || {}), cacheU2C: true};
|
|
1066
|
+
// Ensure code under test uses our shim via util method
|
|
1067
|
+
sinon.stub(services, '_getLocalStorageSafe').returns(global.window.localStorage);
|
|
1068
|
+
// default current env
|
|
1069
|
+
services.webex.config = services.webex.config || {};
|
|
1070
|
+
services.webex.config.services = services.webex.config.services || {discovery: {}};
|
|
1071
|
+
services.webex.config.services.discovery.u2c =
|
|
1072
|
+
services.webex.config.services.discovery.u2c || 'https://u2c.wbx2.com/u2c/api/v1';
|
|
1073
|
+
services.webex.config.fedramp =
|
|
1074
|
+
typeof services.webex.config.fedramp === 'boolean'
|
|
1075
|
+
? services.webex.config.fedramp
|
|
1076
|
+
: false;
|
|
1077
|
+
});
|
|
1078
|
+
|
|
1079
|
+
afterEach(() => {
|
|
1080
|
+
global.window.localStorage = localStorageBackup || undefined;
|
|
1081
|
+
if (!windowBackup) {
|
|
1082
|
+
delete global.window;
|
|
1083
|
+
} else {
|
|
1084
|
+
global.window = windowBackup;
|
|
1085
|
+
}
|
|
1086
|
+
// Restore util stub if present
|
|
1087
|
+
if (services._getLocalStorageSafe && services._getLocalStorageSafe.restore) {
|
|
1088
|
+
services._getLocalStorageSafe.restore();
|
|
1089
|
+
}
|
|
1090
|
+
});
|
|
1091
|
+
|
|
1092
|
+
it('stores selection metadata and env on cache write for preauth', async () => {
|
|
1093
|
+
// Arrange env
|
|
1094
|
+
services.webex.config.services.discovery.u2c = 'https://u2c.wbx2.com/u2c/api/v1';
|
|
1095
|
+
services.webex.config.fedramp = false;
|
|
1096
|
+
|
|
1097
|
+
// Act
|
|
1098
|
+
await services._cacheCatalog(
|
|
1099
|
+
'preauth',
|
|
1100
|
+
{services: [], timestamp: Date.now().toString()},
|
|
1101
|
+
{selectionType: 'orgId', selectionValue: 'urn:EXAMPLE:org'}
|
|
1102
|
+
);
|
|
1103
|
+
|
|
1104
|
+
// Assert
|
|
1105
|
+
const raw = window.localStorage.getItem(CATALOG_CACHE_KEY_V2);
|
|
1106
|
+
assert.isString(raw);
|
|
1107
|
+
const parsed = JSON.parse(raw as string);
|
|
1108
|
+
assert.deepEqual(parsed.env, {
|
|
1109
|
+
fedramp: false,
|
|
1110
|
+
u2cDiscoveryUrl: 'https://u2c.wbx2.com/u2c/api/v1',
|
|
1111
|
+
});
|
|
1112
|
+
assert.isObject(parsed.preauth);
|
|
1113
|
+
assert.deepEqual(parsed.preauth.meta, {
|
|
1114
|
+
selectionType: 'orgId',
|
|
1115
|
+
selectionValue: 'urn:EXAMPLE:org',
|
|
1116
|
+
});
|
|
1117
|
+
});
|
|
1118
|
+
|
|
1119
|
+
it('warms preauth from cache when selection meta matches intended orgId', async () => {
|
|
1120
|
+
// Arrange current env and credentials
|
|
1121
|
+
services.webex.config.services.discovery.u2c = 'https://u2c.wbx2.com/u2c/api/v1';
|
|
1122
|
+
services.webex.config.fedramp = false;
|
|
1123
|
+
services.webex.credentials = {
|
|
1124
|
+
canAuthorize: true,
|
|
1125
|
+
getOrgId: sinon.stub().returns('urn:EXAMPLE:org'),
|
|
1126
|
+
};
|
|
1127
|
+
// Seed cache
|
|
1128
|
+
window.localStorage.setItem(
|
|
1129
|
+
CATALOG_CACHE_KEY_V2,
|
|
1130
|
+
JSON.stringify({
|
|
1131
|
+
cachedAt: Date.now(),
|
|
1132
|
+
env: {fedramp: false, u2cDiscoveryUrl: 'https://u2c.wbx2.com/u2c/api/v1'},
|
|
1133
|
+
preauth: {
|
|
1134
|
+
hostMap: {services: [], timestamp: '1'},
|
|
1135
|
+
meta: {selectionType: 'orgId', selectionValue: 'urn:EXAMPLE:org'},
|
|
1136
|
+
},
|
|
1137
|
+
})
|
|
1138
|
+
);
|
|
1139
|
+
// Spy updateServiceGroups
|
|
1140
|
+
const spy = sinon.spy(services._getCatalog(), 'updateServiceGroups');
|
|
1141
|
+
|
|
1142
|
+
// Act
|
|
1143
|
+
const warmed = await services._loadCatalogFromCache();
|
|
1144
|
+
|
|
1145
|
+
// Assert
|
|
1146
|
+
assert.isTrue(warmed);
|
|
1147
|
+
assert.isTrue(
|
|
1148
|
+
spy.calledWith('preauth', [], '1'),
|
|
1149
|
+
'expected preauth to be warmed when selection matches'
|
|
1150
|
+
);
|
|
1151
|
+
spy.restore && spy.restore();
|
|
1152
|
+
});
|
|
1153
|
+
|
|
1154
|
+
it('does not warm preauth when selection meta is proximity mode', async () => {
|
|
1155
|
+
// Arrange env
|
|
1156
|
+
services.webex.config.services.discovery.u2c = 'https://u2c.wbx2.com/u2c/api/v1';
|
|
1157
|
+
services.webex.config.fedramp = false;
|
|
1158
|
+
window.localStorage.setItem(
|
|
1159
|
+
CATALOG_CACHE_KEY_V2,
|
|
1160
|
+
JSON.stringify({
|
|
1161
|
+
cachedAt: Date.now(),
|
|
1162
|
+
env: {fedramp: false, u2cDiscoveryUrl: 'https://u2c.wbx2.com/u2c/api/v1'},
|
|
1163
|
+
preauth: {
|
|
1164
|
+
hostMap: {services: [], timestamp: '1'},
|
|
1165
|
+
meta: {selectionType: 'mode', selectionValue: 'DEFAULT_BY_PROXIMITY'},
|
|
1166
|
+
},
|
|
1167
|
+
})
|
|
1168
|
+
);
|
|
1169
|
+
const spy = sinon.spy(services._getCatalog(), 'updateServiceGroups');
|
|
1170
|
+
|
|
1171
|
+
// Act
|
|
1172
|
+
const warmed = await services._loadCatalogFromCache();
|
|
1173
|
+
|
|
1174
|
+
// Assert: overall warm-up succeeds, but preauth is skipped
|
|
1175
|
+
assert.isTrue(warmed);
|
|
1176
|
+
assert.isFalse(
|
|
1177
|
+
spy.calledWith('preauth', sinon.match.any, sinon.match.any),
|
|
1178
|
+
'expected preauth not to be warmed for proximity mode'
|
|
1179
|
+
);
|
|
1180
|
+
spy.restore && spy.restore();
|
|
1181
|
+
});
|
|
1182
|
+
|
|
1183
|
+
it('does not warm preauth when selection meta mismatches intended selection', async () => {
|
|
1184
|
+
// Arrange env and credentials
|
|
1185
|
+
services.webex.config.services.discovery.u2c = 'https://u2c.wbx2.com/u2c/api/v1';
|
|
1186
|
+
services.webex.config.fedramp = false;
|
|
1187
|
+
services.webex.credentials = {
|
|
1188
|
+
canAuthorize: true,
|
|
1189
|
+
getOrgId: sinon.stub().returns('urn:EXAMPLE:org'),
|
|
1190
|
+
};
|
|
1191
|
+
window.localStorage.setItem(
|
|
1192
|
+
CATALOG_CACHE_KEY_V2,
|
|
1193
|
+
JSON.stringify({
|
|
1194
|
+
cachedAt: Date.now(),
|
|
1195
|
+
env: {fedramp: false, u2cDiscoveryUrl: 'https://u2c.wbx2.com/u2c/api/v1'},
|
|
1196
|
+
preauth: {
|
|
1197
|
+
hostMap: {services: [], timestamp: '1'},
|
|
1198
|
+
meta: {selectionType: 'orgId', selectionValue: 'urn:DIFF:org'},
|
|
1199
|
+
},
|
|
1200
|
+
})
|
|
1201
|
+
);
|
|
1202
|
+
const spy = sinon.spy(services._getCatalog(), 'updateServiceGroups');
|
|
1203
|
+
|
|
1204
|
+
const warmed = await services._loadCatalogFromCache();
|
|
1205
|
+
|
|
1206
|
+
assert.isTrue(warmed);
|
|
1207
|
+
assert.isFalse(
|
|
1208
|
+
spy.calledWith('preauth', sinon.match.any, sinon.match.any),
|
|
1209
|
+
'expected preauth not to be warmed on selection mismatch'
|
|
1210
|
+
);
|
|
1211
|
+
spy.restore && spy.restore();
|
|
1212
|
+
});
|
|
1213
|
+
|
|
1214
|
+
it('skips warm entirely when environment fingerprint mismatches', async () => {
|
|
1215
|
+
// Cached env differs from current env
|
|
1216
|
+
services.webex.config.services.discovery.u2c = 'https://u2c.current.com/u2c/api/v1';
|
|
1217
|
+
services.webex.config.fedramp = false;
|
|
1218
|
+
window.localStorage.setItem(
|
|
1219
|
+
CATALOG_CACHE_KEY_V2,
|
|
1220
|
+
JSON.stringify({
|
|
1221
|
+
cachedAt: Date.now(),
|
|
1222
|
+
env: {fedramp: false, u2cDiscoveryUrl: 'https://u2c.cached.com/u2c/api/v1'},
|
|
1223
|
+
preauth: {
|
|
1224
|
+
hostMap: {services: [], timestamp: '1'},
|
|
1225
|
+
meta: {selectionType: 'orgId', selectionValue: 'urn:EXAMPLE:org'},
|
|
1226
|
+
},
|
|
1227
|
+
})
|
|
1228
|
+
);
|
|
1229
|
+
const spy = sinon.spy(services._getCatalog(), 'updateServiceGroups');
|
|
1230
|
+
|
|
1231
|
+
const warmed = await services._loadCatalogFromCache();
|
|
1232
|
+
|
|
1233
|
+
assert.isFalse(warmed, 'env mismatch should skip warm and return false');
|
|
1234
|
+
assert.isFalse(spy.called, 'no group should be warmed on env mismatch');
|
|
1235
|
+
spy.restore && spy.restore();
|
|
1236
|
+
});
|
|
1237
|
+
});
|
|
816
1238
|
});
|
|
817
1239
|
});
|