diff --git a/src/cmd/serial.js b/src/cmd/serial.js index f0a3aa87a..a29e6bf8c 100644 --- a/src/cmd/serial.js +++ b/src/cmd/serial.js @@ -31,6 +31,7 @@ function protip(){ console.log.apply(null, args); } + // An LTE device may take up to 18 seconds to power up the modem const MODULE_INFO_COMMAND_TIMEOUT = 20000; const IDENTIFY_COMMAND_TIMEOUT = 20000; @@ -897,7 +898,7 @@ module.exports = class SerialCommand { } function startTimeout(to){ - self._serialTimeout = setTimeout(() => reject('Serial timed out'), to); + self._serialTimeout = setTimeout(() => reject(timeoutError), to); } function resetTimeout(){ @@ -919,8 +920,21 @@ module.exports = class SerialCommand { return promise.finally(cleanUpFn); } + /** + * This is a wrapper function so _serialWifiConfig can return the + * true promise state for testing. + */ + serialWifiConfig(...args) { + return this._serialWifiConfig(...args) + .then(() => { + console.log('Done! Your device should now restart.'); + }, (err) => { + log.error('Something went wrong:', err); + }); + } + /* eslint-disable max-statements */ - serialWifiConfig(device, opts = {}){ + _serialWifiConfig(device, opts = {}){ if (!device){ return Promise.reject('No serial port available'); } @@ -1204,15 +1218,18 @@ module.exports = class SerialCommand { serialTrigger.start(true); serialPort.write('w'); serialPort.drain(); + + // In case device is not in listening mode. + startTimeout(5000, 'Serial timed out while initially listening to device, please ensure device is in listening mode with particle usb start-listening', 'InitialTimeoutError'); }); function serialClosedEarly(){ reject('Serial port closed early'); } - function startTimeout(to){ + function startTimeout(to, message = timeoutError, name = 'TimeoutError'){ self._serialTimeout = setTimeout(() => { - reject('Serial timed out'); + reject(VError({name}, message)); }, to); } @@ -1275,13 +1292,7 @@ module.exports = class SerialCommand { } }); - return promise - .then(() => { - console.log('Done! Your device should now restart.'); - }, (err) => { - log.error('Something went wrong:', err); - }) - .finally(cleanUpFn); + return promise.finally(cleanUpFn); } /* eslint-enable max-statements */ diff --git a/src/cmd/serial.test.js b/src/cmd/serial.test.js index 2ae8ae568..e0505d9f9 100644 --- a/src/cmd/serial.test.js +++ b/src/cmd/serial.test.js @@ -1,15 +1,23 @@ const MockSerial = require('../../test/__mocks__/serial.mock'); -const { expect } = require('../../test/setup'); +const { expect, sinon } = require('../../test/setup'); const SerialCommand = require('./serial'); describe('Serial Command', () => { let serial; + let clock; beforeEach(() => { serial = new SerialCommand({ params: {} }); }); + afterEach(() => { + if (clock !== undefined) { + clock.restore(); + clock = undefined; + } + }); + describe('supportsClaimCode', () => { it('can check if a device supports claiming', () => { var device = { port: 'vintage' }; @@ -53,5 +61,36 @@ describe('Serial Command', () => { }); }); }); + + describe('serialWifiConfig', async () => { + it('can reject with timeout after 5000ms if not getting any serial data', async () => { + clock = sinon.useFakeTimers(); + const device = { port: 'baltimore' }; + const mockSerial = new MockSerial(); + + mockSerial.write = function write(data) { + if (data === 'w') { + // This next tick allows _serialWifiConfig to set the timeout before we move the clock foward. + process.nextTick(() => { + clock.tick(5010); + }); + } + }; + + serial.serialPort = mockSerial; + + + let error; + + try { + await serial._serialWifiConfig(device); + } catch (e) { + error = e; + } + + expect(error).to.be.an.instanceOf(Error); + expect(error).to.have.property('name', 'InitialTimeoutError'); + }); + }); }); diff --git a/test/__mocks__/serial.mock.js b/test/__mocks__/serial.mock.js index 380da1385..f7343af1d 100644 --- a/test/__mocks__/serial.mock.js +++ b/test/__mocks__/serial.mock.js @@ -15,7 +15,7 @@ MockSerial.prototype.write = function write(chunk) { this.data += chunk; }; -MockSerial.prototype.drain = function drain(cb) { +MockSerial.prototype.drain = function drain(cb = () => {}) { this.emit('drain'); process.nextTick(cb); };