etcher/tests/shared/errors.spec.js
Juan Cruz Viotti 57952f6f55 fix(CLI): don't print stack traces by default (#1206)
Currently, the Etcher CLI will print scary stack traces for every single
error (e.g: if you forgot to pass an image to the tool), given that
`errors.getDescription()` will return a stack trace if no other
description could be found.

This commit introduces an `ETCHER_CLI_DEBUG` environment variable, which
when set, it will cause the Etcher CLI to output stack traces, plus a
boolean `userFriendlyDescriptionsOnly` option to
`errors.getDescription()`, so we can control whether
`errors.getDescription()` returns things like stack traces, or
stringified error objects.

Change-Type: minor
Changelog-Entry: Don't print stack traces by default in the CLI.
Signed-off-by: Juan Cruz Viotti <jviotti@openmailbox.org>
2017-03-28 09:43:15 -04:00

743 lines
25 KiB
JavaScript

/*
* Copyright 2016 resin.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
const m = require('mochainon');
const _ = require('lodash');
const errors = require('../../lib/shared/errors');
describe('Shared: Errors', function() {
describe('.HUMAN_FRIENDLY', function() {
it('should be a plain object', function() {
m.chai.expect(_.isPlainObject(errors.HUMAN_FRIENDLY)).to.be.true;
});
it('should contain title and description function properties', function() {
m.chai.expect(_.every(_.map(errors.HUMAN_FRIENDLY, (error) => {
return _.isFunction(error.title) && _.isFunction(error.description);
}))).to.be.true;
});
});
describe('.shouldReport()', function() {
it('should return true for a string error', function() {
const error = 'foo';
m.chai.expect(errors.shouldReport(error)).to.be.true;
});
it('should return true for a number 0 error', function() {
const error = 0;
m.chai.expect(errors.shouldReport(error)).to.be.true;
});
it('should return true for a number 1 error', function() {
const error = 1;
m.chai.expect(errors.shouldReport(error)).to.be.true;
});
it('should return true for a number -1 error', function() {
const error = -1;
m.chai.expect(errors.shouldReport(error)).to.be.true;
});
it('should return true for an array error', function() {
const error = [ 1, 2, 3 ];
m.chai.expect(errors.shouldReport(error)).to.be.true;
});
it('should return true for an undefined error', function() {
const error = undefined;
m.chai.expect(errors.shouldReport(error)).to.be.true;
});
it('should return true for a null error', function() {
const error = null;
m.chai.expect(errors.shouldReport(error)).to.be.true;
});
it('should return true for an empty object error', function() {
const error = {};
m.chai.expect(errors.shouldReport(error)).to.be.true;
});
it('should return true for a basic error', function() {
const error = new Error('foo');
m.chai.expect(errors.shouldReport(error)).to.be.true;
});
it('should return true for an error with a report true property', function() {
const error = new Error('foo');
error.report = true;
m.chai.expect(errors.shouldReport(error)).to.be.true;
});
it('should return false for an error with a report false property', function() {
const error = new Error('foo');
error.report = false;
m.chai.expect(errors.shouldReport(error)).to.be.false;
});
it('should return false for an error with a report undefined property', function() {
const error = new Error('foo');
error.report = undefined;
m.chai.expect(errors.shouldReport(error)).to.be.false;
});
it('should return false for an error with a report null property', function() {
const error = new Error('foo');
error.report = null;
m.chai.expect(errors.shouldReport(error)).to.be.false;
});
it('should return false for an error with a report 0 property', function() {
const error = new Error('foo');
error.report = 0;
m.chai.expect(errors.shouldReport(error)).to.be.false;
});
it('should return true for an error with a report 1 property', function() {
const error = new Error('foo');
error.report = 1;
m.chai.expect(errors.shouldReport(error)).to.be.true;
});
it('should cast the report property to boolean', function() {
const error = new Error('foo');
error.report = '';
m.chai.expect(errors.shouldReport(error)).to.be.false;
});
});
describe('.getTitle()', function() {
it('should accept a string', function() {
const error = 'This is an error';
m.chai.expect(errors.getTitle(error)).to.equal('This is an error');
});
it('should accept a number 0', function() {
const error = 0;
m.chai.expect(errors.getTitle(error)).to.equal('0');
});
it('should accept a number 1', function() {
const error = 1;
m.chai.expect(errors.getTitle(error)).to.equal('1');
});
it('should accept a number -1', function() {
const error = -1;
m.chai.expect(errors.getTitle(error)).to.equal('-1');
});
it('should accept an array', function() {
const error = [ 0, 1, 2 ];
m.chai.expect(errors.getTitle(error)).to.equal('0,1,2');
});
it('should return a generic error message if the error is an empty object', function() {
const error = {};
m.chai.expect(errors.getTitle(error)).to.equal('An error ocurred');
});
it('should return a generic error message if the error is undefined', function() {
const error = undefined;
m.chai.expect(errors.getTitle(error)).to.equal('An error ocurred');
});
it('should return a generic error message if the error is null', function() {
const error = null;
m.chai.expect(errors.getTitle(error)).to.equal('An error ocurred');
});
it('should return the error message', function() {
const error = new Error('This is an error');
m.chai.expect(errors.getTitle(error)).to.equal('This is an error');
});
it('should return the error code if there is no message', function() {
const error = new Error();
error.code = 'MYERROR';
m.chai.expect(errors.getTitle(error)).to.equal('Error code: MYERROR');
});
it('should prioritise the message over the code', function() {
const error = new Error('Foo bar');
error.code = 'MYERROR';
m.chai.expect(errors.getTitle(error)).to.equal('Foo bar');
});
it('should prioritise the code over the message if the message is an empty string', function() {
const error = new Error('');
error.code = 'MYERROR';
m.chai.expect(errors.getTitle(error)).to.equal('Error code: MYERROR');
});
it('should prioritise the code over the message if the message is a blank string', function() {
const error = new Error(' ');
error.code = 'MYERROR';
m.chai.expect(errors.getTitle(error)).to.equal('Error code: MYERROR');
});
it('should understand an error-like object with a code', function() {
const error = {
code: 'MYERROR'
};
m.chai.expect(errors.getTitle(error)).to.equal('Error code: MYERROR');
});
it('should understand an error-like object with a message', function() {
const error = {
message: 'Hello world'
};
m.chai.expect(errors.getTitle(error)).to.equal('Hello world');
});
it('should understand an error-like object with a message and a code', function() {
const error = {
message: 'Hello world',
code: 'MYERROR'
};
m.chai.expect(errors.getTitle(error)).to.equal('Hello world');
});
it('should display an error code 0', function() {
const error = new Error();
error.code = 0;
m.chai.expect(errors.getTitle(error)).to.equal('Error code: 0');
});
it('should display an error code 1', function() {
const error = new Error();
error.code = 1;
m.chai.expect(errors.getTitle(error)).to.equal('Error code: 1');
});
it('should display an error code -1', function() {
const error = new Error();
error.code = -1;
m.chai.expect(errors.getTitle(error)).to.equal('Error code: -1');
});
it('should not display an empty string error code', function() {
const error = new Error();
error.code = '';
m.chai.expect(errors.getTitle(error)).to.equal('An error ocurred');
});
it('should not display a blank string error code', function() {
const error = new Error();
error.code = ' ';
m.chai.expect(errors.getTitle(error)).to.equal('An error ocurred');
});
it('should return a generic error message if no information was found', function() {
const error = new Error();
m.chai.expect(errors.getTitle(error)).to.equal('An error ocurred');
});
it('should return a generic error message if no code and the message is empty', function() {
const error = new Error('');
m.chai.expect(errors.getTitle(error)).to.equal('An error ocurred');
});
it('should return a generic error message if no code and the message is blank', function() {
const error = new Error(' ');
m.chai.expect(errors.getTitle(error)).to.equal('An error ocurred');
});
it('should rephrase an ENOENT error', function() {
const error = new Error('ENOENT error');
error.path = '/foo/bar';
error.code = 'ENOENT';
m.chai.expect(errors.getTitle(error)).to.equal('No such file or directory: /foo/bar');
});
it('should rephrase an EPERM error', function() {
const error = new Error('EPERM error');
error.code = 'EPERM';
m.chai.expect(errors.getTitle(error)).to.equal('You\'re not authorized to perform this operation');
});
it('should rephrase an EACCES error', function() {
const error = new Error('EACCES error');
error.code = 'EACCES';
m.chai.expect(errors.getTitle(error)).to.equal('You don\'t have access to this resource');
});
it('should rephrase an ENOMEM error', function() {
const error = new Error('ENOMEM error');
error.code = 'ENOMEM';
m.chai.expect(errors.getTitle(error)).to.equal('Your system ran out of memory');
});
});
describe('.getDescription()', function() {
it('should return an empty string if the error is a string', function() {
const error = 'My error';
m.chai.expect(errors.getDescription(error)).to.equal('');
});
it('should return an empty string if the error is a number', function() {
const error = 0;
m.chai.expect(errors.getDescription(error)).to.equal('');
});
it('should return an empty string if the error is an array', function() {
const error = [ 1, 2, 3 ];
m.chai.expect(errors.getDescription(error)).to.equal('');
});
it('should return an empty string if the error is undefined', function() {
const error = undefined;
m.chai.expect(errors.getDescription(error)).to.equal('');
});
it('should return an empty string if the error is null', function() {
const error = null;
m.chai.expect(errors.getDescription(error)).to.equal('');
});
it('should return an empty string if the error is an empty object', function() {
const error = {};
m.chai.expect(errors.getDescription(error)).to.equal('');
});
it('should understand an error-like object with a description', function() {
const error = {
description: 'My description'
};
m.chai.expect(errors.getDescription(error)).to.equal('My description');
});
it('should understand an error-like object with a stack', function() {
const error = {
stack: 'My stack'
};
m.chai.expect(errors.getDescription(error)).to.equal('My stack');
});
it('should understand an error-like object with a description and a stack', function() {
const error = {
description: 'My description',
stack: 'My stack'
};
m.chai.expect(errors.getDescription(error)).to.equal('My description');
});
it('should stringify and beautify an object without any known property', function() {
const error = {
name: 'John Doe',
job: 'Developer'
};
m.chai.expect(errors.getDescription(error)).to.equal([
'{',
' "name": "John Doe",',
' "job": "Developer"',
'}'
].join('\n'));
});
it('should return the stack for a basic error', function() {
const error = new Error('Foo');
m.chai.expect(errors.getDescription(error)).to.equal(error.stack);
});
it('should prefer a description property to a stack', function() {
const error = new Error('Foo');
error.description = 'My description';
m.chai.expect(errors.getDescription(error)).to.equal('My description');
});
it('should return the stack if the description is an empty string', function() {
const error = new Error('Foo');
error.description = '';
m.chai.expect(errors.getDescription(error)).to.equal(error.stack);
});
it('should return the stack if the description is a blank string', function() {
const error = new Error('Foo');
error.description = ' ';
m.chai.expect(errors.getDescription(error)).to.equal(error.stack);
});
it('should get a generic description for ENOENT', function() {
const error = new Error('Foo');
error.code = 'ENOENT';
m.chai.expect(errors.getDescription(error)).to.equal('The file you\'re trying to access doesn\'t exist');
});
it('should get a generic description for EPERM', function() {
const error = new Error('Foo');
error.code = 'EPERM';
m.chai.expect(errors.getDescription(error)).to.equal('Please ensure you have necessary permissions for this task');
});
it('should get a generic description for EACCES', function() {
const error = new Error('Foo');
error.code = 'EACCES';
const message = 'Please ensure you have necessary permissions to access this resource';
m.chai.expect(errors.getDescription(error)).to.equal(message);
});
it('should get a generic description for ENOMEM', function() {
const error = new Error('Foo');
error.code = 'ENOMEM';
const message = 'Please make sure your system has enough available memory for this task';
m.chai.expect(errors.getDescription(error)).to.equal(message);
});
it('should prefer a description property than a code description', function() {
const error = new Error('Foo');
error.code = 'ENOMEM';
error.description = 'Memory error';
m.chai.expect(errors.getDescription(error)).to.equal('Memory error');
});
describe('given userFriendlyDescriptionsOnly is false', function() {
it('should return the stack for a basic error', function() {
const error = new Error('Foo');
m.chai.expect(errors.getDescription(error, {
userFriendlyDescriptionsOnly: false
})).to.equal(error.stack);
});
it('should return the stack if the description is an empty string', function() {
const error = new Error('Foo');
error.description = '';
m.chai.expect(errors.getDescription(error, {
userFriendlyDescriptionsOnly: false
})).to.equal(error.stack);
});
it('should return the stack if the description is a blank string', function() {
const error = new Error('Foo');
error.description = ' ';
m.chai.expect(errors.getDescription(error, {
userFriendlyDescriptionsOnly: false
})).to.equal(error.stack);
});
});
describe('given userFriendlyDescriptionsOnly is true', function() {
it('should return an empty string for a basic error', function() {
const error = new Error('Foo');
m.chai.expect(errors.getDescription(error, {
userFriendlyDescriptionsOnly: true
})).to.equal('');
});
it('should return an empty string if the description is an empty string', function() {
const error = new Error('Foo');
error.description = '';
m.chai.expect(errors.getDescription(error, {
userFriendlyDescriptionsOnly: true
})).to.equal('');
});
it('should return an empty string if the description is a blank string', function() {
const error = new Error('Foo');
error.description = ' ';
m.chai.expect(errors.getDescription(error, {
userFriendlyDescriptionsOnly: true
})).to.equal('');
});
});
});
describe('.createError()', function() {
it('should report the resulting error by default', function() {
const error = errors.createError('Foo', 'Something happened');
m.chai.expect(errors.shouldReport(error)).to.be.true;
});
it('should not report the error if report is false', function() {
const error = errors.createError('Foo', 'Something happened', {
report: false
});
m.chai.expect(errors.shouldReport(error)).to.be.false;
});
it('should not report the error if report evaluates to false', function() {
const error = errors.createError('Foo', 'Something happened', {
report: 0
});
m.chai.expect(errors.shouldReport(error)).to.be.false;
});
it('should be an instance of Error', function() {
const error = errors.createError('Foo', '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');
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');
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', '');
m.chai.expect(errors.getDescription(error)).to.equal(error.stack);
});
it('should ignore a blank description', function() {
const error = errors.createError('Foo', ' ');
m.chai.expect(errors.getDescription(error)).to.equal(error.stack);
});
it('should throw if no message', function() {
m.chai.expect(() => {
errors.createError();
}).to.throw('Invalid error title: undefined');
});
it('should throw if message is empty', function() {
m.chai.expect(() => {
errors.createError('');
}).to.throw('Invalid error title: ');
});
it('should throw if message is blank', function() {
m.chai.expect(() => {
errors.createError(' ');
}).to.throw('Invalid error title: ');
});
});
describe('.createUserError()', function() {
it('should not report the resulting error', function() {
const error = errors.createUserError('Foo', 'Something happened');
m.chai.expect(errors.shouldReport(error)).to.be.false;
});
it('should be an instance of Error', function() {
const error = errors.createUserError('Foo', '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');
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');
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', '');
m.chai.expect(errors.getDescription(error)).to.equal(error.stack);
});
it('should ignore a blank description', function() {
const error = errors.createUserError('Foo', ' ');
m.chai.expect(errors.getDescription(error)).to.equal(error.stack);
});
it('should throw if no message', function() {
m.chai.expect(() => {
errors.createUserError();
}).to.throw('Invalid error title: undefined');
});
it('should throw if message is empty', function() {
m.chai.expect(() => {
errors.createUserError('');
}).to.throw('Invalid error title: ');
});
it('should throw if message is blank', function() {
m.chai.expect(() => {
errors.createUserError(' ');
}).to.throw('Invalid error title: ');
});
});
describe('.toJSON()', function() {
it('should convert a simple error', function() {
const error = new Error('My error');
m.chai.expect(errors.toJSON(error)).to.deep.equal({
code: undefined,
description: undefined,
message: 'My error',
stack: error.stack,
report: undefined
});
});
it('should convert an error with a description', function() {
const error = new Error('My error');
error.description = 'My description';
m.chai.expect(errors.toJSON(error)).to.deep.equal({
code: undefined,
description: 'My description',
message: 'My error',
stack: error.stack,
report: undefined
});
});
it('should convert an error with a code', function() {
const error = new Error('My error');
error.code = 'ENOENT';
m.chai.expect(errors.toJSON(error)).to.deep.equal({
code: 'ENOENT',
description: undefined,
message: 'My error',
stack: error.stack,
report: undefined
});
});
it('should convert an error with a description and a code', function() {
const error = new Error('My error');
error.description = 'My description';
error.code = 'ENOENT';
m.chai.expect(errors.toJSON(error)).to.deep.equal({
code: 'ENOENT',
description: 'My description',
message: 'My error',
stack: error.stack,
report: undefined
});
});
it('should convert an error with a report value', function() {
const error = new Error('My error');
error.report = true;
m.chai.expect(errors.toJSON(error)).to.deep.equal({
code: undefined,
description: undefined,
message: 'My error',
stack: error.stack,
report: true
});
});
it('should convert an error without a message', function() {
const error = new Error();
m.chai.expect(errors.toJSON(error)).to.deep.equal({
code: undefined,
description: undefined,
message: '',
stack: error.stack,
report: undefined
});
});
});
describe('.fromJSON()', function() {
it('should return an Error object', function() {
const error = new Error('My error');
const result = errors.fromJSON(errors.toJSON(error));
m.chai.expect(result).to.be.an.instanceof(Error);
});
it('should convert a simple JSON error', function() {
const error = new Error('My error');
const result = errors.fromJSON(errors.toJSON(error));
m.chai.expect(result.message).to.equal(error.message);
m.chai.expect(result.description).to.equal(error.description);
m.chai.expect(result.code).to.equal(error.code);
m.chai.expect(result.stack).to.equal(error.stack);
m.chai.expect(result.report).to.equal(error.report);
});
it('should convert a JSON error with a description', function() {
const error = new Error('My error');
error.description = 'My description';
const result = errors.fromJSON(errors.toJSON(error));
m.chai.expect(result.message).to.equal(error.message);
m.chai.expect(result.description).to.equal(error.description);
m.chai.expect(result.code).to.equal(error.code);
m.chai.expect(result.stack).to.equal(error.stack);
m.chai.expect(result.report).to.equal(error.report);
});
it('should convert a JSON error with a code', function() {
const error = new Error('My error');
error.code = 'ENOENT';
const result = errors.fromJSON(errors.toJSON(error));
m.chai.expect(result.message).to.equal(error.message);
m.chai.expect(result.description).to.equal(error.description);
m.chai.expect(result.code).to.equal(error.code);
m.chai.expect(result.stack).to.equal(error.stack);
m.chai.expect(result.report).to.equal(error.report);
});
it('should convert a JSON error with a report value', function() {
const error = new Error('My error');
error.report = false;
const result = errors.fromJSON(errors.toJSON(error));
m.chai.expect(result.message).to.equal(error.message);
m.chai.expect(result.description).to.equal(error.description);
m.chai.expect(result.code).to.equal(error.code);
m.chai.expect(result.stack).to.equal(error.stack);
m.chai.expect(result.report).to.equal(error.report);
});
});
});