diff --git a/lib/child-writer/writer-proxy.js b/lib/child-writer/writer-proxy.js index 6f65be21..804e2235 100644 --- a/lib/child-writer/writer-proxy.js +++ b/lib/child-writer/writer-proxy.js @@ -152,7 +152,9 @@ return permissions.isElevated().then((elevated) => { name: packageJSON.displayName }).then((stdout, stderr) => { if (!_.isEmpty(stderr)) { - throw errors.createError(stderr); + throw errors.createError({ + title: stderr + }); } // We're hardcoding internal error messages declared by `sudo-prompt`. @@ -167,10 +169,10 @@ return permissions.isElevated().then((elevated) => { }).catch({ message: 'No polkit authentication agent found.' }, () => { - throw errors.createUserError( - 'No polkit authentication agent found', - 'Please install a polkit authentication agent for your desktop environment of choice to continue' - ); + throw errors.createUserError({ + title: 'No polkit authentication agent found', + description: 'Please install a polkit authentication agent for your desktop environment of choice to continue' + }); }); } diff --git a/lib/cli/etcher.js b/lib/cli/etcher.js index 0923ffa8..da561ed0 100644 --- a/lib/cli/etcher.js +++ b/lib/cli/etcher.js @@ -35,10 +35,10 @@ const imagePath = options._[ARGV_IMAGE_PATH_INDEX]; permissions.isElevated().then((elevated) => { if (!elevated) { - throw errors.createUserError( - messages.error.elevationRequired(), - 'This tool requires special permissions to write to external drives' - ); + throw errors.createUserError({ + title: messages.error.elevationRequired(), + description: 'This tool requires special permissions to write to external drives' + }); } return form.run([ @@ -66,7 +66,10 @@ permissions.isElevated().then((elevated) => { }); }).then((answers) => { if (!answers.yes) { - throw errors.createUserError('Aborted', 'We can\'t proceed without confirmation'); + throw errors.createUserError({ + title: 'Aborted', + description: 'We can\'t proceed without confirmation' + }); } const progressBars = { @@ -80,10 +83,10 @@ permissions.isElevated().then((elevated) => { }); if (!selectedDrive) { - throw errors.createUserError( - 'The selected drive was not found', - `We can't find ${answers.drive} in your system. Did you unplug the drive?` - ); + throw errors.createUserError({ + title: 'The selected drive was not found', + description: `We can't find ${answers.drive} in your system. Did you unplug the drive?` + }); } return writer.writeImage(imagePath, selectedDrive, { diff --git a/lib/cli/options.js b/lib/cli/options.js index e09e6939..bd9ba17f 100644 --- a/lib/cli/options.js +++ b/lib/cli/options.js @@ -93,7 +93,9 @@ module.exports = yargs // Error reporting .fail((message, error) => { - const errorObject = error || errors.createUserError(message); + const errorObject = error || errors.createUserError({ + title: message + }); if (robot.isEnabled(process.env)) { robot.printError(errorObject); @@ -112,7 +114,10 @@ module.exports = yargs try { fs.accessSync(imagePath); } catch (error) { - throw errors.createUserError('Unable to access file', `The image ${imagePath} is not accessible`); + throw errors.createUserError({ + title: 'Unable to access file', + description: `The image ${imagePath} is not accessible` + }); } return true; @@ -120,10 +125,10 @@ module.exports = yargs .check((argv) => { if (robot.isEnabled(process.env) && !argv.drive) { - throw errors.createUserError( - 'Missing drive', - 'You need to explicitly pass a drive when enabling robot mode' - ); + throw errors.createUserError({ + title: 'Missing drive', + description: 'You need to explicitly pass a drive when enabling robot mode' + }); } return true; diff --git a/lib/cli/writer.js b/lib/cli/writer.js index b5d9bd5b..164a0a41 100644 --- a/lib/cli/writer.js +++ b/lib/cli/writer.js @@ -70,10 +70,10 @@ exports.writeImage = (imagePath, drive, options, onProgress) => { }).then((driveFileDescriptor) => { return imageStream.getFromFilePath(imagePath).then((image) => { if (!constraints.isDriveLargeEnough(drive, image)) { - throw errors.createUserError( - 'The image you selected is too big for this drive', - 'Please connect a bigger drive and try again' - ); + throw errors.createUserError({ + title: 'The image you selected is too big for this drive', + description: 'Please connect a bigger drive and try again' + }); } return imageWrite.write({ diff --git a/lib/gui/models/store.js b/lib/gui/models/store.js index 5b6c0028..d50ba02f 100644 --- a/lib/gui/models/store.js +++ b/lib/gui/models/store.js @@ -119,11 +119,15 @@ const storeReducer = (state = DEFAULT_STATE, action) => { case ACTIONS.SET_AVAILABLE_DRIVES: { if (!action.data) { - throw errors.createError('Missing drives'); + throw errors.createError({ + title: 'Missing drives' + }); } if (!_.isArray(action.data) || !_.every(action.data, _.isPlainObject)) { - throw errors.createError(`Invalid drives: ${action.data}`); + throw errors.createError({ + title: `Invalid drives: ${action.data}` + }); } const newState = state.set('availableDrives', Immutable.fromJS(action.data)); @@ -170,35 +174,51 @@ const storeReducer = (state = DEFAULT_STATE, action) => { case ACTIONS.SET_FLASH_STATE: { if (!state.get('isFlashing')) { - throw errors.createError('Can\'t set the flashing state when not flashing'); + throw errors.createError({ + title: 'Can\'t set the flashing state when not flashing' + }); } if (!action.data.type) { - throw errors.createError('Missing state type'); + throw errors.createError({ + title: 'Missing state type' + }); } if (!_.isString(action.data.type)) { - throw errors.createError(`Invalid state type: ${action.data.type}`); + throw errors.createError({ + title: `Invalid state type: ${action.data.type}` + }); } if (_.isNil(action.data.percentage)) { - throw errors.createError('Missing state percentage'); + throw errors.createError({ + title: 'Missing state percentage' + }); } if (!_.isNumber(action.data.percentage)) { - throw errors.createError(`Invalid state percentage: ${action.data.percentage}`); + throw errors.createError({ + title: `Invalid state percentage: ${action.data.percentage}` + }); } if (_.isNil(action.data.eta)) { - throw errors.createError('Missing state eta'); + throw errors.createError({ + title: 'Missing state eta' + }); } if (!_.isNumber(action.data.eta)) { - throw errors.createError(`Invalid state eta: ${action.data.eta}`); + throw errors.createError({ + title: `Invalid state eta: ${action.data.eta}` + }); } if (_.isNil(action.data.speed)) { - throw errors.createError('Missing state speed'); + throw errors.createError({ + title: 'Missing state speed' + }); } return state.set('flashState', Immutable.fromJS(action.data)); @@ -218,7 +238,9 @@ const storeReducer = (state = DEFAULT_STATE, action) => { case ACTIONS.UNSET_FLASHING_FLAG: { if (!action.data) { - throw errors.createError('Missing results'); + throw errors.createError({ + title: 'Missing results' + }); } _.defaults(action.data, { @@ -226,19 +248,27 @@ const storeReducer = (state = DEFAULT_STATE, action) => { }); if (!_.isBoolean(action.data.cancelled)) { - throw errors.createError(`Invalid results cancelled: ${action.data.cancelled}`); + throw errors.createError({ + title: `Invalid results cancelled: ${action.data.cancelled}` + }); } if (action.data.cancelled && action.data.sourceChecksum) { - throw errors.createError('The sourceChecksum value can\'t exist if the flashing was cancelled'); + throw errors.createError({ + title: 'The sourceChecksum value can\'t exist if the flashing was cancelled' + }); } if (action.data.sourceChecksum && !_.isString(action.data.sourceChecksum)) { - throw errors.createError(`Invalid results sourceChecksum: ${action.data.sourceChecksum}`); + throw errors.createError({ + title: `Invalid results sourceChecksum: ${action.data.sourceChecksum}` + }); } if (action.data.errorCode && !_.isString(action.data.errorCode) && !_.isNumber(action.data.errorCode)) { - throw errors.createError(`Invalid results errorCode: ${action.data.errorCode}`); + throw errors.createError({ + title: `Invalid results errorCode: ${action.data.errorCode}` + }); } return state @@ -249,26 +279,36 @@ const storeReducer = (state = DEFAULT_STATE, action) => { case ACTIONS.SELECT_DRIVE: { if (!action.data) { - throw errors.createError('Missing drive'); + throw errors.createError({ + title: 'Missing drive' + }); } if (!_.isString(action.data)) { - throw errors.createError(`Invalid drive: ${action.data}`); + throw errors.createError({ + title: `Invalid drive: ${action.data}` + }); } const selectedDrive = findDrive(state, action.data); if (!selectedDrive) { - throw errors.createError(`The drive is not available: ${action.data}`); + throw errors.createError({ + title: `The drive is not available: ${action.data}` + }); } if (selectedDrive.get('protected')) { - throw errors.createError('The drive is write-protected'); + throw errors.createError({ + title: 'The drive is write-protected' + }); } const image = state.getIn([ 'selection', 'image' ]); if (image && !constraints.isDriveLargeEnough(selectedDrive.toJS(), image.toJS())) { - throw errors.createError('The drive is not large enough'); + throw errors.createError({ + title: 'The drive is not large enough' + }); } return state.setIn([ 'selection', 'drive' ], Immutable.fromJS(action.data)); @@ -276,45 +316,65 @@ const storeReducer = (state = DEFAULT_STATE, action) => { case ACTIONS.SELECT_IMAGE: { if (!action.data.path) { - throw errors.createError('Missing image path'); + throw errors.createError({ + title: 'Missing image path' + }); } if (!_.isString(action.data.path)) { - throw errors.createError(`Invalid image path: ${action.data.path}`); + throw errors.createError({ + title: `Invalid image path: ${action.data.path}` + }); } if (!action.data.size) { - throw errors.createError('Missing image size'); + throw errors.createError({ + title: 'Missing image size' + }); } if (!_.isPlainObject(action.data.size)) { - throw errors.createError(`Invalid image size: ${action.data.size}`); + throw errors.createError({ + title: `Invalid image size: ${action.data.size}` + }); } const MINIMUM_IMAGE_SIZE = 0; if (!_.isInteger(action.data.size.original) || action.data.size.original < MINIMUM_IMAGE_SIZE) { - throw errors.createError(`Invalid original image size: ${action.data.size.original}`); + throw errors.createError({ + title: `Invalid original image size: ${action.data.size.original}` + }); } if (!_.isInteger(action.data.size.final.value) || action.data.size.final.value < MINIMUM_IMAGE_SIZE) { - throw errors.createError(`Invalid final image size: ${action.data.size.final.value}`); + throw errors.createError({ + title: `Invalid final image size: ${action.data.size.final.value}` + }); } if (!_.isBoolean(action.data.size.final.estimation)) { - throw errors.createError(`Invalid final image size estimation flag: ${action.data.size.final.estimation}`); + throw errors.createError({ + title: `Invalid final image size estimation flag: ${action.data.size.final.estimation}` + }); } if (action.data.url && !_.isString(action.data.url)) { - throw errors.createError(`Invalid image url: ${action.data.url}`); + throw errors.createError({ + title: `Invalid image url: ${action.data.url}` + }); } if (action.data.name && !_.isString(action.data.name)) { - throw errors.createError(`Invalid image name: ${action.data.name}`); + throw errors.createError({ + title: `Invalid image name: ${action.data.name}` + }); } if (action.data.logo && !_.isString(action.data.logo)) { - throw errors.createError(`Invalid image logo: ${action.data.logo}`); + throw errors.createError({ + title: `Invalid image logo: ${action.data.logo}` + }); } const selectedDrive = findDrive(state, state.getIn([ 'selection', 'drive' ])); @@ -346,19 +406,27 @@ const storeReducer = (state = DEFAULT_STATE, action) => { const value = action.data.value; if (!key) { - throw errors.createError('Missing setting key'); + throw errors.createError({ + title: 'Missing setting key' + }); } if (!_.isString(key)) { - throw errors.createError(`Invalid setting key: ${key}`); + throw errors.createError({ + title: `Invalid setting key: ${key}` + }); } if (!DEFAULT_STATE.get('settings').has(key)) { - throw errors.createError(`Unsupported setting: ${key}`); + throw errors.createError({ + title: `Unsupported setting: ${key}` + }); } if (_.isObject(value)) { - throw errors.createError(`Invalid setting value: ${value}`); + throw errors.createError({ + title: `Invalid setting value: ${value}` + }); } return state.setIn([ 'settings', key ], value); diff --git a/lib/gui/pages/main/controllers/image-selection.js b/lib/gui/pages/main/controllers/image-selection.js index ce34cb27..3984f6b3 100644 --- a/lib/gui/pages/main/controllers/image-selection.js +++ b/lib/gui/pages/main/controllers/image-selection.js @@ -68,9 +68,12 @@ module.exports = function( */ this.selectImage = (image) => { if (!supportedFormats.isSupportedImage(image.path)) { - const invalidImageError = errors.createUserError('Invalid image', messages.error.invalidImage({ - image - })); + const invalidImageError = errors.createUserError({ + title: 'Invalid image', + description: messages.error.invalidImage({ + image + }) + }); OSDialogService.showError(invalidImageError); AnalyticsService.logEvent('Invalid image', image); diff --git a/lib/gui/utils/manifest-bind/directives/manifest-bind.js b/lib/gui/utils/manifest-bind/directives/manifest-bind.js index 5281b5f4..36fb264a 100644 --- a/lib/gui/utils/manifest-bind/directives/manifest-bind.js +++ b/lib/gui/utils/manifest-bind/directives/manifest-bind.js @@ -41,7 +41,9 @@ module.exports = (ManifestBindService) => { const value = ManifestBindService.get(attributes.manifestBind); if (!value) { - throw errors.createError(`ManifestBind: Unknown property \`${attributes.manifestBind}\``); + throw errors.createError({ + title: `ManifestBind: Unknown property \`${attributes.manifestBind}\`` + }); } element.html(value); diff --git a/lib/image-stream/archive-hooks/zip.js b/lib/image-stream/archive-hooks/zip.js index 6fac610d..590514d2 100644 --- a/lib/image-stream/archive-hooks/zip.js +++ b/lib/image-stream/archive-hooks/zip.js @@ -89,7 +89,9 @@ exports.extractFile = (archive, entries, file) => { if (!_.find(entries, { name: file })) { - throw errors.createError(`Invalid entry: ${file}`); + throw errors.createError({ + title: `Invalid entry: ${file}` + }); } yauzl.openAsync(archive, { diff --git a/lib/image-stream/archive.js b/lib/image-stream/archive.js index 1d5c556c..207e3568 100644 --- a/lib/image-stream/archive.js +++ b/lib/image-stream/archive.js @@ -119,10 +119,10 @@ const extractArchiveMetadata = (archive, basePath, options) => { try { return JSON.parse(manifest); } catch (parseError) { - throw errors.createUserError( - 'Invalid archive manifest.json', - 'The archive manifest.json file is not valid JSON' - ); + throw errors.createUserError({ + title: 'Invalid archive manifest.json', + description: 'The archive manifest.json file is not valid JSON' + }); } }); }) @@ -178,10 +178,10 @@ exports.extractImage = (archive, hooks) => { const VALID_NUMBER_OF_IMAGE_ENTRIES = 1; if (imageEntries.length !== VALID_NUMBER_OF_IMAGE_ENTRIES) { - throw errors.createUserError( - 'Invalid archive image', - 'The archive image should contain one and only one top image file' - ); + throw errors.createUserError({ + title: 'Invalid archive image', + description: 'The archive image should contain one and only one top image file' + }); } const imageEntry = _.first(imageEntries); diff --git a/lib/image-stream/index.js b/lib/image-stream/index.js index 2003da3e..ea526063 100644 --- a/lib/image-stream/index.js +++ b/lib/image-stream/index.js @@ -69,12 +69,18 @@ const errors = require('../shared/errors'); exports.getFromFilePath = (file) => { return fs.statAsync(file).then((fileStats) => { if (!fileStats.isFile()) { - throw errors.createUserError('Invalid image', 'The image must be a file'); + throw errors.createUserError({ + title: 'Invalid image', + description: 'The image must be a file' + }); } return utils.getArchiveMimeType(file).then((type) => { if (!_.has(handlers, type)) { - throw errors.createUserError('Invalid image', `The ${type} format is not supported`); + throw errors.createUserError({ + title: 'Invalid image', + description: `The ${type} format is not supported` + }); } return _.invoke(handlers, type, file, { diff --git a/lib/shared/errors.js b/lib/shared/errors.js index 24c39414..4ae1d0c5 100644 --- a/lib/shared/errors.js +++ b/lib/shared/errors.js @@ -231,23 +231,27 @@ exports.getDescription = (error, options = {}) => { * @function * @public * - * @param {String} title - error title - * @param {String} [description] - error description - * @param {Object} [options] - options + * @param {Object} options - options + * @param {String} options.title - error title + * @param {String} [options.description] - error description * @param {Boolean} [options.report] - report error * @returns {Error} error * * @example - * const error = errors.createError('Foo', 'Bar'); + * const error = errors.createError({ + * title: 'Foo' + * description: 'Bar' + * }); + * * throw error; */ -exports.createError = (title, description, options = {}) => { - if (isBlank(title)) { - throw new Error(`Invalid error title: ${title}`); +exports.createError = (options) => { + if (isBlank(options.title)) { + throw new Error(`Invalid error title: ${options.title}`); } - const error = new Error(title); - error.description = description; + const error = new Error(options.title); + error.description = options.description; if (!_.isNil(options.report) && !options.report) { error.report = false; @@ -267,16 +271,23 @@ exports.createError = (title, description, options = {}) => { * Therefore, user errors don't get reported to analytics * and error reporting services. * - * @param {String} title - error title - * @param {String} [description] - error description + * @param {Object} options - options + * @param {String} options.title - error title + * @param {String} [options.description] - error description * @returns {Error} user error * * @example - * const error = errors.createUserError('Foo', 'Bar'); + * const error = errors.createUserError({ + * title: 'Foo', + * description: 'Bar' + * }); + * * throw error; */ -exports.createUserError = (title, description) => { - return exports.createError(title, description, { +exports.createUserError = (options) => { + return exports.createError({ + title: options.title, + description: options.description, report: false }); }; diff --git a/lib/shared/robot/README.md b/lib/shared/robot/README.md index 8ca8e0b4..2925b27e 100644 --- a/lib/shared/robot/README.md +++ b/lib/shared/robot/README.md @@ -120,7 +120,11 @@ functions: `.printError()` and `.recomposeErrorMessage()`. Here's an example of these functions in action: ```javascript -const error = errors.createError('This is an error', 'My description'); +const error = errors.createError({ + title: 'This is an error', + description: 'My description' +}); + robot.printError(error); ``` diff --git a/lib/shared/robot/index.js b/lib/shared/robot/index.js index aa387dbe..ee130383 100644 --- a/lib/shared/robot/index.js +++ b/lib/shared/robot/index.js @@ -56,7 +56,9 @@ exports.isEnabled = (environment) => { */ exports.buildMessage = (title, data = {}) => { if (!_.isPlainObject(data)) { - throw errors.createError(`Invalid data: ${data}`); + throw errors.createError({ + title: `Invalid data: ${data}` + }); } return JSON.stringify({ @@ -117,11 +119,17 @@ exports.parseMessage = (string) => { try { output = JSON.parse(string); } catch (error) { - throw errors.createError('Invalid message', `${string}, ${error.message}`); + throw errors.createError({ + title: 'Invalid message', + description: `${string}, ${error.message}` + }); } if (!output.command || !output.data) { - throw errors.createError('Invalid message', `No command or data: ${string}`); + throw errors.createError({ + title: 'Invalid message', + description: `No command or data: ${string}` + }); } return output; diff --git a/lib/shared/utils.js b/lib/shared/utils.js index c9e98e3a..8a407099 100644 --- a/lib/shared/utils.js +++ b/lib/shared/utils.js @@ -71,7 +71,9 @@ exports.isValidPercentage = (percentage) => { */ exports.percentageToFloat = (percentage) => { if (!exports.isValidPercentage(percentage)) { - throw errors.createError(`Invalid percentage: ${percentage}`); + throw errors.createError({ + title: `Invalid percentage: ${percentage}` + }); } return percentage / PERCENTAGE_MAXIMUM; diff --git a/tests/shared/errors.spec.js b/tests/shared/errors.spec.js index cc79d8a8..b3d3e3ed 100644 --- a/tests/shared/errors.spec.js +++ b/tests/shared/errors.spec.js @@ -390,12 +390,18 @@ describe('Shared: Errors', function() { describe('.createError()', function() { it('should not set `error.report` by default', function() { - const error = errors.createError('Foo', 'Something happened'); + const error = errors.createError({ + title: 'Foo', + description: 'Something happened' + }); + m.chai.expect(error.report).to.be.undefined; }); it('should set `error.report` to false if `options.report` is false', function() { - const error = errors.createError('Foo', 'Something happened', { + const error = errors.createError({ + title: 'Foo', + description: 'Something happened', report: false }); @@ -403,7 +409,9 @@ describe('Shared: Errors', function() { }); it('should set `error.report` to false if `options.report` evaluates to false', function() { - const error = errors.createError('Foo', 'Something happened', { + const error = errors.createError({ + title: 'Foo', + description: 'Something happened', report: 0 }); @@ -411,47 +419,78 @@ describe('Shared: Errors', function() { }); it('should be an instance of Error', function() { - const error = errors.createError('Foo', 'Something happened'); + const error = errors.createError({ + title: 'Foo', + description: 'Something happened' + }); + m.chai.expect(error).to.be.an.instanceof(Error); }); - it('should correctly add both a message and a description', function() { - const error = errors.createError('Foo', 'Something happened'); + it('should correctly add both a title and a description', function() { + const error = errors.createError({ + title: 'Foo', + description: 'Something happened' + }); + m.chai.expect(errors.getTitle(error)).to.equal('Foo'); m.chai.expect(errors.getDescription(error)).to.equal('Something happened'); }); - it('should correctly add only a message', function() { - const error = errors.createError('Foo'); + it('should correctly add only a title', function() { + const error = errors.createError({ + title: 'Foo' + }); + m.chai.expect(errors.getTitle(error)).to.equal('Foo'); m.chai.expect(errors.getDescription(error)).to.equal(error.stack); }); it('should ignore an empty description', function() { - const error = errors.createError('Foo', ''); + const error = errors.createError({ + title: 'Foo', + description: '' + }); + m.chai.expect(errors.getDescription(error)).to.equal(error.stack); }); it('should ignore a blank description', function() { - const error = errors.createError('Foo', ' '); + const error = errors.createError({ + title: 'Foo', + description: ' ' + }); + m.chai.expect(errors.getDescription(error)).to.equal(error.stack); }); - it('should throw if no message', function() { + it('should throw if no title', function() { m.chai.expect(() => { - errors.createError(); + errors.createError({}); }).to.throw('Invalid error title: undefined'); }); - it('should throw if message is empty', function() { + it('should throw if there is a description but no title', function() { m.chai.expect(() => { - errors.createError(''); + errors.createError({ + description: 'foo' + }); + }).to.throw('Invalid error title: undefined'); + }); + + it('should throw if title is empty', function() { + m.chai.expect(() => { + errors.createError({ + title: '' + }); }).to.throw('Invalid error title: '); }); - it('should throw if message is blank', function() { + it('should throw if title is blank', function() { m.chai.expect(() => { - errors.createError(' '); + errors.createError({ + title: ' ' + }); }).to.throw('Invalid error title: '); }); @@ -460,52 +499,87 @@ describe('Shared: Errors', function() { describe('.createUserError()', function() { it('should set the `report` flag to `false`', function() { - const error = errors.createUserError('Foo', 'Something happened'); + const error = errors.createUserError({ + title: 'Foo', + description: 'Something happened' + }); + m.chai.expect(error.report).to.be.false; }); it('should be an instance of Error', function() { - const error = errors.createUserError('Foo', 'Something happened'); + const error = errors.createUserError({ + title: 'Foo', + description: 'Something happened' + }); + m.chai.expect(error).to.be.an.instanceof(Error); }); - it('should correctly add both a message and a description', function() { - const error = errors.createUserError('Foo', 'Something happened'); + it('should correctly add both a title and a description', function() { + const error = errors.createUserError({ + title: 'Foo', + description: 'Something happened' + }); + m.chai.expect(errors.getTitle(error)).to.equal('Foo'); m.chai.expect(errors.getDescription(error)).to.equal('Something happened'); }); - it('should correctly add only a message', function() { - const error = errors.createUserError('Foo'); + it('should correctly add only a title', function() { + const error = errors.createUserError({ + title: 'Foo' + }); + m.chai.expect(errors.getTitle(error)).to.equal('Foo'); m.chai.expect(errors.getDescription(error)).to.equal(error.stack); }); it('should ignore an empty description', function() { - const error = errors.createUserError('Foo', ''); + const error = errors.createUserError({ + title: 'Foo', + description: '' + }); + m.chai.expect(errors.getDescription(error)).to.equal(error.stack); }); it('should ignore a blank description', function() { - const error = errors.createUserError('Foo', ' '); + const error = errors.createUserError({ + title: 'Foo', + description: ' ' + }); + m.chai.expect(errors.getDescription(error)).to.equal(error.stack); }); - it('should throw if no message', function() { + it('should throw if no title', function() { m.chai.expect(() => { - errors.createUserError(); + errors.createUserError({}); }).to.throw('Invalid error title: undefined'); }); - it('should throw if message is empty', function() { + it('should throw if title is empty', function() { m.chai.expect(() => { - errors.createUserError(''); + errors.createUserError({ + title: '' + }); }).to.throw('Invalid error title: '); }); - it('should throw if message is blank', function() { + it('should throw if there is a description but no title', function() { m.chai.expect(() => { - errors.createUserError(' '); + errors.createUserError({ + description: 'foo' + }); + }).to.throw('Invalid error title: undefined'); + }); + + it('should throw if title is blank', function() { + m.chai.expect(() => { + errors.createUserError({ + title: ' ' + }); }).to.throw('Invalid error title: '); });