@webex/internal-plugin-device 3.11.0 → 3.12.0-next.2
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 +16 -10
- package/dist/config.js.map +1 -1
- package/dist/constants.js +6 -1
- package/dist/constants.js.map +1 -1
- package/dist/device.js +181 -79
- package/dist/device.js.map +1 -1
- package/dist/ipNetworkDetector.js +3 -3
- package/dist/ipNetworkDetector.js.map +1 -1
- package/package.json +10 -10
- package/src/config.js +16 -9
- package/src/constants.js +5 -0
- package/src/device.js +150 -26
- package/src/ipNetworkDetector.ts +1 -1
- package/test/unit/spec/device.js +641 -46
package/test/unit/spec/device.js
CHANGED
|
@@ -20,10 +20,19 @@ describe('plugin-device', () => {
|
|
|
20
20
|
let device;
|
|
21
21
|
|
|
22
22
|
beforeEach(() => {
|
|
23
|
+
const fakeStorage = {};
|
|
23
24
|
webex = new MockWebex({
|
|
24
25
|
children: {
|
|
25
26
|
device: Device,
|
|
26
27
|
},
|
|
28
|
+
getWindow: () => ({
|
|
29
|
+
sessionStorage: {
|
|
30
|
+
setItem: (key, value) => {
|
|
31
|
+
fakeStorage[key] = value;
|
|
32
|
+
},
|
|
33
|
+
getItem: (key) => fakeStorage[key],
|
|
34
|
+
},
|
|
35
|
+
}),
|
|
27
36
|
});
|
|
28
37
|
|
|
29
38
|
const clonedDTO = cloneDeep(dto);
|
|
@@ -103,6 +112,54 @@ describe('plugin-device', () => {
|
|
|
103
112
|
});
|
|
104
113
|
});
|
|
105
114
|
});
|
|
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
|
+
});
|
|
106
163
|
});
|
|
107
164
|
|
|
108
165
|
describe('derived properties', () => {
|
|
@@ -424,18 +481,34 @@ describe('plugin-device', () => {
|
|
|
424
481
|
|
|
425
482
|
assert.calledOnce(device.processRegistrationSuccess);
|
|
426
483
|
});
|
|
427
|
-
|
|
428
484
|
});
|
|
429
485
|
|
|
430
486
|
describe('deleteDevices()', () => {
|
|
487
|
+
let requestStub;
|
|
488
|
+
let clock;
|
|
489
|
+
let waitForLimitStub;
|
|
490
|
+
|
|
431
491
|
const setup = (deviceType) => {
|
|
432
492
|
device.config.defaults = {body: {deviceType}};
|
|
433
493
|
};
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
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: {
|
|
439
512
|
devices: [
|
|
440
513
|
{url: 'url3', modificationTime: '2023-10-03T10:00:00Z', deviceType},
|
|
441
514
|
{url: 'url4', modificationTime: '2023-10-04T10:00:00Z', deviceType: 'notweb'},
|
|
@@ -445,49 +518,428 @@ describe('plugin-device', () => {
|
|
|
445
518
|
{url: 'url6', modificationTime: '2023-09-50T10:00:00Z', deviceType},
|
|
446
519
|
{url: 'url7', modificationTime: '2023-09-30T10:00:00Z', deviceType},
|
|
447
520
|
{url: 'url8', modificationTime: '2023-08-30T10:00:00Z', deviceType},
|
|
448
|
-
]
|
|
449
|
-
|
|
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
|
+
},
|
|
450
553
|
};
|
|
451
|
-
const requestStub = sinon.stub(device, 'request');
|
|
452
|
-
requestStub.withArgs(sinon.match({method: 'GET'})).resolves(response);
|
|
453
|
-
requestStub.withArgs(sinon.match({method: 'DELETE'})).resolves();
|
|
454
554
|
|
|
455
|
-
|
|
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
|
+
});
|
|
456
580
|
|
|
457
|
-
|
|
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();
|
|
458
599
|
|
|
459
|
-
|
|
460
|
-
|
|
600
|
+
// ceil(6/3) = 2 devices should be deleted
|
|
601
|
+
assert.equal(deleteOrder.length, 2);
|
|
461
602
|
});
|
|
462
603
|
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
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);
|
|
466
813
|
});
|
|
467
|
-
});});
|
|
468
814
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
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);
|
|
477
833
|
}
|
|
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}};
|
|
478
905
|
};
|
|
479
906
|
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
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
|
+
});
|
|
483
928
|
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
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);
|
|
488
941
|
});
|
|
489
942
|
});
|
|
490
|
-
});
|
|
491
943
|
|
|
492
944
|
describe('#unregister()', () => {
|
|
493
945
|
it('resolves immediately if the device is not registered', async () => {
|
|
@@ -563,11 +1015,17 @@ describe('plugin-device', () => {
|
|
|
563
1015
|
it('calls delete devices when errors with User has excessive device registrations', async () => {
|
|
564
1016
|
setup();
|
|
565
1017
|
sinon.stub(device, 'canRegister').callsFake(() => Promise.resolve());
|
|
566
|
-
const deleteDeviceSpy = sinon
|
|
1018
|
+
const deleteDeviceSpy = sinon
|
|
1019
|
+
.stub(device, 'deleteDevices')
|
|
1020
|
+
.callsFake(() => Promise.resolve());
|
|
567
1021
|
const registerStub = sinon.stub(device, '_registerInternal');
|
|
568
|
-
|
|
569
|
-
registerStub
|
|
570
|
-
|
|
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'}));
|
|
571
1029
|
|
|
572
1030
|
const result = await device.register();
|
|
573
1031
|
|
|
@@ -582,8 +1040,12 @@ describe('plugin-device', () => {
|
|
|
582
1040
|
setup();
|
|
583
1041
|
|
|
584
1042
|
sinon.stub(device, 'canRegister').callsFake(() => Promise.resolve());
|
|
585
|
-
const deleteDeviceSpy = sinon
|
|
586
|
-
|
|
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'));
|
|
587
1049
|
|
|
588
1050
|
try {
|
|
589
1051
|
await device.register({deleteFlag: true});
|
|
@@ -633,7 +1095,7 @@ describe('plugin-device', () => {
|
|
|
633
1095
|
resolve({
|
|
634
1096
|
body: {
|
|
635
1097
|
exampleKey: 'example response value',
|
|
636
|
-
}
|
|
1098
|
+
},
|
|
637
1099
|
});
|
|
638
1100
|
|
|
639
1101
|
await resultPromise;
|
|
@@ -670,7 +1132,6 @@ describe('plugin-device', () => {
|
|
|
670
1132
|
assert.calledOnce(device.processRegistrationSuccess);
|
|
671
1133
|
});
|
|
672
1134
|
|
|
673
|
-
|
|
674
1135
|
it('checks that submitInternalEvent gets called with internal.register.device.response on success', async () => {
|
|
675
1136
|
setup();
|
|
676
1137
|
sinon.stub(device, 'canRegister').callsFake(() => Promise.resolve());
|
|
@@ -822,7 +1283,7 @@ describe('plugin-device', () => {
|
|
|
822
1283
|
|
|
823
1284
|
it('works when request returns 404 when already registered', async () => {
|
|
824
1285
|
setup();
|
|
825
|
-
|
|
1286
|
+
|
|
826
1287
|
sinon.stub(device, 'canRegister').callsFake(() => Promise.resolve());
|
|
827
1288
|
|
|
828
1289
|
const requestStub = sinon.stub(device, 'request');
|
|
@@ -838,7 +1299,52 @@ describe('plugin-device', () => {
|
|
|
838
1299
|
});
|
|
839
1300
|
});
|
|
840
1301
|
|
|
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
|
+
|
|
841
1345
|
describe('#processRegistrationSuccess()', () => {
|
|
1346
|
+
const initialDTOFeatureCounts = {developer: 2, entitlement: 1, user: 1};
|
|
1347
|
+
|
|
842
1348
|
const getClonedDTO = (overrides) => {
|
|
843
1349
|
const clonedDTO = cloneDeep(dto);
|
|
844
1350
|
|
|
@@ -852,6 +1358,22 @@ describe('plugin-device', () => {
|
|
|
852
1358
|
mutable: true,
|
|
853
1359
|
lastModified: '2015-06-29T20:02:48.033Z',
|
|
854
1360
|
},
|
|
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
|
+
},
|
|
855
1377
|
],
|
|
856
1378
|
entitlement: [
|
|
857
1379
|
{
|
|
@@ -875,16 +1397,21 @@ describe('plugin-device', () => {
|
|
|
875
1397
|
return clonedDTO;
|
|
876
1398
|
};
|
|
877
1399
|
|
|
1400
|
+
const checkFeatureTypeCounts = (expectedCounts) => {
|
|
1401
|
+
Object.entries(expectedCounts).forEach(([type, expectedCount]) => {
|
|
1402
|
+
assert.equal(device.features[type].length, expectedCount);
|
|
1403
|
+
});
|
|
1404
|
+
};
|
|
1405
|
+
|
|
878
1406
|
const checkFeatureNotPresent = (type, key) => {
|
|
879
1407
|
assert.isUndefined(device.features[type].get(key));
|
|
880
1408
|
};
|
|
881
1409
|
|
|
882
1410
|
const checkFeature = (type, key, expectedValue) => {
|
|
883
|
-
assert.equal(device.features[type].length, 1);
|
|
884
1411
|
assert.deepEqual(device.features[type].get(key).get('value'), expectedValue);
|
|
885
1412
|
};
|
|
886
1413
|
|
|
887
|
-
it('features are set correctly if etag not in headers', () => {
|
|
1414
|
+
it('features are set correctly if etag not in headers, no debug features', () => {
|
|
888
1415
|
const clonedDTO = getClonedDTO();
|
|
889
1416
|
|
|
890
1417
|
const response = {
|
|
@@ -894,13 +1421,61 @@ describe('plugin-device', () => {
|
|
|
894
1421
|
headers: {},
|
|
895
1422
|
};
|
|
896
1423
|
|
|
1424
|
+
checkFeatureTypeCounts(initialDTOFeatureCounts);
|
|
897
1425
|
checkFeatureNotPresent('developer', '1');
|
|
1426
|
+
checkFeatureNotPresent('developer', 'feature_to_debug_enable');
|
|
1427
|
+
checkFeatureNotPresent('developer', 'feature_to_debug_disable');
|
|
1428
|
+
checkFeatureNotPresent('developer', 'feature_debug_only');
|
|
898
1429
|
checkFeatureNotPresent('entitlement', '2');
|
|
899
1430
|
checkFeatureNotPresent('user', '3');
|
|
900
1431
|
|
|
901
1432
|
device.processRegistrationSuccess(response);
|
|
902
1433
|
|
|
1434
|
+
checkFeatureTypeCounts({developer: 3, entitlement: 1, user: 1});
|
|
903
1435
|
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);
|
|
904
1479
|
checkFeature('entitlement', '2', true);
|
|
905
1480
|
checkFeature('user', '3', true);
|
|
906
1481
|
});
|
|
@@ -919,12 +1494,17 @@ describe('plugin-device', () => {
|
|
|
919
1494
|
},
|
|
920
1495
|
};
|
|
921
1496
|
|
|
1497
|
+
checkFeatureTypeCounts(initialDTOFeatureCounts);
|
|
922
1498
|
checkFeatureNotPresent('developer', '1');
|
|
1499
|
+
checkFeatureNotPresent('developer', 'feature_to_debug_enable');
|
|
1500
|
+
checkFeatureNotPresent('developer', 'feature_to_debug_disable');
|
|
1501
|
+
checkFeatureNotPresent('developer', 'feature_debug_only');
|
|
923
1502
|
checkFeatureNotPresent('entitlement', '2');
|
|
924
1503
|
checkFeatureNotPresent('user', '3');
|
|
925
1504
|
|
|
926
1505
|
device.processRegistrationSuccess(response);
|
|
927
1506
|
|
|
1507
|
+
checkFeatureTypeCounts(initialDTOFeatureCounts.developer);
|
|
928
1508
|
checkFeatureNotPresent('developer', '1');
|
|
929
1509
|
checkFeature('entitlement', '2', true);
|
|
930
1510
|
checkFeature('user', '3', true);
|
|
@@ -947,12 +1527,21 @@ describe('plugin-device', () => {
|
|
|
947
1527
|
},
|
|
948
1528
|
};
|
|
949
1529
|
|
|
1530
|
+
checkFeatureTypeCounts(initialDTOFeatureCounts);
|
|
950
1531
|
checkFeatureNotPresent('developer', '1');
|
|
1532
|
+
checkFeatureNotPresent('developer', 'feature_to_debug_enable');
|
|
1533
|
+
checkFeatureNotPresent('developer', 'feature_to_debug_disable');
|
|
1534
|
+
checkFeatureNotPresent('developer', 'feature_debug_only');
|
|
951
1535
|
checkFeatureNotPresent('entitlement', '2');
|
|
952
1536
|
checkFeatureNotPresent('user', '3');
|
|
953
1537
|
|
|
954
1538
|
device.processRegistrationSuccess(response);
|
|
955
1539
|
|
|
1540
|
+
checkFeatureTypeCounts({
|
|
1541
|
+
developer: initialDTOFeatureCounts.developer,
|
|
1542
|
+
entitlement: 1,
|
|
1543
|
+
user: 1,
|
|
1544
|
+
});
|
|
956
1545
|
checkFeatureNotPresent('developer', '1');
|
|
957
1546
|
checkFeature('entitlement', '2', true);
|
|
958
1547
|
checkFeature('user', '3', true);
|
|
@@ -975,12 +1564,17 @@ describe('plugin-device', () => {
|
|
|
975
1564
|
},
|
|
976
1565
|
};
|
|
977
1566
|
|
|
1567
|
+
checkFeatureTypeCounts(initialDTOFeatureCounts);
|
|
978
1568
|
checkFeatureNotPresent('developer', '1');
|
|
1569
|
+
checkFeatureNotPresent('developer', 'feature_to_debug_enable');
|
|
1570
|
+
checkFeatureNotPresent('developer', 'feature_to_debug_disable');
|
|
1571
|
+
checkFeatureNotPresent('developer', 'feature_debug_only');
|
|
979
1572
|
checkFeatureNotPresent('entitlement', '2');
|
|
980
1573
|
checkFeatureNotPresent('user', '3');
|
|
981
1574
|
|
|
982
1575
|
device.processRegistrationSuccess(response);
|
|
983
1576
|
|
|
1577
|
+
checkFeatureTypeCounts({developer: 3, entitlement: 1, user: 1});
|
|
984
1578
|
checkFeature('developer', '1', true);
|
|
985
1579
|
checkFeature('entitlement', '2', true);
|
|
986
1580
|
checkFeature('user', '3', true);
|
|
@@ -1029,6 +1623,7 @@ describe('plugin-device', () => {
|
|
|
1029
1623
|
device.processRegistrationSuccess(newResponse);
|
|
1030
1624
|
|
|
1031
1625
|
// only the entitlement and user features should have been changed to false
|
|
1626
|
+
checkFeatureTypeCounts({developer: 3, entitlement: 1, user: 1});
|
|
1032
1627
|
checkFeature('developer', '1', true);
|
|
1033
1628
|
checkFeature('entitlement', '2', false);
|
|
1034
1629
|
checkFeature('user', '3', false);
|