From de0b32db4c9df8cfda61275254fd973d30c57408 Mon Sep 17 00:00:00 2001 From: James Hagerman Date: Thu, 23 Apr 2020 01:59:42 -0700 Subject: [PATCH] Moving shared device list filter helper to utilities.js. Adding unit tests for those filters. Adding unit tests and e2e tests to both Cloud List and USB List functionality. --- src/cli/usb.js | 3 +- src/cli/usb.test.js | 6 +- src/cmd/cloud.js | 24 +------- src/cmd/usb.js | 26 +------- src/lib/utilities.js | 33 ++++++++++ src/lib/utilities.test.js | 126 ++++++++++++++++++++++++++++++++++++++ test/e2e/cloud.e2e.js | 121 +++++++++++++++++++++++++++++++++--- test/e2e/usb.e2e.js | 117 ++++++++++++++++++++++++++++++++--- 8 files changed, 394 insertions(+), 62 deletions(-) diff --git a/src/cli/usb.js b/src/cli/usb.js index f515344ae..5a7597fc8 100644 --- a/src/cli/usb.js +++ b/src/cli/usb.js @@ -25,7 +25,8 @@ module.exports = ({ commandProcessor, root }) => { }, handler: (args) => { return usbCommand().list(args); - } + }, + epilogue: 'Param filter can be: online, offline, a platform name (photon, electron, etc), a device ID or name' }); // Common options for start-listening, stop-listening, safe-mode, dfu and reset diff --git a/src/cli/usb.test.js b/src/cli/usb.test.js index 282a4bee3..dd2d1e4fd 100644 --- a/src/cli/usb.test.js +++ b/src/cli/usb.test.js @@ -48,7 +48,7 @@ describe('USB Command-Line Interface', () => { it('Handles `list` command', () => { const argv = commandProcessor.parse(root, ['usb', 'list']); expect(argv.clierror).to.equal(undefined); - expect(argv.params).to.eql({}); + expect(argv.params).to.eql({ filter: undefined }); }); it('Includes help', () => { @@ -56,11 +56,13 @@ describe('USB Command-Line Interface', () => { commandProcessor.showHelp((helpText) => { expect(helpText).to.equal([ 'List the devices connected to the host computer', - 'Usage: particle usb list [options]', + 'Usage: particle usb list [options] [filter]', '', 'Options:', ' --exclude-dfu Do not list devices which are in DFU mode [boolean]', ' --ids-only Print only device IDs [boolean]', + '', + 'Param filter can be: online, offline, a platform name (photon, electron, etc), a device ID or name', '' ].join(os.EOL)); }); diff --git a/src/cmd/cloud.js b/src/cmd/cloud.js index 446589014..1d5324483 100644 --- a/src/cmd/cloud.js +++ b/src/cmd/cloud.js @@ -492,31 +492,11 @@ class CloudCommand { getAllDeviceAttributes(filter) { + const { buildDeviceFilter } = utilities; const api = new ApiClient(); api.ensureToken(); - let filterFunc = null; - - if (filter){ - const platforms = utilities.knownPlatforms(); - if (filter === 'online') { - filterFunc = (d) => { - return d.connected; - }; - } else if (filter === 'offline') { - filterFunc = (d) => { - return !d.connected; - }; - } else if (Object.keys(platforms).indexOf(filter) >= 0) { - filterFunc = (d) => { - return d.product_id === platforms[filter]; - }; - } else { - filterFunc = (d) => { - return d.id === filter || d.name === filter; - }; - } - } + let filterFunc = buildDeviceFilter(filter); return Promise.resolve().then(() => { return api.listDevices(); diff --git a/src/cmd/usb.js b/src/cmd/usb.js index 2ffcfd48e..352ac8269 100644 --- a/src/cmd/usb.js +++ b/src/cmd/usb.js @@ -1,5 +1,5 @@ const { spin } = require('../app/ui'); -const { asyncMapSeries, knownPlatforms } = require('../lib/utilities'); +const { asyncMapSeries, buildDeviceFilter } = require('../lib/utilities'); const { getDevice, formatDeviceInfo } = require('./device-util'); const { getUsbDevices, openUsbDevice, openUsbDeviceById } = require('./usb-util'); const { systemSupportsUdev, udevRulesInstalled, installUdevRules } = require('./udev'); @@ -18,27 +18,7 @@ module.exports = class UsbCommand { const excludeDfu = args['exclude-dfu']; const filter = args.params.filter; - let filterFunc = null; - if (filter){ - const platforms = knownPlatforms(); - if (filter === 'online') { - filterFunc = (d) => { - return d.connected; - }; - } else if (filter === 'offline') { - filterFunc = (d) => { - return !d.connected; - }; - } else if (Object.keys(platforms).indexOf(filter) >= 0) { - filterFunc = (d) => { - return d.platform_id === platforms[filter]; - }; - } else { - filterFunc = (d) => { - return d.id === filter || d.name === filter; - }; - } - } + const filterFunc = buildDeviceFilter(filter); // Enumerate USB devices return getUsbDevices({ dfuMode: !excludeDfu }) @@ -99,7 +79,7 @@ module.exports = class UsbCommand { console.log('No devices found.'); } else { devices = devices.sort((a, b) => a.name.localeCompare(b.name)); // Sort devices by name - + if (filter) { devices = devices.filter(filterFunc); } diff --git a/src/lib/utilities.js b/src/lib/utilities.js index 70c56ecbe..d401e1a70 100644 --- a/src/lib/utilities.js +++ b/src/lib/utilities.js @@ -351,6 +351,39 @@ module.exports = { }; }, + /** + * Generates a filter function to be used with `Array.filter()` when filtering a list of Devices + * by some value. Supports `online`, `offline`, Platform Name, Device ID, or Device Name + * + * @param {string} filter - Filter value to use for filtering a list of devices + * @returns {function|null} + */ + buildDeviceFilter(filter) { + const { knownPlatforms } = module.exports; + let filterFunc = null; + if (filter){ + const platforms = knownPlatforms(); + if (filter === 'online') { + filterFunc = (d) => { + return d.connected; + }; + } else if (filter === 'offline') { + filterFunc = (d) => { + return !d.connected; + }; + } else if (Object.keys(platforms).indexOf(filter) >= 0) { + filterFunc = (d) => { + return d.platform_id === platforms[filter]; + }; + } else { + filterFunc = (d) => { + return d.id === filter || d.name === filter; + }; + } + } + return filterFunc; + }, + ensureError(err){ if (!_.isError(err) && !(err instanceof VError)){ return new Error(_.isArray(err) ? err.join('\n') : err); diff --git a/src/lib/utilities.test.js b/src/lib/utilities.test.js index 546ef4fb2..e3cf3e288 100644 --- a/src/lib/utilities.test.js +++ b/src/lib/utilities.test.js @@ -26,5 +26,131 @@ describe('Utilities', () => { expect(util.compliment(items, excluded)).to.eql(['bar', 'qux']); }); }); + + describe('buildDeviceFilter', () => { + it('returns null if no filter string is provided', () => { + expect(util.buildDeviceFilter()).to.eql(null); + }); + + describe('Built filter functions', () => { + let deviceList; + beforeEach(() => { + deviceList = [ + { + id: 'deadbeef1', + name: 'device-a', + platform_id: 6, + connected: true + }, + { + id: 'deadbeef2', + name: 'device-b', + platform_id: 10, + connected: true + }, + { + id: 'deadbeef3', + name: 'device-c', + platform_id: 13, + connected: false + } + ]; + }); + + it('Filters devices by online', () => { + let filterByOnline = util.buildDeviceFilter('online'); + + let onlineDevices = deviceList.filter(filterByOnline); + + expect(onlineDevices).to.eql([ + { + id: 'deadbeef1', + name: 'device-a', + platform_id: 6, + connected: true + }, + { + id: 'deadbeef2', + name: 'device-b', + platform_id: 10, + connected: true + } + ]); + }); + + it('Filters devices by offline', () => { + let filterByOffline = util.buildDeviceFilter('offline'); + + let offlineDevices = deviceList.filter(filterByOffline); + + expect(offlineDevices).to.eql([ + { + id: 'deadbeef3', + name: 'device-c', + platform_id: 13, + connected: false + } + ]); + }); + + it('Filters devices by platform name', () => { + let electronOnly = util.buildDeviceFilter('electron'); + let boronOnly = util.buildDeviceFilter('boron'); + + let electrons = deviceList.filter(electronOnly); + let borons = deviceList.filter(boronOnly); + + expect(electrons).to.eql([ + { + id: 'deadbeef2', + name: 'device-b', + platform_id: 10, + connected: true + } + ]); + + expect(borons).to.eql([ + { + id: 'deadbeef3', + name: 'device-c', + platform_id: 13, + connected: false + } + ]); + }); + + it('Filters devices by Device ID', () => { + let filterByName = util.buildDeviceFilter('deadbeef2'); + + let matchingDevices = deviceList.filter(filterByName); + + expect(matchingDevices).to.eql([ + { + id: 'deadbeef2', + name: 'device-b', + platform_id: 10, + connected: true + } + ]); + }); + + it('Filters devices by Device Name', () => { + let filterByName = util.buildDeviceFilter('device-a'); + + let matchingDevices = deviceList.filter(filterByName); + + expect(matchingDevices).to.eql([ + { + id: 'deadbeef1', + name: 'device-a', + platform_id: 6, + connected: true + }, + ]); + }); + + + }); + }); }); diff --git a/test/e2e/cloud.e2e.js b/test/e2e/cloud.e2e.js index 0538197a2..b5dc1f9e5 100644 --- a/test/e2e/cloud.e2e.js +++ b/test/e2e/cloud.e2e.js @@ -74,14 +74,121 @@ describe('Cloud Commands [@device]', () => { expect(exitCode).to.equal(0); }); - it('Lists devices', async () => { - const args = ['cloud', 'list']; - const platform = capitalize(DEVICE_PLATFORM_NAME); - const { stdout, stderr, exitCode } = await cli.run(args); + describe('Device Listing', async () => { + let platform, args; + before(async () => { + await cli.revertDeviceName(); + }); + + beforeEach(async () => { + platform = capitalize(DEVICE_PLATFORM_NAME); + args = ['cloud', 'list']; + }); + + it('Lists devices', async () => { + const { stdout, stderr, exitCode } = await cli.run(args); + + expect(stdout).to.include(`${DEVICE_NAME} [${DEVICE_ID}] (${platform})`); + expect(stderr).to.equal(''); + expect(exitCode).to.equal(0); + }); + + describe('Filter by Platform Name', () => { + it('Shows matching devices', async () => { + args.push(DEVICE_PLATFORM_NAME); + const { stdout, stderr, exitCode } = await cli.run(args); + + expect(stdout).to.include(`${DEVICE_NAME} [${DEVICE_ID}] (${platform})`); + expect(stderr).to.equal(''); + expect(exitCode).to.equal(0); + }); + + it('Filters out non-matching devices', async () => { + // Likely platforms being used with e2e testing + const testPlatforms = [ + 'photon', + 'electron', + 'argon', + 'boron' + ]; + + // Derive platform name NOT matching those claimed by E2E test profile + // (i.e. find single, valid inversion of DEVICE_PLATFORM_NAME) + const nonE2EDevicePlatform = testPlatforms.filter((platform) => { + return platform !== DEVICE_PLATFORM_NAME; + })[0]; + + args.push(nonE2EDevicePlatform); + const { stdout, stderr, exitCode } = await cli.run(args); + + expect(stdout).to.not.include(`${DEVICE_NAME} [${DEVICE_ID}] (${platform})`); + expect(stderr).to.equal(''); + expect(exitCode).to.equal(0); + }); + }); + + describe('Filter by Device Name', () => { + it('Shows matching devices', async () => { + args.push(DEVICE_NAME); + const { stdout, stderr, exitCode } = await cli.run(args); + + expect(stdout).to.include(`${DEVICE_NAME} [${DEVICE_ID}] (${platform})`); + expect(stderr).to.equal(''); + expect(exitCode).to.equal(0); + }); + + it('Filters out non-matching devices', async () => { + args.push('non-existent-device-name'); + const { stdout, stderr, exitCode } = await cli.run(args); + + expect(stdout).to.not.include(`${DEVICE_NAME} [${DEVICE_ID}] (${platform})`); + expect(stderr).to.equal(''); + expect(exitCode).to.equal(0); + }); + }); + + describe('Filter by Device ID', () => { + it('Shows matching devices', async () => { + args.push(DEVICE_ID); + const { stdout, stderr, exitCode } = await cli.run(args); + + expect(stdout).to.include(`${DEVICE_NAME} [${DEVICE_ID}] (${platform})`); + expect(stderr).to.equal(''); + expect(exitCode).to.equal(0); + }); + + it('Filters out non-matching devices', async () => { + args.push('non-existent-device-id'); + const { stdout, stderr, exitCode } = await cli.run(args); + + expect(stdout).to.not.include(`${DEVICE_NAME} [${DEVICE_ID}] (${platform})`); + expect(stderr).to.equal(''); + expect(exitCode).to.equal(0); + }); + }); + + describe('Filter by online', () => { + it('Shows online devices', async () => { + args.push('online'); + const { stdout, stderr, exitCode } = await cli.run(args); + + expect(stdout).to.include(`${DEVICE_NAME} [${DEVICE_ID}] (${platform})`); + expect(stderr).to.equal(''); + expect(exitCode).to.equal(0); + }); + }); + + describe('Filter by offline', () => { + it('Does not show online devices', async () => { + args.push('offline'); + const { stdout, stderr, exitCode } = await cli.run(args); + + expect(stdout).to.not.include(`${DEVICE_NAME} [${DEVICE_ID}] (${platform})`); + expect(stderr).to.equal(''); + expect(exitCode).to.equal(0); + }); + }); - expect(stdout).to.include(`${DEVICE_NAME} [${DEVICE_ID}] (${platform})`); - expect(stderr).to.equal(''); - expect(exitCode).to.equal(0); }); it('Compiles firmware', async () => { diff --git a/test/e2e/usb.e2e.js b/test/e2e/usb.e2e.js index 5700f974b..7ca8bc95c 100644 --- a/test/e2e/usb.e2e.js +++ b/test/e2e/usb.e2e.js @@ -65,13 +65,116 @@ describe('USB Commands [@device]', () => { expect(exitCode).to.equal(0); }); - it('Lists connected devices', async () => { - const platform = capitalize(DEVICE_PLATFORM_NAME); - const { stdout, stderr, exitCode } = await cli.run(['usb', 'list']); - - expect(stdout).to.include(`${DEVICE_NAME} [${DEVICE_ID}] (${platform})`); - expect(stderr).to.equal(''); - expect(exitCode).to.equal(0); + describe('Device Listing', async () => { + let platform, args; + beforeEach(() => { + platform = capitalize(DEVICE_PLATFORM_NAME); + args = ['usb', 'list']; + }); + + it('Lists connected devices', async () => { + const { stdout, stderr, exitCode } = await cli.run(args); + + expect(stdout).to.include(`${DEVICE_NAME} [${DEVICE_ID}] (${platform})`); + expect(stderr).to.equal(''); + expect(exitCode).to.equal(0); + }); + + describe('Filter by Platform Name', () => { + it('Shows matching devices', async () => { + args.push(DEVICE_PLATFORM_NAME); + const { stdout, stderr, exitCode } = await cli.run(args); + + expect(stdout).to.include(`${DEVICE_NAME} [${DEVICE_ID}] (${platform})`); + expect(stderr).to.equal(''); + expect(exitCode).to.equal(0); + }); + + it('Filters out non-matching devices', async () => { + // Likely platforms being used with e2e testing + const testPlatforms = [ + 'photon', + 'electron', + 'argon', + 'boron' + ]; + + // Derive platform name NOT matching those claimed by E2E test profile + // (i.e. find single, valid inversion of DEVICE_PLATFORM_NAME) + const nonE2EDevicePlatform = testPlatforms.filter((platform) => { + return platform !== DEVICE_PLATFORM_NAME; + })[0]; + + args.push(nonE2EDevicePlatform); + const { stdout, stderr, exitCode } = await cli.run(args); + + expect(stdout).to.not.include(`${DEVICE_NAME} [${DEVICE_ID}] (${platform})`); + expect(stderr).to.equal(''); + expect(exitCode).to.equal(0); + }); + }); + + describe('Filter by Device Name', () => { + it('Shows matching devices', async () => { + args.push(DEVICE_NAME); + const { stdout, stderr, exitCode } = await cli.run(args); + + expect(stdout).to.include(`${DEVICE_NAME} [${DEVICE_ID}] (${platform})`); + expect(stderr).to.equal(''); + expect(exitCode).to.equal(0); + }); + + it('Filters out non-matching devices', async () => { + args.push('non-existent-device-name'); + const { stdout, stderr, exitCode } = await cli.run(args); + + expect(stdout).to.not.include(`${DEVICE_NAME} [${DEVICE_ID}] (${platform})`); + expect(stderr).to.equal(''); + expect(exitCode).to.equal(0); + }); + }); + + describe('Filter by Device ID', () => { + it('Shows matching devices', async () => { + args.push(DEVICE_ID); + const { stdout, stderr, exitCode } = await cli.run(args); + + expect(stdout).to.include(`${DEVICE_NAME} [${DEVICE_ID}] (${platform})`); + expect(stderr).to.equal(''); + expect(exitCode).to.equal(0); + }); + + it('Filters out non-matching devices', async () => { + args.push('non-existent-device-id'); + const { stdout, stderr, exitCode } = await cli.run(args); + + expect(stdout).to.not.include(`${DEVICE_NAME} [${DEVICE_ID}] (${platform})`); + expect(stderr).to.equal(''); + expect(exitCode).to.equal(0); + }); + }); + + describe('Filter by online', () => { + it('Shows online devices', async () => { + args.push('online'); + const { stdout, stderr, exitCode } = await cli.run(args); + + expect(stdout).to.include(`${DEVICE_NAME} [${DEVICE_ID}] (${platform})`); + expect(stderr).to.equal(''); + expect(exitCode).to.equal(0); + }); + }); + + describe('Filter by offline', () => { + it('Does not show online devices', async () => { + args.push('offline'); + const { stdout, stderr, exitCode } = await cli.run(args); + + expect(stdout).to.not.include(`${DEVICE_NAME} [${DEVICE_ID}] (${platform})`); + expect(stderr).to.equal(''); + expect(exitCode).to.equal(0); + }); + }); }); it('Starts & stops listening', async () => {