@webex/webex-core 3.9.0 → 3.10.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.
@@ -19,6 +19,7 @@ import {
19
19
  formattedServiceHostmapEntryConv,
20
20
  serviceHostmapV2,
21
21
  } from '../../../fixtures/host-catalog-v2';
22
+ import {cloneDeep} from 'lodash';
22
23
 
23
24
  describe('webex-core', () => {
24
25
  describe('ServiceCatalogV2', () => {
@@ -394,13 +395,13 @@ describe('webex-core', () => {
394
395
  );
395
396
 
396
397
  it('resolves to an authed u2c hostmap when no params specified', () => {
397
- assert.typeOf(fullRemoteHM, 'array');
398
- assert.isAbove(fullRemoteHM.length, 0);
398
+ assert.typeOf(fullRemoteHM.services, 'array');
399
+ assert.isAbove(fullRemoteHM.services.length, 0);
399
400
  });
400
401
 
401
402
  it('resolves to a limited u2c hostmap when params specified', () => {
402
- assert.typeOf(limitedRemoteHM, 'array');
403
- assert.isAbove(limitedRemoteHM.length, 0);
403
+ assert.typeOf(limitedRemoteHM.services, 'array');
404
+ assert.isAbove(limitedRemoteHM.services.length, 0);
404
405
  });
405
406
 
406
407
  it('rejects if the params provided are invalid', () =>
@@ -449,7 +450,7 @@ describe('webex-core', () => {
449
450
  catalog
450
451
  .waitForCatalog('postauth', 1)
451
452
  .then(() => assert(true, 'promise resolved'))
452
- .finally(() => catalog.updateServiceGroups('postauth', formattedHM));
453
+ .finally(() => catalog.updateServiceGroups('postauth', formattedHM.services));
453
454
  });
454
455
  });
455
456
 
@@ -463,7 +464,7 @@ describe('webex-core', () => {
463
464
  });
464
465
 
465
466
  it('removes any unused urls from current services', () => {
466
- catalog.updateServiceGroups('preauth', formattedHM);
467
+ catalog.updateServiceGroups('preauth', formattedHM.services);
467
468
 
468
469
  const originalLength = catalog.serviceGroups.preauth.length;
469
470
 
@@ -473,9 +474,9 @@ describe('webex-core', () => {
473
474
  });
474
475
 
475
476
  it('updates the target catalog to contain the provided hosts', () => {
476
- catalog.updateServiceGroups('preauth', formattedHM);
477
+ catalog.updateServiceGroups('preauth', formattedHM.services);
477
478
 
478
- assert.equal(catalog.serviceGroups.preauth.length, formattedHM.length);
479
+ assert.equal(catalog.serviceGroups.preauth.length, formattedHM.services.length);
479
480
  });
480
481
 
481
482
  it('updates any existing ServiceUrls', () => {
@@ -601,21 +602,21 @@ describe('webex-core', () => {
601
602
  format: 'U2Cv2',
602
603
  };
603
604
 
604
- catalog.updateServiceGroups('preauth', formattedHM);
605
+ catalog.updateServiceGroups('preauth', formattedHM.services);
605
606
 
606
607
  const oldServiceDetails = catalog._getAllServiceDetails('preauth');
607
608
 
608
609
  const newFormattedHM = services._formatReceivedHostmap(newServiceHM);
609
610
 
610
- catalog.updateServiceGroups('preauth', newFormattedHM);
611
+ catalog.updateServiceGroups('preauth', newFormattedHM.services);
611
612
 
612
613
  oldServiceDetails.forEach((serviceDetail) =>
613
- assert.isTrue(!!formattedHM.find((service) => service.id === serviceDetail.id))
614
+ assert.isTrue(!!formattedHM.services.find((service) => service.id === serviceDetail.id))
614
615
  );
615
616
 
616
617
  const newServiceDetails = catalog._getAllServiceDetails('preauth');
617
618
 
618
- formattedHM.forEach((oldServiceDetail) =>
619
+ formattedHM.services.forEach((oldServiceDetail) =>
619
620
  assert.notEqual(
620
621
  oldServiceDetail.serviceUrls[0].baseUrl,
621
622
  newServiceDetails.find((service) => service.id === oldServiceDetail.id).get()
@@ -624,7 +625,7 @@ describe('webex-core', () => {
624
625
  });
625
626
 
626
627
  it('creates an array of equal length of active services', () => {
627
- assert.equal(serviceHostmap.services.length, formattedHM.length);
628
+ assert.equal(serviceHostmap.services.length, formattedHM.services.length);
628
629
  });
629
630
 
630
631
  it('creates an array with matching host data', () => {
@@ -646,7 +647,7 @@ describe('webex-core', () => {
646
647
  done();
647
648
  });
648
649
 
649
- catalog.updateServiceGroups('preauth', formattedHM);
650
+ catalog.updateServiceGroups('preauth', formattedHM.services);
650
651
  });
651
652
 
652
653
  it('updates the services list', (done) => {
@@ -657,7 +658,42 @@ describe('webex-core', () => {
657
658
  done();
658
659
  });
659
660
 
660
- catalog.updateServiceGroups('preauth', formattedHM);
661
+ catalog.updateServiceGroups('preauth', formattedHM.services);
662
+ });
663
+ it('make sure the serviceUrls is in Priority order', (done) => {
664
+ const notInOrderServiceHM = {
665
+ activeServices: {
666
+ conversation: 'urn:TEAM:us-east-2_a:conversation',
667
+ idbroker: 'urn:TEAM:us-east-2_a:idbroker',
668
+ locus: 'urn:TEAM:us-east-2_a:locus',
669
+ mercury: 'urn:TEAM:us-east-2_a:mercury',
670
+ },
671
+ services: [
672
+ {
673
+ id: 'urn:TEAM:us-east-2_a:conversation',
674
+ serviceName: 'conversation',
675
+ serviceUrls: [
676
+ {
677
+ baseUrl: 'https://example-1.svc.webex.com/conversation/api/v1',
678
+ priority: 2,
679
+ },
680
+ {
681
+ baseUrl: 'https://conv-a.wbx2.com/conversation/api/v1',
682
+ priority: 1,
683
+ },
684
+ ],
685
+ },
686
+ ],
687
+ orgId: '3e0e410f-f83f-4ee4-ac32-12692e99355c',
688
+ timestamp: '1745533341',
689
+ format: 'U2Cv2',
690
+ };
691
+ const notInOrderFormattedHM = services._formatReceivedHostmap(notInOrderServiceHM);
692
+ const checkFormattedHM = cloneDeep(notInOrderFormattedHM);
693
+ catalog.updateServiceGroups('preauth', notInOrderFormattedHM.services);
694
+ assert.deepEqual(catalog._getServiceDetail('urn:TEAM:us-east-2_a:conversation')?.serviceUrls[0], checkFormattedHM.services[0].serviceUrls[1])
695
+ assert.equal( catalog.get('urn:TEAM:us-east-2_a:conversation'), 'https://conv-a.wbx2.com/conversation/api/v1')
696
+ done();
661
697
  });
662
698
  });
663
699
  });
@@ -23,6 +23,7 @@ import {
23
23
  formattedServiceHostmapEntryConv,
24
24
  formattedServiceHostmapEntryMercury,
25
25
  formattedServiceHostmapEntryTest,
26
+ serviceHostmapV2
26
27
  } from '../../../fixtures/host-catalog-v2';
27
28
 
28
29
  // /* eslint-disable no-underscore-dangle */
@@ -389,6 +390,32 @@ describe('webex-core', () => {
389
390
  .then(() => assert.calledTwice(services.updateServices))
390
391
  );
391
392
  });
393
+
394
+ it('should call services#collectPreauthCatalog with the OrgId and forceRefresh is true', () => {
395
+ services.collectPreauthCatalog = sinon.stub().resolves();
396
+
397
+ return services.initServiceCatalogs(true).then(() =>
398
+ assert.calledWith(
399
+ services.collectPreauthCatalog,
400
+ sinon.match({
401
+ orgId: webex.credentials.getOrgId(),
402
+ }),
403
+ sinon.match(true)
404
+ )
405
+ );
406
+ });
407
+
408
+ it('should call services#updateServices() with forceRefresh is true', () => {
409
+ services.updateServices = sinon.stub().resolves();
410
+
411
+ return (
412
+ services
413
+ .initServiceCatalogs(true)
414
+ // services#updateServices() gets called once by the limited catalog
415
+ // retrieval and should get called again when authorized.
416
+ .then(() => assert.calledTwice(services.updateServices) && assert.calledWith(services.updateServices, sinon.match({forceRefresh: true})))
417
+ );
418
+ });
392
419
  });
393
420
 
394
421
  describe('#isAllowedDomainUrl()', () => {
@@ -513,6 +540,54 @@ describe('webex-core', () => {
513
540
  });
514
541
  });
515
542
 
543
+ describe('#switchActiveClusterIds', () => {
544
+ let requestStub;
545
+
546
+ beforeEach(() => {
547
+ services._formatReceivedHostmap(serviceHostmapV2);
548
+ });
549
+
550
+ afterEach(() => {
551
+ requestStub.restore();
552
+ });
553
+
554
+ it('fetches new catalog when id does not exist', () => {
555
+ requestStub = sinon
556
+ .stub(webex.internal.newMetrics.callDiagnosticLatencies, 'measureLatency')
557
+ .returns(
558
+ Promise.resolve({
559
+ body: {
560
+ activeServices: {
561
+ ...serviceHostmapV2.activeServices,
562
+ conversation: 'urn:TEAM:me-central-1_asdf:conversation',
563
+ },
564
+ services: [
565
+ ...serviceHostmapV2.services,
566
+ {
567
+ id: 'urn:TEAM:me-central-1_asdf:conversation',
568
+ serviceName: 'conversation',
569
+ serviceUrls: [{baseUrl: 'baseurl.com', priority: 1}],
570
+ },
571
+ ],
572
+ },
573
+ })
574
+ );
575
+
576
+ services
577
+ .switchActiveClusterIds({
578
+ conversation: 'urn:TEAM:me-central-1_asdf:conversation',
579
+ })
580
+ .then(() => {
581
+ assert.equal(
582
+ !!services._services.find(
583
+ (service) => service.id === 'urn:TEAM:me-central-1_asdf:conversation'
584
+ ),
585
+ true
586
+ );
587
+ });
588
+ });
589
+ });
590
+
516
591
  describe('#updateServices()', () => {
517
592
  it('returns a Promise that and resolves on success', (done) => {
518
593
  const servicesPromise = services.updateServices();
@@ -1034,13 +1109,13 @@ describe('webex-core', () => {
1034
1109
  );
1035
1110
 
1036
1111
  it('resolves to an authed u2c hostmap when no params specified', () => {
1037
- assert.typeOf(fullRemoteHM, 'array');
1038
- assert.isAbove(fullRemoteHM.length, 0);
1112
+ assert.typeOf(fullRemoteHM.services, 'array');
1113
+ assert.isAbove(fullRemoteHM.services.length, 0);
1039
1114
  });
1040
1115
 
1041
1116
  it('resolves to a limited u2c hostmap when params specified', () => {
1042
- assert.typeOf(limitedRemoteHM, 'array');
1043
- assert.isAbove(limitedRemoteHM.length, 0);
1117
+ assert.typeOf(limitedRemoteHM.services, 'array');
1118
+ assert.isAbove(limitedRemoteHM.services.length, 0);
1044
1119
  });
1045
1120
 
1046
1121
  it('rejects if the params provided are invalid', () =>
@@ -0,0 +1,73 @@
1
+ /*!
2
+ * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.
3
+ */
4
+
5
+ import {assert} from '@webex/test-helper-chai';
6
+ import {skipInBrowser, skipInNode} from '@webex/test-helper-mocha';
7
+ import {ProxyInterceptor} from '@webex/webex-core';
8
+
9
+ import pkg from '../../../../package';
10
+
11
+ describe('webex-core', () => {
12
+ describe('Interceptors', () => {
13
+ describe('ProxyInterceptor', () => {
14
+ describe('#onRequest', () => {
15
+ it('defaults to no proxy', () => {
16
+ const interceptor = Reflect.apply(
17
+ ProxyInterceptor.create,
18
+ {
19
+ version: pkg.version,
20
+ },
21
+ []
22
+ );
23
+ const options = {};
24
+
25
+ interceptor.onRequest(options);
26
+
27
+ assert.isUndefined(options.proxy);
28
+ });
29
+
30
+ skipInBrowser(describe)('#onRequestNode', () => {
31
+ it('allows custom proxy in node', () => {
32
+ const interceptor = Reflect.apply(
33
+ ProxyInterceptor.create,
34
+ {
35
+ version: pkg.version,
36
+ config: {
37
+ proxy: 'http://proxy.company.com'
38
+ },
39
+ },
40
+ []
41
+ );
42
+ const options = {};
43
+
44
+ interceptor.onRequest(options);
45
+
46
+ assert.property(options, 'proxy');
47
+ assert.equal(options.proxy, 'http://proxy.company.com');
48
+ });
49
+ });
50
+
51
+ skipInNode(describe)('#onRequestBrowser', () => {
52
+ it('removes custom proxy in browser', () => {
53
+ const interceptor = Reflect.apply(
54
+ ProxyInterceptor.create,
55
+ {
56
+ version: pkg.version,
57
+ config: {
58
+ proxy: 'http://proxy.company.com'
59
+ },
60
+ },
61
+ []
62
+ );
63
+ const options = {};
64
+
65
+ interceptor.onRequest(options);
66
+
67
+ assert.isUndefined(options.proxy);
68
+ });
69
+ });
70
+ });
71
+ });
72
+ });
73
+ });
@@ -268,12 +268,58 @@ describe('webex-core', () => {
268
268
  });
269
269
  });
270
270
 
271
+ describe('#switchActiveClusterIds', () => {
272
+ let serviceHostmap;
273
+ let formattedHM;
274
+
275
+ beforeEach(() => {
276
+ serviceHostmap = serviceHostmapV2;
277
+ formattedHM = services._formatReceivedHostmap(serviceHostmap);
278
+
279
+ services.initServiceCatalogs = sinon.stub().returns(Promise.resolve());
280
+ services.webex.credentials = {
281
+ getOrgId: sinon.stub().returns('')
282
+ };
283
+ catalog.status = {};
284
+ });
285
+
286
+ it('switches properly when id exists', async () => {
287
+ services._updateActiveServices = sinon.stub().callsFake((data) => {
288
+ Object.assign(services._activeServices, data);
289
+ });
290
+
291
+ await services.switchActiveClusterIds({
292
+ conversation: 'urn:TEAM:me-central-1_d:conversation',
293
+ });
294
+
295
+ assert.notCalled(services.initServiceCatalogs);
296
+
297
+ assert.calledWith(services._updateActiveServices, {
298
+ conversation: 'urn:TEAM:me-central-1_d:conversation',
299
+ });
300
+
301
+ assert.equal(services._activeServices.conversation, 'urn:TEAM:me-central-1_d:conversation');
302
+ });
303
+
304
+ it('makes request to fetch when id does not exist', async () => {
305
+ services._updateActiveServices = sinon.stub().callsFake((data) => {
306
+ Object.assign(services._activeServices, data);
307
+ });
308
+
309
+ await services.switchActiveClusterIds({
310
+ conversation: 'urn:TEAM:me-central-1_asdf:conversation',
311
+ });
312
+
313
+ assert.calledOnce(services.initServiceCatalogs);
314
+ });
315
+ });
316
+
271
317
  describe('#updateCatalog', () => {
272
318
  it('updates the catalog', async () => {
273
319
  const serviceGroup = 'postauth';
274
- const hostmap = [{hostmap: 'hostmap'}];
320
+ const hostmap = {services: [{hostmap: 'hostmap'}]};
275
321
 
276
- services._formatReceivedHostmap = sinon.stub().returns([{some: 'hostmap'}]);
322
+ services._formatReceivedHostmap = sinon.stub().returns({services : [{some: 'hostmap'}]});
277
323
 
278
324
  catalog.updateServiceGroups = sinon.stub().returns(Promise.resolve([{some: 'value'}]));
279
325
 
@@ -401,7 +447,7 @@ describe('webex-core', () => {
401
447
  formattedHM = services._formatReceivedHostmap(serviceHostmap);
402
448
 
403
449
  assert(
404
- serviceHostmap.services.length >= formattedHM.length,
450
+ serviceHostmap.services.length >= formattedHM.services.length,
405
451
  'length is not equal or less than'
406
452
  );
407
453
  });
@@ -409,7 +455,7 @@ describe('webex-core', () => {
409
455
  it('has all keys in host map hosts', () => {
410
456
  formattedHM = services._formatReceivedHostmap(serviceHostmap);
411
457
 
412
- formattedHM.forEach((service) => {
458
+ formattedHM.services.forEach((service) => {
413
459
  assert.hasAllKeys(
414
460
  service,
415
461
  ['id', 'serviceName', 'serviceUrls'],
@@ -428,7 +474,7 @@ describe('webex-core', () => {
428
474
  it('creates a formmated host map containing all received host map service entries', () => {
429
475
  formattedHM = services._formatReceivedHostmap(serviceHostmap);
430
476
 
431
- formattedHM.forEach((service) => {
477
+ formattedHM.services.forEach((service) => {
432
478
  const foundServiceKey = Object.keys(serviceHostmap.activeServices).find(
433
479
  (key) => service.serviceName === key
434
480
  );
@@ -440,7 +486,7 @@ describe('webex-core', () => {
440
486
  it('creates the expected formatted host map', () => {
441
487
  formattedHM = services._formatReceivedHostmap(serviceHostmap);
442
488
 
443
- assert.deepEqual(formattedHM, formattedServiceHostmapV2);
489
+ assert.deepEqual(formattedHM.services, formattedServiceHostmapV2);
444
490
  });
445
491
 
446
492
  it('has hostCatalog updated', () => {
@@ -512,5 +558,142 @@ describe('webex-core', () => {
512
558
  assert.equal(webex.config.credentials.authorizeUrl, authUrl);
513
559
  });
514
560
  });
561
+
562
+ describe('#invalidateCache', () => {
563
+ beforeEach( () => {
564
+ services.initServiceCatalogs = sinon.stub().returns(Promise.resolve());
565
+ services.webex.credentials = {
566
+ getOrgId: sinon.stub().returns('')
567
+ };
568
+ catalog.status = {};
569
+ })
570
+ it('should log the timestamp parameter', async () => {
571
+ const timestamp = '1234567890';
572
+ services.logger.info = sinon.stub();
573
+ services._getCatalog = sinon.stub().returns({timestamp: '1234567880'});
574
+
575
+ await services.invalidateCache(timestamp);
576
+
577
+ assert.calledWith(services.logger.info, 'services: invalidate cache, timestamp:', timestamp);
578
+ });
579
+
580
+ it('should call initServiceCatalogs when invalidate timestamp is newer than catalog timestamp', async () => {
581
+ const newTimestamp = '1234567890';
582
+ const oldTimestamp = '1234567880';
583
+ services.logger.info = sinon.stub();
584
+ services._getCatalog = sinon.stub().returns({timestamp: oldTimestamp});
585
+
586
+ await services.invalidateCache(newTimestamp);
587
+
588
+ assert.calledOnce(services.initServiceCatalogs);
589
+ assert.calledWith(services.logger.info, 'services: invalidateCache, refresh services');
590
+ });
591
+
592
+ it('should not call initServiceCatalogs when invalidate timestamp is older than catalog timestamp', async () => {
593
+ const oldTimestamp = '1234567880';
594
+ const newTimestamp = '1234567890';
595
+ services._getCatalog = sinon.stub().returns({timestamp: newTimestamp});
596
+ await services.invalidateCache(oldTimestamp);
597
+
598
+ assert.notCalled(services.initServiceCatalogs);
599
+ });
600
+
601
+ it('should not call initServiceCatalogs when invalidate timestamp equals catalog timestamp', async () => {
602
+ const timestamp = '1234567890';
603
+ services._getCatalog = sinon.stub().returns({timestamp: timestamp});
604
+
605
+ await services.invalidateCache(timestamp);
606
+
607
+ assert.notCalled(services.initServiceCatalogs);
608
+ });
609
+
610
+ it('should handle numeric timestamp strings correctly', async () => {
611
+ const newTimestamp = '1700000000';
612
+ const oldTimestamp = '1600000000';
613
+ services._getCatalog = sinon.stub().returns({timestamp: oldTimestamp});
614
+
615
+ await services.invalidateCache(newTimestamp);
616
+
617
+ assert.calledOnce(services.initServiceCatalogs);
618
+ });
619
+
620
+ it('should handle undefined catalog gracefully', async () => {
621
+ const timestamp = '1234567890';
622
+ services._getCatalog = sinon.stub().returns(undefined);
623
+
624
+ await services.invalidateCache(timestamp);
625
+
626
+ assert.calledOnce(services.initServiceCatalogs);
627
+ });
628
+
629
+ it('should handle catalog without timestamp gracefully', async () => {
630
+ const timestamp = '1234567890';
631
+ services._getCatalog = sinon.stub().returns({});
632
+
633
+ await services.invalidateCache(timestamp);
634
+
635
+ assert.calledOnce(services.initServiceCatalogs);
636
+ });
637
+
638
+ it('should handle null catalog timestamp gracefully', async () => {
639
+ const timestamp = '1234567890';
640
+ services._getCatalog = sinon.stub().returns({timestamp: null});
641
+
642
+ await services.invalidateCache(timestamp);
643
+
644
+ assert.calledOnce(services.initServiceCatalogs);
645
+ });
646
+
647
+ it('should handle undefined timestamp parameter gracefully', async () => {
648
+ services._getCatalog = sinon.stub().returns({timestamp: '1234567890'});
649
+
650
+ await services.invalidateCache(undefined);
651
+
652
+ assert.notCalled(services.initServiceCatalogs);
653
+ });
654
+
655
+ it('should handle null timestamp parameter gracefully', async () => {
656
+ services._getCatalog = sinon.stub().returns({timestamp: '1234567890'});
657
+
658
+ await services.invalidateCache(null);
659
+
660
+ assert.notCalled(services.initServiceCatalogs);
661
+ });
662
+
663
+ it('should handle empty string timestamp parameter gracefully', async () => {
664
+ services._getCatalog = sinon.stub().returns({timestamp: '1234567890'});
665
+
666
+ await services.invalidateCache('');
667
+
668
+ assert.notCalled(services.initServiceCatalogs);
669
+ });
670
+
671
+ it('should handle non-numeric timestamp strings gracefully', async () => {
672
+ const invalidTimestamp = 'not-a-number';
673
+ services._getCatalog = sinon.stub().returns({timestamp: '1234567890'});
674
+
675
+ await services.invalidateCache(invalidTimestamp);
676
+
677
+ assert.notCalled(services.initServiceCatalogs);
678
+ });
679
+
680
+ it('should handle non-numeric catalog timestamp gracefully', async () => {
681
+ const timestamp = '1234567890';
682
+ services._getCatalog = sinon.stub().returns({timestamp: 'not-a-number'});
683
+
684
+ await services.invalidateCache(timestamp);
685
+
686
+ assert.calledOnce(services.initServiceCatalogs);
687
+ });
688
+
689
+ it('should return a resolved Promise', async () => {
690
+ const timestamp = '1234567890';
691
+ services._getCatalog = sinon.stub().returns({timestamp: '1234567880'});
692
+
693
+ const result = await services.invalidateCache(timestamp);
694
+
695
+ assert.isUndefined(result);
696
+ });
697
+ });
515
698
  });
516
699
  });
@@ -2,6 +2,7 @@
2
2
  * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.
3
3
  */
4
4
 
5
+ import {EventEmitter} from 'events';
5
6
  import {assert} from '@webex/test-helper-chai';
6
7
  import sinon from 'sinon';
7
8
  import WebexCore, {
@@ -13,6 +14,7 @@ import WebexCore, {
13
14
  } from '@webex/webex-core';
14
15
  import {set} from 'lodash';
15
16
  import {version} from '@webex/webex-core/package';
17
+ import {proxyEvents} from '@webex/common/src/events';
16
18
  // TODO: fix circular dependency core->metrics->core https://jira-eng-gpk2.cisco.com/jira/browse/SPARK-515520
17
19
  require('@webex/internal-plugin-metrics');
18
20
 
@@ -177,11 +179,11 @@ describe('Webex', () => {
177
179
 
178
180
  describe('initializes with interceptors', () => {
179
181
  [
180
- // 4 pre, 4 post, 9 remaining default = 17
182
+ // 4 pre, 4 post, 10 remaining default = 18
181
183
  [
182
184
  'defaults to existing interceptors if undefined',
183
185
  undefined,
184
- 17,
186
+ 18,
185
187
  [
186
188
  'RequestTimingInterceptor',
187
189
  'RequestEventInterceptor',
@@ -189,6 +191,7 @@ describe('Webex', () => {
189
191
  'RateLimitInterceptor',
190
192
  'ServiceInterceptor',
191
193
  'UserAgentInterceptor',
194
+ 'ProxyInterceptor',
192
195
  'WebexUserAgentInterceptor',
193
196
  'AuthInterceptor',
194
197
  'PayloadTransformerInterceptor',
@@ -263,6 +266,63 @@ describe('Webex', () => {
263
266
  });
264
267
  });
265
268
 
269
+ describe('#upload()', () => {
270
+ it('rejects when no object is specified', () => {
271
+ return webex.upload().catch((err) => {
272
+ assert.equal(err.message, '`options.file` is required');
273
+ });
274
+ });
275
+
276
+ it('rejects when no file is specified', () => {
277
+ return webex.upload({}).catch((err) => {
278
+ assert.equal(err.message, '`options.file` is required');
279
+ });
280
+ });
281
+
282
+ it('passes through progress events to caller', () => {
283
+ const uploadEventEmitter = new EventEmitter();
284
+
285
+ webex._uploadPhaseInitialize = sinon.stub().callsFake(() => {
286
+ return Promise.resolve();
287
+ });
288
+ webex._uploadPhaseUpload = sinon.stub().callsFake(() => {
289
+ const promise = Promise.resolve();
290
+
291
+ proxyEvents(uploadEventEmitter, promise);
292
+
293
+ return promise;
294
+ });
295
+ webex._uploadPhaseFinalize = sinon.stub().callsFake(() => {
296
+ return Promise.resolve({
297
+ body: {
298
+ url: 'https://example.com/download',
299
+ },
300
+ headers: {
301
+ 'Content-Type': 'image/png',
302
+ },
303
+ });
304
+ });
305
+
306
+ const uploader = webex.upload({file: {}});
307
+
308
+ assert.isFunction(uploader.on);
309
+
310
+ return uploader.then((res) => {
311
+ const progressStub = sinon.stub();
312
+ uploader.on('progress', progressStub);
313
+
314
+ uploadEventEmitter.emit('progress', {percent: 25});
315
+
316
+ assert.isTrue(progressStub.calledOnce);
317
+ assert.isTrue(webex._uploadPhaseInitialize.calledOnce);
318
+ assert.isTrue(webex._uploadPhaseUpload.calledOnce);
319
+ assert.isTrue(webex._uploadPhaseFinalize.calledOnce);
320
+ assert.equal(res.url, 'https://example.com/download');
321
+ assert.equal(res['Content-Type'], 'image/png');
322
+ });
323
+ });
324
+ });
325
+
266
326
  it('emits the `loaded` event when the storage layer has loaded all data', () => {
267
327
  // I'd love to do this with mock webex, or at least, a mock plugin, but I
268
328
  // couldn't get it to work. We do get better coverage this way, but it means