Skip to content

Commit

Permalink
feat(internal-device-plugin): added deleteDevices (#3998)
Browse files Browse the repository at this point in the history
  • Loading branch information
shnaaz authored Nov 25, 2024
1 parent 6b5dae2 commit e8a8fc0
Show file tree
Hide file tree
Showing 2 changed files with 167 additions and 6 deletions.
77 changes: 71 additions & 6 deletions packages/@webex/internal-plugin-device/src/device.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import {deprecated, oneFlight} from '@webex/common';
import {persist, waitForValue, WebexPlugin} from '@webex/webex-core';
import {safeSetTimeout} from '@webex/common-timers';
import {orderBy} from 'lodash';

import METRICS from './metrics';
import {FEATURE_COLLECTION_NAMES, DEVICE_EVENT_REGISTRATION_SUCCESS} from './constants';
Expand Down Expand Up @@ -439,6 +440,74 @@ const Device = WebexPlugin.extend({
});
});
},
/**
* Fetches the web devices and deletes the third of them which are not recent devices in use
* @returns {Promise<void, Error>}
*/
deleteDevices() {
// Fetch devices with a GET request
return this.request({
method: 'GET',
service: 'wdm',
resource: 'devices',
})
.then((response) => {
const {devices} = response.body;

const {deviceType} = this._getBody();

// Filter devices of type deviceType
const webDevices = devices.filter((item) => item.deviceType === deviceType);

const sortedDevices = orderBy(webDevices, [(item) => new Date(item.modificationTime)]);

// If there are more than two devices, delete the last third
if (sortedDevices.length > 2) {
const totalItems = sortedDevices.length;
const countToDelete = Math.ceil(totalItems / 3);
const urlsToDelete = sortedDevices.slice(0, countToDelete).map((item) => item.url);

return Promise.race(
urlsToDelete.map((url) => {
return this.request({
uri: url,
method: 'DELETE',
});
})
);
}

return Promise.resolve();
})
.catch((error) => {
this.logger.error('Failed to retrieve devices:', error);

return Promise.reject(error);
});
},

/**
* Registers and when fails deletes devices
*/
@oneFlight
@waitForValue('@')
register(deviceRegistrationOptions = {}) {
return this._registerInternal(deviceRegistrationOptions).catch((error) => {
if (error?.body?.message === 'User has excessive device registrations') {
return this.deleteDevices().then(() => {
return this._registerInternal(deviceRegistrationOptions);
});
}
throw error;
});
},

_getBody() {
return {
...(this.config.defaults.body ? this.config.defaults.body : {}),
...(this.config.body ? this.config.body : {}),
};
},

/**
* Register or refresh a device depending on the current device state. Device
Expand All @@ -451,7 +520,7 @@ const Device = WebexPlugin.extend({
*/
@oneFlight
@waitForValue('@')
register(deviceRegistrationOptions = {}) {
_registerInternal(deviceRegistrationOptions = {}) {
this.logger.info('device: registering');

this.webex.internal.newMetrics.callDiagnosticMetrics.setDeviceInfo(this);
Expand All @@ -466,10 +535,7 @@ const Device = WebexPlugin.extend({
}

// Merge body configurations, overriding defaults.
const body = {
...(this.config.defaults.body ? this.config.defaults.body : {}),
...(this.config.body ? this.config.body : {}),
};
const body = this._getBody();

// Merge header configurations, overriding defaults.
const headers = {
Expand Down Expand Up @@ -527,7 +593,6 @@ const Device = WebexPlugin.extend({
});
});
},

/**
* Unregister the current registered device if available. Unregistering a
* device utilizes the services plugin to send the request to the **WDM**
Expand Down
96 changes: 96 additions & 0 deletions packages/@webex/internal-plugin-device/test/unit/spec/device.js
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,68 @@ describe('plugin-device', () => {
});
});

describe('deleteDevices()', () => {
const setup = (deviceType) => {
device.config.defaults = {body: {deviceType}};
};
['WEB', 'WEBCLIENT'].forEach(deviceType => {
it(`should delete correct number of devices for ${deviceType}`, async () => {
setup(deviceType);
const response = {
body: {
devices: [
{url: 'url3', modificationTime: '2023-10-03T10:00:00Z', deviceType},
{url: 'url4', modificationTime: '2023-10-04T10:00:00Z', deviceType: 'notweb'},
{url: 'url1', modificationTime: '2023-10-01T10:00:00Z', deviceType},
{url: 'url2', modificationTime: '2023-10-02T10:00:00Z', deviceType},
{url: 'url5', modificationTime: '2023-10-00T10:00:00Z', deviceType},
{url: 'url6', modificationTime: '2023-09-50T10:00:00Z', deviceType},
{url: 'url7', modificationTime: '2023-09-30T10:00:00Z', deviceType},
{url: 'url8', modificationTime: '2023-08-30T10:00:00Z', deviceType},
]
}
};
const requestStub = sinon.stub(device, 'request');
requestStub.withArgs(sinon.match({method: 'GET'})).resolves(response);
requestStub.withArgs(sinon.match({method: 'DELETE'})).resolves();

await device.deleteDevices();

const expectedDeletions = ['url8', 'url7', 'url1'];

expectedDeletions.forEach(url => {
assert(requestStub.calledWith(sinon.match({uri: url, method: 'DELETE'})));
});

const notDeletedUrls = ['url2', 'url3', 'url5', 'url6', 'url4'];
notDeletedUrls.forEach(url => {
assert(requestStub.neverCalledWith(sinon.match({uri: url, method: 'DELETE'})));
});
});});

it('does not delete when there are just 2 devices', async () => {
setup('WEB');
const response = {
body: {
devices: [
{url: 'url1', modificationTime: '2023-10-01T10:00:00Z', deviceType: 'WEB'},
{url: 'url2', modificationTime: '2023-10-02T10:00:00Z', deviceType: 'WEB'},
]
}
};

const requestStub = sinon.stub(device, 'request');
requestStub.withArgs(sinon.match({method: 'GET'})).resolves(response);
requestStub.withArgs(sinon.match({method: 'DELETE'})).resolves();

await device.deleteDevices();
const notDeletedUrls = ['url1', 'url2'];
notDeletedUrls.forEach(url => {
assert(requestStub.neverCalledWith(sinon.match({uri: url, method: 'DELETE'})));
});
});
});

describe('#register()', () => {
const setup = (config = {}) => {
webex.internal.metrics.submitClientMetrics = sinon.stub();
Expand Down Expand Up @@ -386,6 +448,40 @@ describe('plugin-device', () => {
});
});

it('calls delete devices when errors with User has excessive device registrations', async () => {
setup();
const deleteDeviceSpy = sinon.stub(device, 'deleteDevices').callsFake(() => Promise.resolve());
const registerStub = sinon.stub(device, '_registerInternal');

registerStub.onFirstCall().rejects({body: {message: 'User has excessive device registrations'}});
registerStub.onSecondCall().callsFake(() => Promise.resolve({exampleKey: 'example response value',}));

const result = await device.register();

assert.calledOnce(deleteDeviceSpy);

assert.equal(registerStub.callCount, 2);

assert.deepEqual(result, {exampleKey: 'example response value'});
});

it('does not call delete devices when some other error', async () => {
setup();

const deleteDeviceSpy = sinon.stub(device, 'deleteDevices').callsFake(() => Promise.resolve());
const registerStub = sinon.stub(device, '_registerInternal').rejects(new Error('some error'));

try {
await device.register({deleteFlag: true});
} catch (error) {
assert.notCalled(deleteDeviceSpy);

assert.equal(registerStub.callCount, 1);

assert.match(error.message, /some error/, 'Expected error message not matched');
}
});

it('checks that submitInternalEvent gets called with internal.register.device.response on error', async () => {
setup();
sinon.stub(device, 'canRegister').callsFake(() => Promise.resolve());
Expand Down

0 comments on commit e8a8fc0

Please sign in to comment.