etcher/tests/shared/robot.spec.js
Juan Cruz Viotti e4a9a03239 refactor: unify error related tasks (#1154)
The current error handling logic is a mess. We have code that tries to
fetch information about errors in different places throughout the
application, and its incredibly hard to ensure certain types of error
get decent human friendly error messages.

This commit groups, improves, and tests all error related functions in
`lib/shared/errors.js`.

Here's a summary of the changes, in more detail:

- Move the `HUMAN_FRIENDLY` object to `shared/errors.js`
- Extend `HUMAN_FRIENDLY` with error descriptions
- Add `ENOMEM` to `shared/errors.js`
- Group CLI and `OSDialogService` mechanisms for getting an error title
  and an error description
- Move error serialisation routines from `robot` to `shared/errors.js`
- Create and use `createError()` and `createUserError()` utility
  functions
- Add user friendly descriptions to many errors
- Don't report user errors to TrackJS

Fixes: https://github.com/resin-io/etcher/issues/1098
Change-Type: minor
Changelog-Entry: Make errors more user friendly throughout the application.
Signed-off-by: Juan Cruz Viotti <jviotti@openmailbox.org>
2017-03-10 13:11:45 -04:00

257 lines
7.2 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 robot = require('../../lib/shared/robot');
describe('Shared: Robot', function() {
describe('.isEnabled()', function() {
it('should return false if ETCHER_CLI_ROBOT is not set', function() {
m.chai.expect(robot.isEnabled({})).to.be.false;
});
it('should return true if ETCHER_CLI_ROBOT=1', function() {
m.chai.expect(robot.isEnabled({
ETCHER_CLI_ROBOT: 1
})).to.be.true;
});
it('should return false if ETCHER_CLI_ROBOT=0', function() {
m.chai.expect(robot.isEnabled({
ETCHER_CLI_ROBOT: 0
})).to.be.false;
});
it('should return true if ETCHER_CLI_ROBOT="true"', function() {
m.chai.expect(robot.isEnabled({
ETCHER_CLI_ROBOT: 'true'
})).to.be.true;
});
it('should return false if ETCHER_CLI_ROBOT="false"', function() {
m.chai.expect(robot.isEnabled({
ETCHER_CLI_ROBOT: 'false'
})).to.be.false;
});
it('should return true if ETCHER_CLI_ROBOT=true', function() {
m.chai.expect(robot.isEnabled({
ETCHER_CLI_ROBOT: true
})).to.be.true;
});
it('should return false if ETCHER_CLI_ROBOT=false', function() {
m.chai.expect(robot.isEnabled({
ETCHER_CLI_ROBOT: false
})).to.be.false;
});
});
describe('.buildMessage()', function() {
it('should build a message without data', function() {
const message = robot.buildMessage('hello');
const result = '{"command":"hello","data":{}}';
m.chai.expect(message).to.equal(result);
});
it('should build a message with data', function() {
const message = robot.buildMessage('hello', {
foo: 1,
bar: 2
});
const result = '{"command":"hello","data":{"foo":1,"bar":2}}';
m.chai.expect(message).to.equal(result);
});
it('should throw if data is defined but it not an object', function() {
m.chai.expect(() => {
robot.buildMessage('hello', 'world');
}).to.throw('Invalid data: world');
});
});
describe('.buildErrorMessage()', function() {
it('should build a message from a simple error', function() {
const error = new Error('foo');
const message = robot.buildErrorMessage(error);
m.chai.expect(JSON.parse(message)).to.deep.equal({
command: 'error',
data: {
message: 'foo',
stack: error.stack
}
});
});
it('should save the error description', function() {
const error = new Error('foo');
error.description = 'error description';
const message = robot.buildErrorMessage(error);
m.chai.expect(JSON.parse(message)).to.deep.equal({
command: 'error',
data: {
message: 'foo',
description: 'error description',
stack: error.stack
}
});
});
it('should save the error code', function() {
const error = new Error('foo');
error.code = 'MYERROR';
const message = robot.buildErrorMessage(error);
m.chai.expect(JSON.parse(message)).to.deep.equal({
command: 'error',
data: {
message: 'foo',
code: 'MYERROR',
stack: error.stack
}
});
});
it('should handle a string error', function() {
const message = JSON.parse(robot.buildErrorMessage('foo'));
m.chai.expect(message.data.message).to.equal('foo');
m.chai.expect(message.data.stack).to.be.a.string;
m.chai.expect(_.isEmpty(message.data.stack)).to.be.false;
});
});
describe('.parseMessage()', function() {
it('should parse a valid message', function() {
const message = robot.buildMessage('foo', {
bar: 1
});
m.chai.expect(robot.parseMessage(message)).to.deep.equal({
command: 'foo',
data: {
bar: 1
}
});
});
it('should parse a valid without data', function() {
const message = robot.buildMessage('foo');
m.chai.expect(robot.parseMessage(message)).to.deep.equal({
command: 'foo',
data: {}
});
});
it('should throw if input is not valid JSON', function() {
m.chai.expect(() => {
robot.parseMessage('Hello world\nFoo Bar');
}).to.throw('Invalid message');
});
it('should throw if input has no command', function() {
m.chai.expect(() => {
robot.parseMessage('{"data":{"foo":"bar"}}');
}).to.throw('Invalid message');
});
it('should throw if input has no data', function() {
m.chai.expect(() => {
robot.parseMessage('{"command":"foo"}');
}).to.throw('Invalid message');
});
});
describe('.getCommand()', function() {
it('should get the command of a message', function() {
const message = robot.parseMessage(robot.buildMessage('hello', {
foo: 1,
bar: 2
}));
m.chai.expect(robot.getCommand(message)).to.equal('hello');
});
});
describe('.getData()', function() {
it('should get the data of a message', function() {
const message = robot.parseMessage(robot.buildMessage('hello', {
foo: 1,
bar: 2
}));
m.chai.expect(robot.getData(message)).to.deep.equal({
foo: 1,
bar: 2
});
});
it('should return an empty object if the message has no data', function() {
m.chai.expect(robot.getData({
command: 'foo'
})).to.deep.equal({});
});
});
describe('.recomposeErrorMessage()', function() {
it('should return an instance of Error', function() {
const error = new Error('Foo bar');
const message = robot.parseMessage(robot.buildErrorMessage(error));
m.chai.expect(robot.recomposeErrorMessage(message)).to.be.an.instanceof(Error);
});
it('should be able to recompose an error object', function() {
const error = new Error('Foo bar');
const message = robot.parseMessage(robot.buildErrorMessage(error));
m.chai.expect(robot.recomposeErrorMessage(message)).to.deep.equal(error);
});
it('should be able to recompose an error object with a code', function() {
const error = new Error('Foo bar');
error.code = 'FOO';
const message = robot.parseMessage(robot.buildErrorMessage(error));
m.chai.expect(robot.recomposeErrorMessage(message)).to.deep.equal(error);
});
it('should be able to recompose an error object with a description', function() {
const error = new Error('Foo bar');
error.description = 'My description';
const message = robot.parseMessage(robot.buildErrorMessage(error));
m.chai.expect(robot.recomposeErrorMessage(message)).to.deep.equal(error);
});
});
});