Skip to content

Commit

Permalink
Merge e396c9f into cdddd0f
Browse files Browse the repository at this point in the history
  • Loading branch information
busticated committed Aug 1, 2020
2 parents cdddd0f + e396c9f commit d7cb656
Show file tree
Hide file tree
Showing 5 changed files with 129 additions and 30 deletions.
12 changes: 11 additions & 1 deletion src/cli/product.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ module.exports = ({ commandProcessor, root }) => {
});

commandProcessor.createCommand(device, 'add', 'Adds one or more devices into a Product', {
params: '<product> [device]',
params: '<product> [deviceID]',
options: {
file: {
alias: 'f',
Expand All @@ -58,5 +58,15 @@ module.exports = ({ commandProcessor, root }) => {
}
});

commandProcessor.createCommand(device, 'remove', 'Removes a device from a Product', {
params: '<product> <deviceID>',
examples: {
'$0 $command 12345 0123456789abcdef01234567': 'Remove device id `0123456789abcdef01234567` from product `12345`',
},
handler: (args) => {
const ProdCmd = require('../cmd/product');
return new ProdCmd(args).removeDevice(args);
}
});
return product;
};
9 changes: 5 additions & 4 deletions src/cli/product.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,9 @@ describe('Product Command-Line Interface', () => {
'Help: particle help product device <command>',
'',
'Commands:',
' list List all devices that are part of a product',
' add Adds one or more devices into a Product',
' list List all devices that are part of a product',
' add Adds one or more devices into a Product',
' remove Removes a device from a Product',
''
].join(os.EOL));
});
Expand Down Expand Up @@ -104,7 +105,7 @@ describe('Product Command-Line Interface', () => {
it('Parses arguments', () => {
const argv = commandProcessor.parse(root, ['product', 'device', 'add', '12345', '5a8ef38cb85f8720edce631a']);
expect(argv.clierror).to.equal(undefined);
expect(argv.params).to.eql({ product: '12345', device: '5a8ef38cb85f8720edce631a' });
expect(argv.params).to.eql({ product: '12345', deviceID: '5a8ef38cb85f8720edce631a' });
});

it('Errors when required arguments are missing', () => {
Expand All @@ -122,7 +123,7 @@ describe('Product Command-Line Interface', () => {
commandProcessor.showHelp((helpText) => {
expect(helpText).to.equal([
'Adds one or more devices into a Product',
'Usage: particle product device add [options] <product> [device]',
'Usage: particle product device add [options] <product> [deviceID]',
'',
'Options:',
' --file, -f Path to single column .txt file with list of IDs, S/Ns, IMEIs, or ICCIDs of the devices to add [string]',
Expand Down
41 changes: 31 additions & 10 deletions src/cmd/product.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,26 +14,26 @@ module.exports = class ProductCommand extends CLICommandBase {
super(...args);
}

addDevice({ params: { product, device } }){
const identifiers = [device];
const msg = `Adding device ${device} to product ${product}`;
addDevice({ params: { product, deviceID } }){
const identifiers = [deviceID];
const msg = `Adding device ${deviceID} to product ${product}`;
const upload = uploadProductDevices(product, identifiers);
return this.ui.showBusySpinnerUntilResolved(msg, upload)
.then(result => this.showDeviceAddResult(result));
}

addDevices({ file, params: { product, device } }){
if (!device && !file){
addDevices({ file, params: { product, deviceID } }){
if (!deviceID && !file){
throw usageError(
'`device` parameter or `--file` option is required'
'`deviceID` parameter or `--file` option is required'
);
}

if (device){
if (!this.isDeviceId(device)){
return this.showUsageError(`\`device\` parameter must be an id - received: ${device}`);
if (deviceID){
if (!this.isDeviceId(deviceID)){
return this.showUsageError(`\`deviceID\` parameter must be an id - received: ${deviceID}`);
}
return this.addDevice({ params: { product, device } });
return this.addDevice({ params: { product, deviceID } });
}

const msg = `Adding devices in ${file} to product ${product}`;
Expand Down Expand Up @@ -81,6 +81,27 @@ module.exports = class ProductCommand extends CLICommandBase {
return this.ui.write(message.join(os.EOL));
}

removeDevice({ params: { product, deviceID } }){
if (!this.isDeviceId(deviceID)){
return this.showUsageError(`\`deviceID\` parameter must be an id - received: ${deviceID}`);
}

const msg = `Removing device ${deviceID} from product ${product}`;
const remove = createAPI()
.removeDevice(deviceID, product)
.catch(error => {
const message = 'Error removing device from product';
throw createAPIErrorResult({ error, message, json: false });
});

return this.ui.showBusySpinnerUntilResolved(msg, remove)
.then(() => this.showDeviceRemoveResult({ product, deviceID }));
}

showDeviceRemoveResult({ product, deviceID }){
return this.ui.write(`Success! Removed device ${deviceID} from product ${product}${os.EOL}`);
}

showDeviceDetail({ json, params: { product, device } }){
const msg = `Fetching device ${device} detail`;
const fetchData = createAPI().getDeviceAttributes(device, product);
Expand Down
4 changes: 2 additions & 2 deletions test/e2e/help.e2e.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ describe('Help & Unknown Command / Argument Handling', () => {
'library', 'list', 'login', 'logout', 'mesh create', 'mesh add',
'mesh remove', 'mesh list', 'mesh info', 'mesh scan', 'mesh', 'monitor',
'nyan', 'preprocess', 'product device list', 'product device add',
'product device', 'product', 'project create', 'project', 'publish',
'serial list', 'serial monitor', 'serial identify', 'serial wifi',
'product device remove', 'product device', 'product', 'project create',
'project', 'publish', 'serial list', 'serial monitor', 'serial identify', 'serial wifi',
'serial mac', 'serial inspect', 'serial flash', 'serial claim',
'serial', 'setup', 'subscribe', 'token list', 'token revoke',
'token create', 'token', 'udp send', 'udp listen', 'udp', 'update',
Expand Down
93 changes: 80 additions & 13 deletions test/e2e/product.e2e.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,14 +72,14 @@ describe('Product Commands', () => {
// TODO (mirande): sometimes entity includes: `pinned_build_target`
const detailedDeviceFieldNames = ['cellular', 'connected',
'current_build_target', 'default_build_target', 'denied',
'desired_firmware_version', 'development', 'firmware_product_id',
'firmware_updates_enabled', 'firmware_updates_forced',
'firmware_version', 'functions', 'groups', 'iccid', 'id', 'imei',
'last_handshake_at', 'last_heard', 'last_iccid', 'last_ip_address',
'mobile_secret', 'name', 'notes', 'online', 'owner',
'pinned_build_target', 'platform_id', 'product_id', 'quarantined',
'serial_number', 'status', 'system_firmware_version',
'targeted_firmware_release_version', 'variables'];
'development', 'firmware_product_id', 'firmware_updates_enabled',
'firmware_updates_forced', 'firmware_version', 'functions', 'groups',
'iccid', 'id', 'imei', 'last_handshake_at', 'last_heard',
'last_iccid', 'last_ip_address', 'mobile_secret', 'name', 'notes',
'online', 'owner', 'pinned_build_target', 'platform_id',
'product_id', 'quarantined', 'serial_number', 'status',
'system_firmware_version', 'targeted_firmware_release_version',
'variables'];

it('Lists devices', async () => {
const args = ['product', 'device', 'list', PRODUCT_01_ID];
Expand Down Expand Up @@ -283,7 +283,7 @@ describe('Product Commands', () => {
const deviceIDsEmptyFilePath = path.join(PATH_TMP_DIR, 'product-device-ids-empty.txt');
const help = [
'Adds one or more devices into a Product',
'Usage: particle product device add [options] <product> [device]',
'Usage: particle product device add [options] <product> [deviceID]',
'',
'Global Options:',
' -v, --verbose Increases how much logging to display [count]',
Expand Down Expand Up @@ -333,20 +333,20 @@ describe('Product Commands', () => {
expect(exitCode).to.equal(0);
});

it('Fails to add a single device when `device` param or `--file` flag is not provided', async () => {
it('Fails to add a single device when `deviceID` param or `--file` flag is not provided', async () => {
const args = ['product', 'device', 'add', PRODUCT_01_ID];
const { stdout, stderr, exitCode } = await cli.run(args);

expect(stdout).to.include('`device` parameter or `--file` option is required');
expect(stdout).to.include('`deviceID` parameter or `--file` option is required');
expect(stderr.split(os.EOL)).to.include.members(help);
expect(exitCode).to.equal(1);
});

it('Fails to add a single device when `device` param is not an id', async () => {
it('Fails to add a single device when `deviceID` param is not an id', async () => {
const args = ['product', 'device', 'add', PRODUCT_01_ID, PRODUCT_01_DEVICE_01_NAME];
const { stdout, stderr, exitCode } = await cli.run(args);

expect(stdout).to.include(`\`device\` parameter must be an id - received: ${PRODUCT_01_DEVICE_01_NAME}`);
expect(stdout).to.include(`\`deviceID\` parameter must be an id - received: ${PRODUCT_01_DEVICE_01_NAME}`);
expect(stderr.split(os.EOL)).to.include.members(help);
expect(exitCode).to.equal(1);
});
Expand Down Expand Up @@ -440,6 +440,73 @@ describe('Product Commands', () => {
});
});

describe('Device Remove Subcommand', () => {
const help = [
'Removes a device from a Product',
'Usage: particle product device remove [options] <product> <deviceID>',
'',
'Global Options:',
' -v, --verbose Increases how much logging to display [count]',
' -q, --quiet Decreases how much logging to display [count]',
'',
'Examples:',
' particle product device remove 12345 0123456789abcdef01234567 Remove device id `0123456789abcdef01234567` from product `12345`'
];

before(async () => {
await cli.setTestProfileAndLogin();
});

after(async () => {
await cli.run(['product', 'device', 'add', PRODUCT_01_ID, PRODUCT_01_DEVICE_01_ID]);
});

it('Removes a device', async () => {
const args = ['product', 'device', 'remove', PRODUCT_01_ID, PRODUCT_01_DEVICE_01_ID];
const { stdout, stderr, exitCode } = await cli.run(args);

expect(stdout).to.include(`Success! Removed device ${PRODUCT_01_DEVICE_01_ID} from product ${PRODUCT_01_ID}${os.EOL}`);
expect(stderr).to.equal('');
expect(exitCode).to.equal(0);
});

it('Fails to remove a device when `deviceID` param is not provided', async () => {
const args = ['product', 'device', 'remove', PRODUCT_01_ID];
const { stdout, stderr, exitCode } = await cli.run(args);

expect(stdout).to.include('Parameter \'deviceID\' is required.');
expect(stderr.split(os.EOL)).to.include.members(help);
expect(exitCode).to.equal(1);
});

it('Fails to remove a device when `deviceID` param is not an id', async () => {
const args = ['product', 'device', 'remove', PRODUCT_01_ID, PRODUCT_01_DEVICE_01_NAME];
const { stdout, stderr, exitCode } = await cli.run(args);

expect(stdout).to.include(`\`deviceID\` parameter must be an id - received: ${PRODUCT_01_DEVICE_01_NAME}`);
expect(stderr.split(os.EOL)).to.include.members(help);
expect(exitCode).to.equal(1);
});

it('Fails to remove a device when `product` is unknown', async () => {
const args = ['product', 'device', 'remove', 'LOLWUTNOPE', PRODUCT_01_DEVICE_01_ID];
const { stdout, stderr, exitCode } = await cli.run(args);

expect(stdout).to.include('HTTP error 404');
expect(stderr).to.equal('');
expect(exitCode).to.equal(1);
});

it('Fails to remove a device when `device` is unknown', async () => {
const args = ['product', 'device', 'remove', PRODUCT_01_ID, '000000000000000000000001'];
const { stdout, stderr, exitCode } = await cli.run(args);

expect(stdout).to.include('Error removing device from product: Device not found for this product');
expect(stderr).to.equal('');
expect(exitCode).to.equal(1);
});
});

function parseAndSortDeviceList(stdout){
const json = JSON.parse(stdout);

Expand Down

0 comments on commit d7cb656

Please sign in to comment.