@webex/internal-plugin-device 3.12.0-next.8 → 3.12.0-task-refactor.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.
@@ -20,19 +20,10 @@ describe('plugin-device', () => {
20
20
  let device;
21
21
 
22
22
  beforeEach(() => {
23
- const fakeStorage = {};
24
23
  webex = new MockWebex({
25
24
  children: {
26
25
  device: Device,
27
26
  },
28
- getWindow: () => ({
29
- sessionStorage: {
30
- setItem: (key, value) => {
31
- fakeStorage[key] = value;
32
- },
33
- getItem: (key) => fakeStorage[key],
34
- },
35
- }),
36
27
  });
37
28
 
38
29
  const clonedDTO = cloneDeep(dto);
@@ -112,54 +103,6 @@ describe('plugin-device', () => {
112
103
  });
113
104
  });
114
105
  });
115
-
116
- describe('when the config is changed', () => {
117
- it("should unset the 'etag' if debug features are set", () => {
118
- device.set('etag', 'etag-value');
119
- device.config.debugFeatureTogglesKey = 'debug-feature-toggles';
120
-
121
- webex.getWindow().sessionStorage.setItem(
122
- 'debug-feature-toggles',
123
- JSON.stringify({
124
- test_feature: true,
125
- })
126
- );
127
- assert.equal(device.etag, 'etag-value');
128
-
129
- webex.trigger('change:config');
130
- assert.isUndefined(device.etag);
131
- });
132
-
133
- it("should not unset the 'etag' if debug features are not set", () => {
134
- device.set('etag', 'etag-value');
135
- device.config.debugFeatureTogglesKey = 'debug-feature-toggles';
136
-
137
- assert.equal(device.etag, 'etag-value');
138
-
139
- webex.trigger('change:config');
140
- assert.equal(device.etag, 'etag-value');
141
- });
142
-
143
- it("should only unset the 'etag' the first time the event is sent", () => {
144
- device.set('etag', 'etag-value');
145
- device.config.debugFeatureTogglesKey = 'debug-feature-toggles';
146
-
147
- webex.getWindow().sessionStorage.setItem(
148
- 'debug-feature-toggles',
149
- JSON.stringify({
150
- test_feature: true,
151
- })
152
- );
153
- assert.equal(device.etag, 'etag-value');
154
-
155
- webex.trigger('change:config');
156
- assert.isUndefined(device.etag);
157
-
158
- device.set('etag', 'etag-value');
159
- webex.trigger('change:config');
160
- assert.equal(device.etag, 'etag-value');
161
- });
162
- });
163
106
  });
164
107
 
165
108
  describe('derived properties', () => {
@@ -481,34 +424,18 @@ describe('plugin-device', () => {
481
424
 
482
425
  assert.calledOnce(device.processRegistrationSuccess);
483
426
  });
427
+
484
428
  });
485
429
 
486
430
  describe('deleteDevices()', () => {
487
- let requestStub;
488
- let clock;
489
- let waitForLimitStub;
490
-
491
431
  const setup = (deviceType) => {
492
432
  device.config.defaults = {body: {deviceType}};
493
433
  };
494
-
495
- beforeEach(() => {
496
- waitForLimitStub = sinon.stub(device, '_waitForDeviceCountBelowLimit').resolves();
497
- });
498
-
499
- afterEach(() => {
500
- sinon.restore();
501
- if (clock) {
502
- clock.restore();
503
- clock = null;
504
- }
505
- });
506
-
507
- ['WEB', 'WEBCLIENT'].forEach((deviceType) => {
508
- it(`should delete correct number of devices for ${deviceType}`, async () => {
509
- setup(deviceType);
510
- const response = {
511
- body: {
434
+ ['WEB', 'WEBCLIENT'].forEach(deviceType => {
435
+ it(`should delete correct number of devices for ${deviceType}`, async () => {
436
+ setup(deviceType);
437
+ const response = {
438
+ body: {
512
439
  devices: [
513
440
  {url: 'url3', modificationTime: '2023-10-03T10:00:00Z', deviceType},
514
441
  {url: 'url4', modificationTime: '2023-10-04T10:00:00Z', deviceType: 'notweb'},
@@ -518,428 +445,49 @@ describe('plugin-device', () => {
518
445
  {url: 'url6', modificationTime: '2023-09-50T10:00:00Z', deviceType},
519
446
  {url: 'url7', modificationTime: '2023-09-30T10:00:00Z', deviceType},
520
447
  {url: 'url8', modificationTime: '2023-08-30T10:00:00Z', deviceType},
521
- ],
522
- },
523
- };
524
-
525
- requestStub = sinon.stub(device, 'request');
526
- requestStub.withArgs(sinon.match({method: 'GET'})).resolves(response);
527
- requestStub.withArgs(sinon.match({method: 'DELETE'})).resolves();
528
-
529
- await device.deleteDevices();
530
-
531
- const expectedDeletions = ['url8', 'url7', 'url1'];
532
-
533
- expectedDeletions.forEach((url) => {
534
- assert(requestStub.calledWith(sinon.match({uri: url, method: 'DELETE'})));
535
- });
536
-
537
- const notDeletedUrls = ['url2', 'url3', 'url5', 'url6', 'url4'];
538
- notDeletedUrls.forEach((url) => {
539
- assert(requestStub.neverCalledWith(sinon.match({uri: url, method: 'DELETE'})));
540
- });
541
- });
542
- });
543
-
544
- it('does not delete when there are only 2 devices (below MIN_DEVICES_FOR_CLEANUP)', async () => {
545
- setup('WEB');
546
- const response = {
547
- body: {
548
- devices: [
549
- {url: 'url1', modificationTime: '2023-10-01T10:00:00Z', deviceType: 'WEB'},
550
- {url: 'url2', modificationTime: '2023-10-02T10:00:00Z', deviceType: 'WEB'},
551
- ],
552
- },
448
+ ]
449
+ }
553
450
  };
451
+ const requestStub = sinon.stub(device, 'request');
452
+ requestStub.withArgs(sinon.match({method: 'GET'})).resolves(response);
453
+ requestStub.withArgs(sinon.match({method: 'DELETE'})).resolves();
554
454
 
555
- requestStub = sinon.stub(device, 'request');
556
- requestStub.withArgs(sinon.match({method: 'GET'})).resolves(response);
557
- requestStub.withArgs(sinon.match({method: 'DELETE'})).resolves();
558
-
559
- await device.deleteDevices();
560
- // MIN_DEVICES_FOR_CLEANUP = 5; 2 devices is below the threshold, so nothing should be deleted
561
- assert(requestStub.neverCalledWith(sinon.match({method: 'DELETE'})));
562
- });
563
-
564
- it('does not delete when device count equals MIN_DEVICES_FOR_CLEANUP (5 devices)', async () => {
565
- setup('WEB');
566
- const devices = Array.from({length: 5}, (_, i) => ({
567
- url: `url${i}`,
568
- modificationTime: `2023-10-0${i + 1}T10:00:00Z`,
569
- deviceType: 'WEB',
570
- }));
571
-
572
- requestStub = sinon.stub(device, 'request');
573
- requestStub.withArgs(sinon.match({method: 'GET'})).resolves({body: {devices}});
574
- requestStub.withArgs(sinon.match({method: 'DELETE'})).resolves();
575
-
576
- await device.deleteDevices();
577
- // MIN_DEVICES_FOR_CLEANUP = 5; exactly at the threshold means no deletion
578
- assert(requestStub.neverCalledWith(sinon.match({method: 'DELETE'})));
579
- });
455
+ await device.deleteDevices();
580
456
 
581
- it('waits for all deletions to complete before proceeding', async () => {
582
- setup('WEB');
583
- const devices = Array.from({length: 6}, (_, i) => ({
584
- url: `url${i}`,
585
- modificationTime: `2023-10-0${i}T10:00:00Z`,
586
- deviceType: 'WEB',
587
- }));
588
-
589
- requestStub = sinon.stub(device, 'request');
590
- requestStub.withArgs(sinon.match({method: 'GET'})).resolves({body: {devices}});
591
-
592
- const deleteOrder = [];
593
- requestStub.withArgs(sinon.match({method: 'DELETE'})).callsFake((opts) => {
594
- deleteOrder.push(opts.uri);
595
- return Promise.resolve();
596
- });
597
-
598
- await device.deleteDevices();
457
+ const expectedDeletions = ['url8', 'url7', 'url1'];
599
458
 
600
- // ceil(6/3) = 2 devices should be deleted
601
- assert.equal(deleteOrder.length, 2);
459
+ expectedDeletions.forEach(url => {
460
+ assert(requestStub.calledWith(sinon.match({uri: url, method: 'DELETE'})));
602
461
  });
603
462
 
604
- it('does not delete when there are zero devices', async () => {
605
- setup('WEB');
606
- requestStub = sinon.stub(device, 'request');
607
- requestStub.withArgs(sinon.match({method: 'GET'})).resolves({body: {devices: []}});
608
- requestStub.withArgs(sinon.match({method: 'DELETE'})).resolves();
609
-
610
- await device.deleteDevices();
611
-
612
- assert(requestStub.neverCalledWith(sinon.match({method: 'DELETE'})));
613
- });
614
-
615
- it('only deletes devices matching the current device type', async () => {
616
- setup('WEB');
617
- const devices = [
618
- {url: 'web1', modificationTime: '2023-10-01T10:00:00Z', deviceType: 'WEB'},
619
- {url: 'web2', modificationTime: '2023-10-02T10:00:00Z', deviceType: 'WEB'},
620
- {url: 'web3', modificationTime: '2023-10-03T10:00:00Z', deviceType: 'WEB'},
621
- {url: 'web4', modificationTime: '2023-10-04T10:00:00Z', deviceType: 'WEB'},
622
- {url: 'web5', modificationTime: '2023-10-05T10:00:00Z', deviceType: 'WEB'},
623
- {url: 'web6', modificationTime: '2023-10-06T10:00:00Z', deviceType: 'WEB'},
624
- {url: 'desktop1', modificationTime: '2023-10-01T10:00:00Z', deviceType: 'DESKTOP'},
625
- {url: 'mobile1', modificationTime: '2023-10-01T10:00:00Z', deviceType: 'MOBILE'},
626
- ];
627
-
628
- requestStub = sinon.stub(device, 'request');
629
- requestStub.withArgs(sinon.match({method: 'GET'})).resolves({body: {devices}});
630
- requestStub.withArgs(sinon.match({method: 'DELETE'})).resolves();
631
-
632
- await device.deleteDevices();
633
-
634
- // Only WEB devices considered: 6 total (> MIN_DEVICES_FOR_CLEANUP=5), ceil(6/3)=2 deleted (oldest: web1, web2)
635
- assert(requestStub.calledWith(sinon.match({uri: 'web1', method: 'DELETE'})));
636
- assert(requestStub.calledWith(sinon.match({uri: 'web2', method: 'DELETE'})));
637
- assert(requestStub.neverCalledWith(sinon.match({uri: 'desktop1', method: 'DELETE'})));
638
- assert(requestStub.neverCalledWith(sinon.match({uri: 'mobile1', method: 'DELETE'})));
639
- });
640
-
641
- it('rejects when fetching devices fails', async () => {
642
- setup('WEB');
643
- requestStub = sinon.stub(device, 'request');
644
- requestStub.withArgs(sinon.match({method: 'GET'})).rejects(new Error('network error'));
645
-
646
- await assert.isRejected(device.deleteDevices(), 'network error');
647
- });
648
-
649
- it('resolves when all deletion requests fail (best-effort)', async () => {
650
- setup('WEB');
651
- // Use 6 devices (> MIN_DEVICES_FOR_CLEANUP=5) to ensure deletion is attempted
652
- const devices = [
653
- {url: 'url1', modificationTime: '2023-10-01T10:00:00Z', deviceType: 'WEB'},
654
- {url: 'url2', modificationTime: '2023-10-02T10:00:00Z', deviceType: 'WEB'},
655
- {url: 'url3', modificationTime: '2023-10-03T10:00:00Z', deviceType: 'WEB'},
656
- {url: 'url4', modificationTime: '2023-10-04T10:00:00Z', deviceType: 'WEB'},
657
- {url: 'url5', modificationTime: '2023-10-05T10:00:00Z', deviceType: 'WEB'},
658
- {url: 'url6', modificationTime: '2023-10-06T10:00:00Z', deviceType: 'WEB'},
659
- ];
660
-
661
- requestStub = sinon.stub(device, 'request');
662
- requestStub.withArgs(sinon.match({method: 'GET'})).resolves({body: {devices}});
663
- requestStub.withArgs(sinon.match({method: 'DELETE'})).rejects(new Error('delete failed'));
664
-
665
- // Should resolve despite DELETE failures — best-effort cleanup must not block registration retry
666
- await device.deleteDevices();
667
- assert.calledWith(device.logger.warn, sinon.match(/deletions failed/));
668
- });
669
-
670
- it('resolves when only some deletion requests fail (partial failure)', async () => {
671
- setup('WEB');
672
- const devices = [
673
- {url: 'url1', modificationTime: '2023-10-01T10:00:00Z', deviceType: 'WEB'},
674
- {url: 'url2', modificationTime: '2023-10-02T10:00:00Z', deviceType: 'WEB'},
675
- {url: 'url3', modificationTime: '2023-10-03T10:00:00Z', deviceType: 'WEB'},
676
- {url: 'url4', modificationTime: '2023-10-04T10:00:00Z', deviceType: 'WEB'},
677
- {url: 'url5', modificationTime: '2023-10-05T10:00:00Z', deviceType: 'WEB'},
678
- {url: 'url6', modificationTime: '2023-10-06T10:00:00Z', deviceType: 'WEB'},
679
- ];
680
-
681
- requestStub = sinon.stub(device, 'request');
682
- requestStub.withArgs(sinon.match({method: 'GET'})).resolves({body: {devices}});
683
- // ceil(6/3) = 2 deletions; first succeeds, second fails
684
- requestStub
685
- .withArgs(sinon.match({method: 'DELETE'}))
686
- .onFirstCall()
687
- .resolves()
688
- .onSecondCall()
689
- .rejects(new Error('404 not found'));
690
-
691
- await device.deleteDevices();
692
- assert.calledWith(device.logger.warn, sinon.match(/deletions failed/));
693
- });
694
-
695
- it('calls _waitForDeviceCountBelowLimit with targetCount equal to preCount minus min(5, deletedCount)', async () => {
696
- setup('WEB');
697
- const devices = Array.from({length: 20}, (_, i) => ({
698
- url: `url${i}`,
699
- modificationTime: `2023-10-${String(i + 1).padStart(2, '0')}T10:00:00Z`,
700
- deviceType: 'WEB',
701
- }));
702
-
703
- requestStub = sinon.stub(device, 'request');
704
- requestStub.withArgs(sinon.match({method: 'GET'})).resolves({body: {devices}});
705
- requestStub.withArgs(sinon.match({method: 'DELETE'})).resolves();
706
-
707
- await device.deleteDevices();
708
-
709
- // 20 WEB devices, ceil(20/3) = 7 deletions (>= 5), targetCount = 20 - min(5, 7) = 15
710
- assert.calledWith(waitForLimitStub, 15, 0);
711
- });
712
-
713
- it('small-n: 6-device case — targetCount is reachable (ceil(6/3)=2 < 5, so wait for 6-2=4)', async () => {
714
- setup('WEB');
715
- const devices = Array.from({length: 6}, (_, i) => ({
716
- url: `url${i}`,
717
- modificationTime: `2023-10-0${i + 1}T10:00:00Z`,
718
- deviceType: 'WEB',
719
- }));
720
-
721
- requestStub = sinon.stub(device, 'request');
722
- requestStub.withArgs(sinon.match({method: 'GET'})).resolves({body: {devices}});
723
- requestStub.withArgs(sinon.match({method: 'DELETE'})).resolves();
724
-
725
- await device.deleteDevices();
726
-
727
- // ceil(6/3) = 2 deletions (< 5), targetCount = 6 - min(5, 2) = 4
728
- // With the old n-5 formula this was 1, which is unreachable and burned all 5 polls
729
- assert.equal(requestStub.withArgs(sinon.match({method: 'DELETE'})).callCount, 2);
730
- assert.calledWith(waitForLimitStub, 4, 0);
731
- });
732
-
733
- it('regression: 144-device case — deleteDevices passes targetCount=139 (144 - min(5, ceil(144/3)))', async () => {
734
- setup('WEB');
735
- const devices = Array.from({length: 144}, (_, i) => ({
736
- url: `url${i}`,
737
- modificationTime: new Date(Date.UTC(2020, 0, 1, 0, i)).toISOString(),
738
- deviceType: 'WEB',
739
- }));
740
-
741
- requestStub = sinon.stub(device, 'request');
742
- requestStub.withArgs(sinon.match({method: 'GET'})).resolves({body: {devices}});
743
- requestStub.withArgs(sinon.match({method: 'DELETE'})).resolves();
744
-
745
- await device.deleteDevices();
746
-
747
- // ceil(144/3) = 48 deletions (>= 5), targetCount = 144 - min(5, 48) = 139
748
- assert.equal(requestStub.withArgs(sinon.match({method: 'DELETE'})).callCount, 48);
749
- assert.calledWith(waitForLimitStub, 139, 0);
750
- });
751
- });
752
-
753
- describe('_waitForDeviceCountBelowLimit()', () => {
754
- let clock;
755
-
756
- const setup = (deviceType) => {
757
- device.config.defaults = {body: {deviceType}};
758
- };
759
-
760
- beforeEach(() => {
761
- clock = sinon.useFakeTimers();
762
- });
763
-
764
- afterEach(() => {
765
- sinon.restore();
766
- clock.restore();
767
- });
768
-
769
- it('resolves immediately when device count is below the limit on first check', async () => {
770
- setup('WEB');
771
- const devices = Array.from({length: 50}, (_, i) => ({
772
- url: `url${i}`,
773
- modificationTime: `2023-10-01T10:00:00Z`,
774
- deviceType: 'WEB',
775
- }));
776
-
777
- sinon.stub(device, 'request')
778
- .withArgs(sinon.match({method: 'GET'}))
779
- .resolves({body: {devices}});
780
-
781
- const promise = device._waitForDeviceCountBelowLimit(55, 0);
782
- await clock.tickAsync(3000);
783
- await promise;
784
- });
785
-
786
- it('polls multiple times until device count drops below the limit', async () => {
787
- setup('WEB');
788
- const makeDevices = (count) =>
789
- Array.from({length: count}, (_, i) => ({
790
- url: `url${i}`,
791
- modificationTime: `2023-10-01T10:00:00Z`,
792
- deviceType: 'WEB',
793
- }));
794
-
795
- const requestStub = sinon.stub(device, 'request');
796
- requestStub.withArgs(sinon.match({method: 'GET'}))
797
- .onFirstCall().resolves({body: {devices: makeDevices(102)}})
798
- .onSecondCall().resolves({body: {devices: makeDevices(100)}})
799
- .onThirdCall().resolves({body: {devices: makeDevices(68)}});
800
-
801
- const promise = device._waitForDeviceCountBelowLimit(95, 0);
802
-
803
- // First poll: 102 devices (above target 95), continue polling
804
- await clock.tickAsync(3000);
805
- // Second poll: 100 devices (still above target 95), continue polling
806
- await clock.tickAsync(3000);
807
- // Third poll: 68 devices (below target 95), resolve
808
- await clock.tickAsync(3000);
809
-
810
- await promise;
811
-
812
- assert.equal(requestStub.withArgs(sinon.match({method: 'GET'})).callCount, 3);
463
+ const notDeletedUrls = ['url2', 'url3', 'url5', 'url6', 'url4'];
464
+ notDeletedUrls.forEach(url => {
465
+ assert(requestStub.neverCalledWith(sinon.match({uri: url, method: 'DELETE'})));
813
466
  });
467
+ });});
814
468
 
815
- it('gives up after max confirmation attempts and resolves anyway', async () => {
816
- setup('WEB');
817
- const makeDevices = (count) =>
818
- Array.from({length: count}, (_, i) => ({
819
- url: `url${i}`,
820
- modificationTime: `2023-10-01T10:00:00Z`,
821
- deviceType: 'WEB',
822
- }));
823
-
824
- const requestStub = sinon.stub(device, 'request');
825
- requestStub.withArgs(sinon.match({method: 'GET'}))
826
- .resolves({body: {devices: makeDevices(105)}});
827
-
828
- const promise = device._waitForDeviceCountBelowLimit(100, 0);
829
-
830
- // Tick through all 5 attempts (5 * 3000ms)
831
- for (let i = 0; i < 5; i += 1) {
832
- await clock.tickAsync(3000);
469
+ it('does not delete when there are just 2 devices', async () => {
470
+ setup('WEB');
471
+ const response = {
472
+ body: {
473
+ devices: [
474
+ {url: 'url1', modificationTime: '2023-10-01T10:00:00Z', deviceType: 'WEB'},
475
+ {url: 'url2', modificationTime: '2023-10-02T10:00:00Z', deviceType: 'WEB'},
476
+ ]
833
477
  }
834
-
835
- await promise;
836
-
837
- assert(device.logger.warn.calledWith('device: max confirmation attempts reached, proceeding anyway'));
838
- assert.equal(requestStub.withArgs(sinon.match({method: 'GET'})).callCount, 5);
839
- });
840
-
841
- it('resolves when count equals exactly 95 (5 below limit)', async () => {
842
- setup('WEB');
843
- const devices = Array.from({length: 95}, (_, i) => ({
844
- url: `url${i}`,
845
- modificationTime: `2023-10-01T10:00:00Z`,
846
- deviceType: 'WEB',
847
- }));
848
-
849
- sinon.stub(device, 'request')
850
- .withArgs(sinon.match({method: 'GET'}))
851
- .resolves({body: {devices}});
852
-
853
- const promise = device._waitForDeviceCountBelowLimit(95, 0);
854
- await clock.tickAsync(3000);
855
- await promise;
856
- });
857
-
858
- it('keeps polling when count is above the 5-below-limit threshold', async () => {
859
- setup('WEB');
860
- const makeDevices = (count) =>
861
- Array.from({length: count}, (_, i) => ({
862
- url: `url${i}`,
863
- modificationTime: `2023-10-01T10:00:00Z`,
864
- deviceType: 'WEB',
865
- }));
866
-
867
- const requestStub = sinon.stub(device, 'request');
868
- requestStub.withArgs(sinon.match({method: 'GET'}))
869
- .onFirstCall().resolves({body: {devices: makeDevices(100)}})
870
- .onSecondCall().resolves({body: {devices: makeDevices(99)}})
871
- .onThirdCall().resolves({body: {devices: makeDevices(95)}});
872
-
873
- const promise = device._waitForDeviceCountBelowLimit(95, 0);
874
- // First poll: 100 devices (still over the 95 threshold), continue polling
875
- await clock.tickAsync(3000);
876
- // Second poll: 99 devices (still over the 95 threshold), continue polling
877
- await clock.tickAsync(3000);
878
- // Third poll: 95 devices (at the safe threshold), resolve
879
- await clock.tickAsync(3000);
880
- await promise;
881
-
882
- assert.equal(requestStub.withArgs(sinon.match({method: 'GET'})).callCount, 3);
883
- });
884
-
885
- it('resolves (best-effort) when the polling GET throws a transient error', async () => {
886
- setup('WEB');
887
-
888
- sinon.stub(device, 'request')
889
- .withArgs(sinon.match({method: 'GET'}))
890
- .rejects(new Error('transient network error'));
891
-
892
- const promise = device._waitForDeviceCountBelowLimit(95, 0);
893
- await clock.tickAsync(3000);
894
- await promise;
895
-
896
- assert(device.logger.warn.calledWith(
897
- sinon.match('device: confirmation check 1 failed, proceeding anyway:')
898
- ));
899
- });
900
- });
901
-
902
- describe('_getDevicesOfCurrentType()', () => {
903
- const setup = (deviceType) => {
904
- device.config.defaults = {body: {deviceType}};
905
478
  };
906
479
 
907
- afterEach(() => {
908
- sinon.restore();
909
- });
910
-
911
- it('filters devices by the current device type', async () => {
912
- setup('WEB');
913
- const allDevices = [
914
- {url: 'web1', deviceType: 'WEB'},
915
- {url: 'desktop1', deviceType: 'DESKTOP'},
916
- {url: 'web2', deviceType: 'WEB'},
917
- {url: 'mobile1', deviceType: 'MOBILE'},
918
- ];
919
-
920
- sinon.stub(device, 'request').resolves({body: {devices: allDevices}});
921
-
922
- const result = await device._getDevicesOfCurrentType();
923
-
924
- assert.equal(result.length, 2);
925
- assert.equal(result[0].url, 'web1');
926
- assert.equal(result[1].url, 'web2');
927
- });
480
+ const requestStub = sinon.stub(device, 'request');
481
+ requestStub.withArgs(sinon.match({method: 'GET'})).resolves(response);
482
+ requestStub.withArgs(sinon.match({method: 'DELETE'})).resolves();
928
483
 
929
- it('returns an empty array when no devices match', async () => {
930
- setup('WEB');
931
- const allDevices = [
932
- {url: 'desktop1', deviceType: 'DESKTOP'},
933
- {url: 'mobile1', deviceType: 'MOBILE'},
934
- ];
935
-
936
- sinon.stub(device, 'request').resolves({body: {devices: allDevices}});
937
-
938
- const result = await device._getDevicesOfCurrentType();
939
-
940
- assert.equal(result.length, 0);
484
+ await device.deleteDevices();
485
+ const notDeletedUrls = ['url1', 'url2'];
486
+ notDeletedUrls.forEach(url => {
487
+ assert(requestStub.neverCalledWith(sinon.match({uri: url, method: 'DELETE'})));
941
488
  });
942
489
  });
490
+ });
943
491
 
944
492
  describe('#unregister()', () => {
945
493
  it('resolves immediately if the device is not registered', async () => {
@@ -1015,17 +563,11 @@ describe('plugin-device', () => {
1015
563
  it('calls delete devices when errors with User has excessive device registrations', async () => {
1016
564
  setup();
1017
565
  sinon.stub(device, 'canRegister').callsFake(() => Promise.resolve());
1018
- const deleteDeviceSpy = sinon
1019
- .stub(device, 'deleteDevices')
1020
- .callsFake(() => Promise.resolve());
566
+ const deleteDeviceSpy = sinon.stub(device, 'deleteDevices').callsFake(() => Promise.resolve());
1021
567
  const registerStub = sinon.stub(device, '_registerInternal');
1022
-
1023
- registerStub
1024
- .onFirstCall()
1025
- .rejects({body: {message: 'User has excessive device registrations'}});
1026
- registerStub
1027
- .onSecondCall()
1028
- .callsFake(() => Promise.resolve({exampleKey: 'example response value'}));
568
+
569
+ registerStub.onFirstCall().rejects({body: {message: 'User has excessive device registrations'}});
570
+ registerStub.onSecondCall().callsFake(() => Promise.resolve({exampleKey: 'example response value',}));
1029
571
 
1030
572
  const result = await device.register();
1031
573
 
@@ -1040,12 +582,8 @@ describe('plugin-device', () => {
1040
582
  setup();
1041
583
 
1042
584
  sinon.stub(device, 'canRegister').callsFake(() => Promise.resolve());
1043
- const deleteDeviceSpy = sinon
1044
- .stub(device, 'deleteDevices')
1045
- .callsFake(() => Promise.resolve());
1046
- const registerStub = sinon
1047
- .stub(device, '_registerInternal')
1048
- .rejects(new Error('some error'));
585
+ const deleteDeviceSpy = sinon.stub(device, 'deleteDevices').callsFake(() => Promise.resolve());
586
+ const registerStub = sinon.stub(device, '_registerInternal').rejects(new Error('some error'));
1049
587
 
1050
588
  try {
1051
589
  await device.register({deleteFlag: true});
@@ -1095,7 +633,7 @@ describe('plugin-device', () => {
1095
633
  resolve({
1096
634
  body: {
1097
635
  exampleKey: 'example response value',
1098
- },
636
+ }
1099
637
  });
1100
638
 
1101
639
  await resultPromise;
@@ -1132,6 +670,7 @@ describe('plugin-device', () => {
1132
670
  assert.calledOnce(device.processRegistrationSuccess);
1133
671
  });
1134
672
 
673
+
1135
674
  it('checks that submitInternalEvent gets called with internal.register.device.response on success', async () => {
1136
675
  setup();
1137
676
  sinon.stub(device, 'canRegister').callsFake(() => Promise.resolve());
@@ -1283,7 +822,7 @@ describe('plugin-device', () => {
1283
822
 
1284
823
  it('works when request returns 404 when already registered', async () => {
1285
824
  setup();
1286
-
825
+
1287
826
  sinon.stub(device, 'canRegister').callsFake(() => Promise.resolve());
1288
827
 
1289
828
  const requestStub = sinon.stub(device, 'request');
@@ -1299,52 +838,7 @@ describe('plugin-device', () => {
1299
838
  });
1300
839
  });
1301
840
 
1302
- describe('getDebugFeatures()', () => {
1303
- it('returns empty list if debugFeatureTogglesKey is not set', () => {
1304
- assert.isUndefined(device.config.debugFeatureTogglesKey);
1305
- const debugFeatures = device.getDebugFeatures();
1306
-
1307
- assert.deepEqual(debugFeatures, []);
1308
- });
1309
-
1310
- it('returns empty list if no debug features in session storage', () => {
1311
- device.config.debugFeatureTogglesKey = 'debug-feature-toggles';
1312
- assert.isUndefined(webex.getWindow().sessionStorage.getItem('debug-feature-toggles'));
1313
- const debugFeatures = device.getDebugFeatures();
1314
-
1315
- assert.deepEqual(debugFeatures, []);
1316
- });
1317
-
1318
- it('returns debug features from session storage', () => {
1319
- device.config.debugFeatureTogglesKey = 'debug-feature-toggles';
1320
- webex.getWindow().sessionStorage.setItem(
1321
- 'debug-feature-toggles',
1322
- JSON.stringify({
1323
- feature_to_debug_enable: true,
1324
- feature_to_debug_disable: false,
1325
- })
1326
- );
1327
- const debugFeatures = device.getDebugFeatures();
1328
-
1329
- assert.equal(debugFeatures.length, 2);
1330
-
1331
- assert.properties(debugFeatures[0], ['key', 'val', 'mutable', 'lastModified']);
1332
- assert.equal(debugFeatures[0].key, 'feature_to_debug_enable');
1333
- assert.equal(debugFeatures[0].val, 'true');
1334
- assert.isTrue(debugFeatures[0].mutable);
1335
- assert.isISODate(debugFeatures[0].lastModified);
1336
-
1337
- assert.properties(debugFeatures[1], ['key', 'val', 'mutable', 'lastModified']);
1338
- assert.equal(debugFeatures[1].key, 'feature_to_debug_disable');
1339
- assert.equal(debugFeatures[1].val, 'false');
1340
- assert.isTrue(debugFeatures[1].mutable);
1341
- assert.isISODate(debugFeatures[1].lastModified);
1342
- });
1343
- });
1344
-
1345
841
  describe('#processRegistrationSuccess()', () => {
1346
- const initialDTOFeatureCounts = {developer: 2, entitlement: 1, user: 1};
1347
-
1348
842
  const getClonedDTO = (overrides) => {
1349
843
  const clonedDTO = cloneDeep(dto);
1350
844
 
@@ -1358,22 +852,6 @@ describe('plugin-device', () => {
1358
852
  mutable: true,
1359
853
  lastModified: '2015-06-29T20:02:48.033Z',
1360
854
  },
1361
- {
1362
- key: 'feature_to_debug_enable',
1363
- type: 'boolean',
1364
- val: 'false',
1365
- value: false,
1366
- mutable: true,
1367
- lastModified: '2015-06-29T20:02:48.033Z',
1368
- },
1369
- {
1370
- key: 'feature_to_debug_disable',
1371
- type: 'boolean',
1372
- val: 'true',
1373
- value: true,
1374
- mutable: true,
1375
- lastModified: '2015-06-29T20:02:48.033Z',
1376
- },
1377
855
  ],
1378
856
  entitlement: [
1379
857
  {
@@ -1397,21 +875,16 @@ describe('plugin-device', () => {
1397
875
  return clonedDTO;
1398
876
  };
1399
877
 
1400
- const checkFeatureTypeCounts = (expectedCounts) => {
1401
- Object.entries(expectedCounts).forEach(([type, expectedCount]) => {
1402
- assert.equal(device.features[type].length, expectedCount);
1403
- });
1404
- };
1405
-
1406
878
  const checkFeatureNotPresent = (type, key) => {
1407
879
  assert.isUndefined(device.features[type].get(key));
1408
880
  };
1409
881
 
1410
882
  const checkFeature = (type, key, expectedValue) => {
883
+ assert.equal(device.features[type].length, 1);
1411
884
  assert.deepEqual(device.features[type].get(key).get('value'), expectedValue);
1412
885
  };
1413
886
 
1414
- it('features are set correctly if etag not in headers, no debug features', () => {
887
+ it('features are set correctly if etag not in headers', () => {
1415
888
  const clonedDTO = getClonedDTO();
1416
889
 
1417
890
  const response = {
@@ -1421,61 +894,13 @@ describe('plugin-device', () => {
1421
894
  headers: {},
1422
895
  };
1423
896
 
1424
- checkFeatureTypeCounts(initialDTOFeatureCounts);
1425
897
  checkFeatureNotPresent('developer', '1');
1426
- checkFeatureNotPresent('developer', 'feature_to_debug_enable');
1427
- checkFeatureNotPresent('developer', 'feature_to_debug_disable');
1428
- checkFeatureNotPresent('developer', 'feature_debug_only');
1429
898
  checkFeatureNotPresent('entitlement', '2');
1430
899
  checkFeatureNotPresent('user', '3');
1431
900
 
1432
901
  device.processRegistrationSuccess(response);
1433
902
 
1434
- checkFeatureTypeCounts({developer: 3, entitlement: 1, user: 1});
1435
903
  checkFeature('developer', '1', true);
1436
- checkFeature('developer', 'feature_to_debug_enable', false);
1437
- checkFeature('developer', 'feature_to_debug_disable', true);
1438
- checkFeatureNotPresent('developer', 'feature_debug_only');
1439
- checkFeature('entitlement', '2', true);
1440
- checkFeature('user', '3', true);
1441
- });
1442
-
1443
- it('features are set correctly if etag not in headers, debug features in session storage', () => {
1444
- const clonedDTO = getClonedDTO();
1445
-
1446
- const response = {
1447
- body: {
1448
- ...clonedDTO,
1449
- },
1450
- headers: {},
1451
- };
1452
-
1453
- device.config.debugFeatureTogglesKey = 'debug-feature-toggles';
1454
-
1455
- webex.getWindow().sessionStorage.setItem(
1456
- 'debug-feature-toggles',
1457
- JSON.stringify({
1458
- feature_to_debug_enable: true,
1459
- feature_to_debug_disable: false,
1460
- feature_debug_only: true,
1461
- })
1462
- );
1463
-
1464
- checkFeatureTypeCounts(initialDTOFeatureCounts);
1465
- checkFeatureNotPresent('developer', '1');
1466
- checkFeatureNotPresent('developer', 'feature_to_debug_enable');
1467
- checkFeatureNotPresent('developer', 'feature_to_debug_disable');
1468
- checkFeatureNotPresent('developer', 'feature_debug_only');
1469
- checkFeatureNotPresent('entitlement', '2');
1470
- checkFeatureNotPresent('user', '3');
1471
-
1472
- device.processRegistrationSuccess(response);
1473
-
1474
- checkFeatureTypeCounts({developer: 4, entitlement: 1, user: 1});
1475
- checkFeature('developer', '1', true);
1476
- checkFeature('developer', 'feature_to_debug_enable', true);
1477
- checkFeature('developer', 'feature_to_debug_disable', false);
1478
- checkFeature('developer', 'feature_debug_only', true);
1479
904
  checkFeature('entitlement', '2', true);
1480
905
  checkFeature('user', '3', true);
1481
906
  });
@@ -1494,17 +919,12 @@ describe('plugin-device', () => {
1494
919
  },
1495
920
  };
1496
921
 
1497
- checkFeatureTypeCounts(initialDTOFeatureCounts);
1498
922
  checkFeatureNotPresent('developer', '1');
1499
- checkFeatureNotPresent('developer', 'feature_to_debug_enable');
1500
- checkFeatureNotPresent('developer', 'feature_to_debug_disable');
1501
- checkFeatureNotPresent('developer', 'feature_debug_only');
1502
923
  checkFeatureNotPresent('entitlement', '2');
1503
924
  checkFeatureNotPresent('user', '3');
1504
925
 
1505
926
  device.processRegistrationSuccess(response);
1506
927
 
1507
- checkFeatureTypeCounts(initialDTOFeatureCounts.developer);
1508
928
  checkFeatureNotPresent('developer', '1');
1509
929
  checkFeature('entitlement', '2', true);
1510
930
  checkFeature('user', '3', true);
@@ -1527,21 +947,12 @@ describe('plugin-device', () => {
1527
947
  },
1528
948
  };
1529
949
 
1530
- checkFeatureTypeCounts(initialDTOFeatureCounts);
1531
950
  checkFeatureNotPresent('developer', '1');
1532
- checkFeatureNotPresent('developer', 'feature_to_debug_enable');
1533
- checkFeatureNotPresent('developer', 'feature_to_debug_disable');
1534
- checkFeatureNotPresent('developer', 'feature_debug_only');
1535
951
  checkFeatureNotPresent('entitlement', '2');
1536
952
  checkFeatureNotPresent('user', '3');
1537
953
 
1538
954
  device.processRegistrationSuccess(response);
1539
955
 
1540
- checkFeatureTypeCounts({
1541
- developer: initialDTOFeatureCounts.developer,
1542
- entitlement: 1,
1543
- user: 1,
1544
- });
1545
956
  checkFeatureNotPresent('developer', '1');
1546
957
  checkFeature('entitlement', '2', true);
1547
958
  checkFeature('user', '3', true);
@@ -1564,17 +975,12 @@ describe('plugin-device', () => {
1564
975
  },
1565
976
  };
1566
977
 
1567
- checkFeatureTypeCounts(initialDTOFeatureCounts);
1568
978
  checkFeatureNotPresent('developer', '1');
1569
- checkFeatureNotPresent('developer', 'feature_to_debug_enable');
1570
- checkFeatureNotPresent('developer', 'feature_to_debug_disable');
1571
- checkFeatureNotPresent('developer', 'feature_debug_only');
1572
979
  checkFeatureNotPresent('entitlement', '2');
1573
980
  checkFeatureNotPresent('user', '3');
1574
981
 
1575
982
  device.processRegistrationSuccess(response);
1576
983
 
1577
- checkFeatureTypeCounts({developer: 3, entitlement: 1, user: 1});
1578
984
  checkFeature('developer', '1', true);
1579
985
  checkFeature('entitlement', '2', true);
1580
986
  checkFeature('user', '3', true);
@@ -1623,7 +1029,6 @@ describe('plugin-device', () => {
1623
1029
  device.processRegistrationSuccess(newResponse);
1624
1030
 
1625
1031
  // only the entitlement and user features should have been changed to false
1626
- checkFeatureTypeCounts({developer: 3, entitlement: 1, user: 1});
1627
1032
  checkFeature('developer', '1', true);
1628
1033
  checkFeature('entitlement', '2', false);
1629
1034
  checkFeature('user', '3', false);