@webex/webex-core 3.11.0 → 3.12.0-llmrefactor.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/README.md +5 -2
- package/dist/config.js +15 -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/domains.js +90 -0
- package/dist/lib/domains.js.map +1 -0
- package/dist/lib/services/service-catalog.js +6 -10
- package/dist/lib/services/service-catalog.js.map +1 -1
- package/dist/lib/services/services.js +217 -31
- package/dist/lib/services/services.js.map +1 -1
- package/dist/lib/services-v2/service-catalog.js +6 -12
- package/dist/lib/services-v2/service-catalog.js.map +1 -1
- package/dist/lib/services-v2/services-v2.js +221 -30
- package/dist/lib/services-v2/services-v2.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 +17 -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/domains.ts +94 -0
- package/src/lib/services/service-catalog.js +6 -10
- package/src/lib/services/services.js +192 -17
- package/src/lib/services-v2/service-catalog.ts +6 -11
- package/src/lib/services-v2/services-v2.ts +195 -17
- package/test/fixtures/activation-email.ts +22 -0
- package/test/integration/spec/services/services.js +44 -8
- package/test/integration/spec/services-v2/services-v2.js +44 -8
- package/test/unit/spec/credentials/credentials.js +133 -2
- package/test/unit/spec/interceptors/auth.js +56 -0
- package/test/unit/spec/lib/batcher.js +56 -0
- package/test/unit/spec/services/service-catalog.js +93 -11
- package/test/unit/spec/services/services.js +358 -2
- package/test/unit/spec/services-v2/service-catalog.ts +93 -11
- package/test/unit/spec/services-v2/services-v2.ts +362 -0
|
@@ -20,6 +20,7 @@ import WebexCore, {
|
|
|
20
20
|
import testUsers from '@webex/test-helper-test-users';
|
|
21
21
|
import uuid from 'uuid';
|
|
22
22
|
import sinon from 'sinon';
|
|
23
|
+
import {createActivationEmail} from '../../../fixtures/activation-email';
|
|
23
24
|
|
|
24
25
|
/* eslint-disable no-underscore-dangle */
|
|
25
26
|
describe('webex-core', () => {
|
|
@@ -408,11 +409,13 @@ describe('webex-core', () => {
|
|
|
408
409
|
services._loadCatalogFromCache = sinon.stub().resolves(false);
|
|
409
410
|
services.initServiceCatalogs = sinon.stub().resolves();
|
|
410
411
|
services.initialize();
|
|
412
|
+
// The mode-specific ('ready'/'loaded') listener is registered inside the
|
|
413
|
+
// change:config handler, so fire change:config first, then 'ready'.
|
|
414
|
+
webex.trigger('change:config');
|
|
411
415
|
webex.trigger('ready');
|
|
412
416
|
// Wait for the async 'ready' handler to complete
|
|
413
417
|
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
414
418
|
assert.called(services.initServiceCatalogs);
|
|
415
|
-
assert.isTrue(catalog.isReady);
|
|
416
419
|
});
|
|
417
420
|
|
|
418
421
|
it('should collect different catalogs based on OrgId region', () =>
|
|
@@ -430,6 +433,39 @@ describe('webex-core', () => {
|
|
|
430
433
|
done();
|
|
431
434
|
}, 2000);
|
|
432
435
|
});
|
|
436
|
+
|
|
437
|
+
it('blocks webex.ready until services.ready flips when waitForCatalogInit is enabled', async () => {
|
|
438
|
+
const gatedWebex = new WebexCore({
|
|
439
|
+
credentials: {supertoken: webexUser.token},
|
|
440
|
+
config: {services: {waitForCatalogInit: true}},
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
// Before init settles, webex.ready must be false because services.ready
|
|
444
|
+
// is a dependency and starts false in the gated path.
|
|
445
|
+
assert.isFalse(gatedWebex.internal.services.ready, 'services.ready should start false');
|
|
446
|
+
assert.isFalse(gatedWebex.ready, 'webex.ready should not fire while services.ready is false');
|
|
447
|
+
|
|
448
|
+
// Wait up to 30s for services init to complete and flip ready.
|
|
449
|
+
await new Promise((resolve, reject) => {
|
|
450
|
+
if (gatedWebex.internal.services.ready) {
|
|
451
|
+
resolve();
|
|
452
|
+
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
const timer = setTimeout(
|
|
456
|
+
() => reject(new Error('timed out waiting for services.ready')),
|
|
457
|
+
30_000
|
|
458
|
+
);
|
|
459
|
+
|
|
460
|
+
gatedWebex.internal.services.once('change:ready', () => {
|
|
461
|
+
clearTimeout(timer);
|
|
462
|
+
resolve();
|
|
463
|
+
});
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
assert.isTrue(gatedWebex.internal.services.ready, 'services.ready should flip true after init settles');
|
|
467
|
+
assert.isTrue(gatedWebex.ready, 'webex.ready should fire once services.ready flips');
|
|
468
|
+
});
|
|
433
469
|
});
|
|
434
470
|
|
|
435
471
|
describe('#initServiceCatalogs()', () => {
|
|
@@ -892,7 +928,7 @@ describe('webex-core', () => {
|
|
|
892
928
|
|
|
893
929
|
it('validates a non-existing user', () =>
|
|
894
930
|
unauthServices
|
|
895
|
-
.validateUser({email:
|
|
931
|
+
.validateUser({email: createActivationEmail()})
|
|
896
932
|
.then((r) => {
|
|
897
933
|
assert.hasAllKeys(r, ['activated', 'exists', 'user', 'details']);
|
|
898
934
|
assert.equal(r.activated, false);
|
|
@@ -905,7 +941,7 @@ describe('webex-core', () => {
|
|
|
905
941
|
it('validates new user with activationOptions suppressEmail false', () =>
|
|
906
942
|
unauthServices
|
|
907
943
|
.validateUser({
|
|
908
|
-
email:
|
|
944
|
+
email: createActivationEmail(),
|
|
909
945
|
activationOptions: {suppressEmail: false},
|
|
910
946
|
})
|
|
911
947
|
.then((r) => {
|
|
@@ -921,7 +957,7 @@ describe('webex-core', () => {
|
|
|
921
957
|
it('validates new user with activationOptions suppressEmail true', () =>
|
|
922
958
|
unauthServices
|
|
923
959
|
.validateUser({
|
|
924
|
-
email:
|
|
960
|
+
email: createActivationEmail(),
|
|
925
961
|
activationOptions: {suppressEmail: true},
|
|
926
962
|
})
|
|
927
963
|
.then((r) => {
|
|
@@ -977,7 +1013,7 @@ describe('webex-core', () => {
|
|
|
977
1013
|
|
|
978
1014
|
return unauthServices
|
|
979
1015
|
.validateUser({
|
|
980
|
-
email:
|
|
1016
|
+
email: createActivationEmail(),
|
|
981
1017
|
activationOptions: {suppressEmail: true},
|
|
982
1018
|
})
|
|
983
1019
|
.then(() => {
|
|
@@ -991,7 +1027,7 @@ describe('webex-core', () => {
|
|
|
991
1027
|
|
|
992
1028
|
return unauthServices
|
|
993
1029
|
.validateUser({
|
|
994
|
-
email:
|
|
1030
|
+
email: createActivationEmail(),
|
|
995
1031
|
activationOptions: {suppressEmail: true},
|
|
996
1032
|
preloginUserId,
|
|
997
1033
|
})
|
|
@@ -1008,7 +1044,7 @@ describe('webex-core', () => {
|
|
|
1008
1044
|
|
|
1009
1045
|
return unauthServices
|
|
1010
1046
|
.validateUser({
|
|
1011
|
-
email:
|
|
1047
|
+
email: createActivationEmail(),
|
|
1012
1048
|
activationOptions: {suppressEmail: true},
|
|
1013
1049
|
})
|
|
1014
1050
|
.then(() => {
|
|
@@ -1031,7 +1067,7 @@ describe('webex-core', () => {
|
|
|
1031
1067
|
|
|
1032
1068
|
return userOnboardingServices
|
|
1033
1069
|
.validateUser({
|
|
1034
|
-
email:
|
|
1070
|
+
email: createActivationEmail(),
|
|
1035
1071
|
activationOptions: {suppressEmail: true},
|
|
1036
1072
|
})
|
|
1037
1073
|
.then(() => {
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
formattedServiceHostmapEntryTest,
|
|
26
26
|
serviceHostmapV2,
|
|
27
27
|
} from '../../../fixtures/host-catalog-v2';
|
|
28
|
+
import {createActivationEmail} from '../../../fixtures/activation-email';
|
|
28
29
|
|
|
29
30
|
// /* eslint-disable no-underscore-dangle */
|
|
30
31
|
describe('webex-core', () => {
|
|
@@ -320,11 +321,13 @@ describe('webex-core', () => {
|
|
|
320
321
|
services._loadCatalogFromCache = sinon.stub().resolves(false);
|
|
321
322
|
services.initServiceCatalogs = sinon.stub().resolves();
|
|
322
323
|
services.initialize();
|
|
324
|
+
// The mode-specific ('ready'/'loaded') listener is registered inside the
|
|
325
|
+
// change:config handler, so fire change:config first, then 'ready'.
|
|
326
|
+
webex.trigger('change:config');
|
|
323
327
|
webex.trigger('ready');
|
|
324
328
|
// Wait for the async 'ready' handler to complete
|
|
325
329
|
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
326
330
|
assert.called(services.initServiceCatalogs);
|
|
327
|
-
assert.isTrue(catalog.isReady);
|
|
328
331
|
});
|
|
329
332
|
|
|
330
333
|
it('should collect different catalogs based on OrgId region', () =>
|
|
@@ -341,6 +344,39 @@ describe('webex-core', () => {
|
|
|
341
344
|
done();
|
|
342
345
|
}, 2000);
|
|
343
346
|
});
|
|
347
|
+
|
|
348
|
+
it('blocks webex.ready until services.ready flips when waitForCatalogInit is enabled', async () => {
|
|
349
|
+
const gatedWebex = new WebexCore({
|
|
350
|
+
credentials: {supertoken: webexUser.token},
|
|
351
|
+
config: {services: {waitForCatalogInit: true}},
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
// Before init settles, webex.ready must be false because services.ready
|
|
355
|
+
// is a dependency and starts false in the gated path.
|
|
356
|
+
assert.isFalse(gatedWebex.internal.services.ready, 'services.ready should start false');
|
|
357
|
+
assert.isFalse(gatedWebex.ready, 'webex.ready should not fire while services.ready is false');
|
|
358
|
+
|
|
359
|
+
// Wait up to 30s for services init to complete and flip ready.
|
|
360
|
+
await new Promise((resolve, reject) => {
|
|
361
|
+
if (gatedWebex.internal.services.ready) {
|
|
362
|
+
resolve();
|
|
363
|
+
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
const timer = setTimeout(
|
|
367
|
+
() => reject(new Error('timed out waiting for services.ready')),
|
|
368
|
+
30_000
|
|
369
|
+
);
|
|
370
|
+
|
|
371
|
+
gatedWebex.internal.services.once('change:ready', () => {
|
|
372
|
+
clearTimeout(timer);
|
|
373
|
+
resolve();
|
|
374
|
+
});
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
assert.isTrue(gatedWebex.internal.services.ready, 'services.ready should flip true after init settles');
|
|
378
|
+
assert.isTrue(gatedWebex.ready, 'webex.ready should fire once services.ready flips');
|
|
379
|
+
});
|
|
344
380
|
});
|
|
345
381
|
|
|
346
382
|
describe('#initServiceCatalogs()', () => {
|
|
@@ -815,7 +851,7 @@ describe('webex-core', () => {
|
|
|
815
851
|
|
|
816
852
|
it('validates a non-existing user', () =>
|
|
817
853
|
unauthServices
|
|
818
|
-
.validateUser({email:
|
|
854
|
+
.validateUser({email: createActivationEmail()})
|
|
819
855
|
.then((r) => {
|
|
820
856
|
assert.hasAllKeys(r, ['activated', 'exists', 'user', 'details']);
|
|
821
857
|
assert.equal(r.activated, false);
|
|
@@ -825,7 +861,7 @@ describe('webex-core', () => {
|
|
|
825
861
|
it('validates new user with activationOptions suppressEmail false', () =>
|
|
826
862
|
unauthServices
|
|
827
863
|
.validateUser({
|
|
828
|
-
email:
|
|
864
|
+
email: createActivationEmail(),
|
|
829
865
|
activationOptions: {suppressEmail: false},
|
|
830
866
|
})
|
|
831
867
|
.then((r) => {
|
|
@@ -838,7 +874,7 @@ describe('webex-core', () => {
|
|
|
838
874
|
it('validates new user with activationOptions suppressEmail true', () =>
|
|
839
875
|
unauthServices
|
|
840
876
|
.validateUser({
|
|
841
|
-
email:
|
|
877
|
+
email: createActivationEmail(),
|
|
842
878
|
activationOptions: {suppressEmail: true},
|
|
843
879
|
})
|
|
844
880
|
.then((r) => {
|
|
@@ -882,7 +918,7 @@ describe('webex-core', () => {
|
|
|
882
918
|
|
|
883
919
|
return unauthServices
|
|
884
920
|
.validateUser({
|
|
885
|
-
email:
|
|
921
|
+
email: createActivationEmail(),
|
|
886
922
|
activationOptions: {suppressEmail: true},
|
|
887
923
|
})
|
|
888
924
|
.then(() => {
|
|
@@ -896,7 +932,7 @@ describe('webex-core', () => {
|
|
|
896
932
|
|
|
897
933
|
return unauthServices
|
|
898
934
|
.validateUser({
|
|
899
|
-
email:
|
|
935
|
+
email: createActivationEmail(),
|
|
900
936
|
activationOptions: {suppressEmail: true},
|
|
901
937
|
preloginUserId,
|
|
902
938
|
})
|
|
@@ -913,7 +949,7 @@ describe('webex-core', () => {
|
|
|
913
949
|
|
|
914
950
|
return unauthServices
|
|
915
951
|
.validateUser({
|
|
916
|
-
email:
|
|
952
|
+
email: createActivationEmail(),
|
|
917
953
|
activationOptions: {suppressEmail: true},
|
|
918
954
|
})
|
|
919
955
|
.then(() => {
|
|
@@ -936,7 +972,7 @@ describe('webex-core', () => {
|
|
|
936
972
|
|
|
937
973
|
return userOnboardingServices
|
|
938
974
|
.validateUser({
|
|
939
|
-
email:
|
|
975
|
+
email: createActivationEmail(),
|
|
940
976
|
activationOptions: {suppressEmail: true},
|
|
941
977
|
})
|
|
942
978
|
.then(() => {
|
|
@@ -183,6 +183,10 @@ describe('webex-core', () => {
|
|
|
183
183
|
webex.credentials.buildLoginUrl({state: 'state'});
|
|
184
184
|
}, /if specified, `options.state` must be an object/);
|
|
185
185
|
|
|
186
|
+
assert.throws(() => {
|
|
187
|
+
webex.credentials.buildLoginUrl({state: null});
|
|
188
|
+
}, /if specified, `options.state` must be an object/);
|
|
189
|
+
|
|
186
190
|
assert.doesNotThrow(() => {
|
|
187
191
|
webex.credentials.buildLoginUrl({state: {}});
|
|
188
192
|
}, /if specified, `options.state` must be an object/);
|
|
@@ -232,6 +236,132 @@ describe('webex-core', () => {
|
|
|
232
236
|
});
|
|
233
237
|
});
|
|
234
238
|
|
|
239
|
+
describe('#buildThirdPartyLoginUrl()', () => {
|
|
240
|
+
it('throws if both `oauth2provider` and `returnURL` are missing', () => {
|
|
241
|
+
const webex = new MockWebex();
|
|
242
|
+
const credentials = new Credentials(undefined, {parent: webex});
|
|
243
|
+
|
|
244
|
+
webex.trigger('change:config');
|
|
245
|
+
|
|
246
|
+
assert.throws(() => {
|
|
247
|
+
credentials.buildThirdPartyLoginUrl({});
|
|
248
|
+
}, /`options.oauth2provider` is required/);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it('throws if `oauth2provider` is missing', () => {
|
|
252
|
+
const webex = new MockWebex();
|
|
253
|
+
const credentials = new Credentials(undefined, {parent: webex});
|
|
254
|
+
|
|
255
|
+
webex.trigger('change:config');
|
|
256
|
+
|
|
257
|
+
assert.throws(() => {
|
|
258
|
+
credentials.buildThirdPartyLoginUrl({returnURL: 'https://web.webex.com'});
|
|
259
|
+
}, /`options.oauth2provider` is required/);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it('throws if `returnURL` is missing', () => {
|
|
263
|
+
const webex = new MockWebex();
|
|
264
|
+
const credentials = new Credentials(undefined, {parent: webex});
|
|
265
|
+
|
|
266
|
+
webex.trigger('change:config');
|
|
267
|
+
|
|
268
|
+
assert.throws(() => {
|
|
269
|
+
credentials.buildThirdPartyLoginUrl({oauth2provider: 'google'});
|
|
270
|
+
}, /`options.returnURL` is required/);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
skipInBrowser(it)('generates the third-party login url', () => {
|
|
274
|
+
const webex = new MockWebex();
|
|
275
|
+
const credentials = new Credentials(undefined, {parent: webex});
|
|
276
|
+
|
|
277
|
+
webex.trigger('change:config');
|
|
278
|
+
|
|
279
|
+
assert.equal(
|
|
280
|
+
credentials.buildThirdPartyLoginUrl({
|
|
281
|
+
oauth2provider: 'google',
|
|
282
|
+
returnURL: 'https://web.webex.com',
|
|
283
|
+
}),
|
|
284
|
+
`${
|
|
285
|
+
process.env.IDBROKER_BASE_URL || 'https://idbroker.webex.com'
|
|
286
|
+
}/idb/ThirdPartyLogin?oauth2provider=google&returnURL=https%3A%2F%2Fweb.webex.com`
|
|
287
|
+
);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
skipInBrowser(it)('generates the url with different parameter values', () => {
|
|
291
|
+
const webex = new MockWebex();
|
|
292
|
+
const credentials = new Credentials(undefined, {parent: webex});
|
|
293
|
+
|
|
294
|
+
webex.trigger('change:config');
|
|
295
|
+
|
|
296
|
+
assert.equal(
|
|
297
|
+
credentials.buildThirdPartyLoginUrl({
|
|
298
|
+
oauth2provider: 'apple',
|
|
299
|
+
returnURL: 'https://example.com/callback',
|
|
300
|
+
}),
|
|
301
|
+
`${
|
|
302
|
+
process.env.IDBROKER_BASE_URL || 'https://idbroker.webex.com'
|
|
303
|
+
}/idb/ThirdPartyLogin?oauth2provider=apple&returnURL=https%3A%2F%2Fexample.com%2Fcallback`
|
|
304
|
+
);
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
it('throws if `state` is not an object', () => {
|
|
308
|
+
const webex = new MockWebex();
|
|
309
|
+
const credentials = new Credentials(undefined, {parent: webex});
|
|
310
|
+
|
|
311
|
+
webex.trigger('change:config');
|
|
312
|
+
|
|
313
|
+
assert.throws(() => {
|
|
314
|
+
credentials.buildThirdPartyLoginUrl({
|
|
315
|
+
oauth2provider: 'google',
|
|
316
|
+
returnURL: 'https://web.webex.com',
|
|
317
|
+
state: 'not-an-object',
|
|
318
|
+
});
|
|
319
|
+
}, /`options.state` must be an object/);
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
skipInBrowser(it)('omits `state` when an empty object is provided', () => {
|
|
323
|
+
const webex = new MockWebex();
|
|
324
|
+
const credentials = new Credentials(undefined, {parent: webex});
|
|
325
|
+
|
|
326
|
+
webex.trigger('change:config');
|
|
327
|
+
|
|
328
|
+
const result = credentials.buildThirdPartyLoginUrl({
|
|
329
|
+
oauth2provider: 'google',
|
|
330
|
+
returnURL: 'https://web.webex.com',
|
|
331
|
+
state: {},
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
const parsed = new URL(result);
|
|
335
|
+
|
|
336
|
+
assert.isFalse(parsed.searchParams.has('state'));
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
skipInBrowser(it)('base64url-encodes a non-empty `state` and emits it as a top-level query param', () => {
|
|
340
|
+
const webex = new MockWebex();
|
|
341
|
+
const credentials = new Credentials(undefined, {parent: webex});
|
|
342
|
+
|
|
343
|
+
webex.trigger('change:config');
|
|
344
|
+
|
|
345
|
+
const result = credentials.buildThirdPartyLoginUrl({
|
|
346
|
+
oauth2provider: 'google',
|
|
347
|
+
returnURL: 'https://web.webex.com',
|
|
348
|
+
state: {csrf_token: 'abc', popUpSignIn: true},
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
// Literal base64url of '{"csrf_token":"abc","popUpSignIn":true}'
|
|
352
|
+
const expectedState = 'eyJjc3JmX3Rva2VuIjoiYWJjIiwicG9wVXBTaWduSW4iOnRydWV9';
|
|
353
|
+
|
|
354
|
+
assert.equal(
|
|
355
|
+
result,
|
|
356
|
+
`${
|
|
357
|
+
process.env.IDBROKER_BASE_URL || 'https://idbroker.webex.com'
|
|
358
|
+
}/idb/ThirdPartyLogin?oauth2provider=google&returnURL=${encodeURIComponent(
|
|
359
|
+
'https://web.webex.com'
|
|
360
|
+
)}&state=${expectedState}`
|
|
361
|
+
);
|
|
362
|
+
});
|
|
363
|
+
});
|
|
364
|
+
|
|
235
365
|
describe('#buildLogoutUrl()', () => {
|
|
236
366
|
skipInBrowser(it)('generates the logout url', () => {
|
|
237
367
|
const webex = new MockWebex();
|
|
@@ -368,7 +498,9 @@ describe('webex-core', () => {
|
|
|
368
498
|
});
|
|
369
499
|
|
|
370
500
|
it('should throw when provided an invalid token', () =>
|
|
371
|
-
expect(() => credentials.extractOrgIdFromUserToken()).toThrow(
|
|
501
|
+
expect(() => credentials.extractOrgIdFromUserToken()).toThrow(
|
|
502
|
+
'the provided token is not a valid format, token has 1 sections'
|
|
503
|
+
));
|
|
372
504
|
|
|
373
505
|
it('should throw when no token is provided', () =>
|
|
374
506
|
expect(() => credentials.extractOrgIdFromUserToken()).toThrow());
|
|
@@ -799,7 +931,6 @@ describe('webex-core', () => {
|
|
|
799
931
|
.then(() => assert.isRejected(webex.boundedStorage.get('Credentials', '@'), /NotFound/));
|
|
800
932
|
});
|
|
801
933
|
|
|
802
|
-
|
|
803
934
|
// it('does not induce any token refreshes');
|
|
804
935
|
|
|
805
936
|
it('prevents #getUserToken() from being invoked', () => {
|
|
@@ -14,6 +14,8 @@ import {
|
|
|
14
14
|
AuthInterceptor,
|
|
15
15
|
config,
|
|
16
16
|
Credentials,
|
|
17
|
+
Services,
|
|
18
|
+
ServicesV2,
|
|
17
19
|
WebexHttpError,
|
|
18
20
|
Token,
|
|
19
21
|
serviceConstants,
|
|
@@ -380,6 +382,60 @@ describe('webex-core', () => {
|
|
|
380
382
|
});
|
|
381
383
|
});
|
|
382
384
|
|
|
385
|
+
describe('#onRequest() against a real service catalog', () => {
|
|
386
|
+
// The cases above stub `isAllowedDomainUrl`, so they answer the
|
|
387
|
+
// allowed-domain question themselves and cannot show that the catalog
|
|
388
|
+
// matcher is reached by the code that attaches the token. These use a
|
|
389
|
+
// real catalog and real allowed-domain methods.
|
|
390
|
+
[
|
|
391
|
+
{name: 'Services', Constructor: Services},
|
|
392
|
+
{name: 'ServicesV2', Constructor: ServicesV2},
|
|
393
|
+
].forEach(({name, Constructor}) => {
|
|
394
|
+
describe(name, () => {
|
|
395
|
+
let getUserToken;
|
|
396
|
+
|
|
397
|
+
beforeEach(() => {
|
|
398
|
+
const services = new Constructor(undefined, {parent: webex});
|
|
399
|
+
|
|
400
|
+
services._getCatalog().setAllowedDomains(['webex.com']);
|
|
401
|
+
// the catalog holds no services, so a url that is not covered by
|
|
402
|
+
// an allowed domain has nothing else to authorize it
|
|
403
|
+
services.waitForService = sinon.stub().rejects(new Error('no such service'));
|
|
404
|
+
|
|
405
|
+
webex.internal.services = services;
|
|
406
|
+
getUserToken = sinon.spy(webex.credentials, 'getUserToken');
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
afterEach(() => {
|
|
410
|
+
getUserToken.restore();
|
|
411
|
+
delete webex.internal.services;
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
it('adds the authorization header for a url under an allowed domain', () =>
|
|
415
|
+
interceptor
|
|
416
|
+
.onRequest({uri: 'https://api.webex.com/resource', headers: {}})
|
|
417
|
+
.then((options) => {
|
|
418
|
+
assert.equal(
|
|
419
|
+
options.headers.authorization,
|
|
420
|
+
webex.credentials.supertoken.toString()
|
|
421
|
+
);
|
|
422
|
+
assert.calledOnce(getUserToken);
|
|
423
|
+
}));
|
|
424
|
+
|
|
425
|
+
[
|
|
426
|
+
'https://notwebex.com/resource',
|
|
427
|
+
'https://webex.com.unrelated.example/resource',
|
|
428
|
+
].forEach((uri) => {
|
|
429
|
+
it(`does not add the authorization header for ${uri}`, () =>
|
|
430
|
+
interceptor.onRequest({uri, headers: {}}).then((options) => {
|
|
431
|
+
assert.notProperty(options.headers, 'authorization');
|
|
432
|
+
assert.notCalled(getUserToken);
|
|
433
|
+
}));
|
|
434
|
+
});
|
|
435
|
+
});
|
|
436
|
+
});
|
|
437
|
+
});
|
|
438
|
+
|
|
383
439
|
describe('#onResponseError()', () => {
|
|
384
440
|
describe('when the server responds with 401', () => {
|
|
385
441
|
nodeOnly(it)('refreshes the access token and replays the request', () => {
|
|
@@ -7,6 +7,7 @@ import {assert} from '@webex/test-helper-chai';
|
|
|
7
7
|
import MockWebex from '@webex/test-helper-mock-webex';
|
|
8
8
|
import sinon from 'sinon';
|
|
9
9
|
import {Batcher} from '@webex/webex-core';
|
|
10
|
+
import WebexHttpError from '../../../../src/lib/webex-http-error';
|
|
10
11
|
|
|
11
12
|
function promiseTick(count) {
|
|
12
13
|
let promise = Promise.resolve();
|
|
@@ -154,6 +155,61 @@ describe('webex-core', () => {
|
|
|
154
155
|
return Promise.all([assert.isRejected(p1), assert.isRejected(p2)]);
|
|
155
156
|
});
|
|
156
157
|
});
|
|
158
|
+
|
|
159
|
+
it('does not trigger unhandledRejection when caller handles rejection', () => {
|
|
160
|
+
const unhandled = [];
|
|
161
|
+
const onUnhandledRejection = (reason) => {
|
|
162
|
+
unhandled.push(reason);
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
process.on('unhandledRejection', onUnhandledRejection);
|
|
166
|
+
|
|
167
|
+
const p = webex.internal.batcher.request(1);
|
|
168
|
+
|
|
169
|
+
// eslint-disable-next-line prefer-promise-reject-errors
|
|
170
|
+
webex.request.returns(Promise.reject({statusCode: 0}));
|
|
171
|
+
|
|
172
|
+
return promiseTick(50)
|
|
173
|
+
.then(() => clock.tick(2))
|
|
174
|
+
.then(() => promiseTick(50))
|
|
175
|
+
.then(() => assert.isRejected(p))
|
|
176
|
+
.then(() => promiseTick(50))
|
|
177
|
+
.then(() => {
|
|
178
|
+
assert.lengthOf(unhandled, 0);
|
|
179
|
+
})
|
|
180
|
+
.finally(() => {
|
|
181
|
+
process.removeListener('unhandledRejection', onUnhandledRejection);
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('fails queued deferreds for webex http errors without request body', () => {
|
|
186
|
+
const p1 = webex.internal.batcher.request(1);
|
|
187
|
+
const p2 = webex.internal.batcher.request(2);
|
|
188
|
+
const reason = new WebexHttpError.BadRequest({
|
|
189
|
+
statusCode: 400,
|
|
190
|
+
body: {message: 'simulated failure'},
|
|
191
|
+
options: {
|
|
192
|
+
method: 'GET',
|
|
193
|
+
uri: 'https://example.com/v1/mock/batch',
|
|
194
|
+
headers: {trackingid: 'test-tracking-id'},
|
|
195
|
+
},
|
|
196
|
+
headers: {},
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
webex.request.returns(Promise.reject(reason));
|
|
200
|
+
|
|
201
|
+
return promiseTick(50)
|
|
202
|
+
.then(() => clock.tick(2))
|
|
203
|
+
.then(() => promiseTick(50))
|
|
204
|
+
.then(() => {
|
|
205
|
+
assert.calledOnce(webex.request);
|
|
206
|
+
|
|
207
|
+
return Promise.all([
|
|
208
|
+
assert.isRejected(p1, /simulated failure/),
|
|
209
|
+
assert.isRejected(p2, /simulated failure/),
|
|
210
|
+
]);
|
|
211
|
+
});
|
|
212
|
+
});
|
|
157
213
|
});
|
|
158
214
|
|
|
159
215
|
describe('when the number of request attempts exceeds a given threshold', () => {
|