/* * Copyright 2016 balena.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. */ import { expect } from 'chai'; import * as _ from 'lodash'; import * as errors from '../../lib/shared/errors'; describe('Shared: Errors', function () { describe('.HUMAN_FRIENDLY', function () { it('should be a plain object', function () { expect(_.isPlainObject(errors.HUMAN_FRIENDLY)).to.be.true; }); it('should contain title and description function properties', function () { expect( _.every( _.map(errors.HUMAN_FRIENDLY, (error) => { return _.isFunction(error.title) && _.isFunction(error.description); }), ), ).to.be.true; }); }); describe('.getTitle()', function () { it('should return a generic error message if the error is an empty object', function () { const error = {}; // @ts-ignore expect(errors.getTitle(error)).to.equal('An error ocurred'); }); it('should return the error message', function () { const error = new Error('This is an error'); 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(); // @ts-ignore error.code = 'MYERROR'; expect(errors.getTitle(error)).to.equal('Error code: MYERROR'); }); it('should prioritize the message over the code', function () { const error = new Error('Foo bar'); // @ts-ignore error.code = 'MYERROR'; expect(errors.getTitle(error)).to.equal('Foo bar'); }); it('should prioritize the code over the message if the message is an empty string', function () { const error = new Error(''); // @ts-ignore error.code = 'MYERROR'; expect(errors.getTitle(error)).to.equal('Error code: MYERROR'); }); it('should prioritize the code over the message if the message is a blank string', function () { const error = new Error(' '); // @ts-ignore error.code = 'MYERROR'; expect(errors.getTitle(error)).to.equal('Error code: MYERROR'); }); it('should understand an error-like object with a code', function () { const error = { code: 'MYERROR', }; // @ts-ignore 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', }; // @ts-ignore 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', }; // @ts-ignore expect(errors.getTitle(error)).to.equal('Hello world'); }); it('should display an error code 0', function () { const error = new Error(); // @ts-ignore error.code = 0; expect(errors.getTitle(error)).to.equal('Error code: 0'); }); it('should display an error code 1', function () { const error = new Error(); // @ts-ignore error.code = 1; expect(errors.getTitle(error)).to.equal('Error code: 1'); }); it('should display an error code -1', function () { const error = new Error(); // @ts-ignore error.code = -1; expect(errors.getTitle(error)).to.equal('Error code: -1'); }); it('should not display an empty string error code', function () { const error = new Error(); // @ts-ignore error.code = ''; expect(errors.getTitle(error)).to.equal('An error ocurred'); }); it('should not display a blank string error code', function () { const error = new Error(); // @ts-ignore error.code = ' '; 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(); 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(''); 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(' '); expect(errors.getTitle(error)).to.equal('An error ocurred'); }); it('should rephrase an ENOENT error', function () { const error = new Error('ENOENT error'); // @ts-ignore error.path = '/foo/bar'; // @ts-ignore error.code = 'ENOENT'; 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'); // @ts-ignore error.code = 'EPERM'; 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'); // @ts-ignore error.code = 'EACCES'; 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'); // @ts-ignore error.code = 'ENOMEM'; expect(errors.getTitle(error)).to.equal('Your system ran out of memory'); }); }); describe('.getDescription()', function () { it('should understand an error-like object with a description', function () { const error = { description: 'My description', }; // @ts-ignore expect(errors.getDescription(error)).to.equal('My description'); }); it('should understand an error-like object with a stack', function () { const error = { stack: 'My stack', }; // @ts-ignore 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', }; // @ts-ignore 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', }; // @ts-ignore 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'); expect(errors.getDescription(error)).to.equal(error.stack); }); it('should prefer a description property to a stack', function () { const error = new Error('Foo'); // @ts-ignore error.description = 'My description'; 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'); // @ts-ignore error.description = ''; 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'); // @ts-ignore error.description = ' '; expect(errors.getDescription(error)).to.equal(error.stack); }); it('should get a generic description for ENOENT', function () { const error = new Error('Foo'); // @ts-ignore error.code = 'ENOENT'; 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'); // @ts-ignore error.code = 'EPERM'; 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'); // @ts-ignore error.code = 'EACCES'; const message = 'Please ensure you have necessary permissions to access this resource'; expect(errors.getDescription(error)).to.equal(message); }); it('should get a generic description for ENOMEM', function () { const error = new Error('Foo'); // @ts-ignore error.code = 'ENOMEM'; const message = 'Please make sure your system has enough available memory for this task'; expect(errors.getDescription(error)).to.equal(message); }); it('should prefer a description property than a code description', function () { const error = new Error('Foo'); // @ts-ignore error.code = 'ENOMEM'; // @ts-ignore error.description = 'Memory error'; 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'); expect(errors.getDescription(error)).to.equal(error.stack); }); it('should return the stack if the description is an empty string', function () { const error = new Error('Foo'); // @ts-ignore error.description = ''; 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'); // @ts-ignore error.description = ' '; expect(errors.getDescription(error)).to.equal(error.stack); }); }); }); describe('.createError()', function () { it('should be an instance of Error', function () { const error = errors.createError({ title: 'Foo', description: 'Something happened', }); expect(error).to.be.an.instanceof(Error); }); it('should correctly add both a title and a description', function () { const error = errors.createError({ title: 'Foo', description: 'Something happened', }); expect(errors.getTitle(error)).to.equal('Foo'); expect(errors.getDescription(error)).to.equal('Something happened'); }); it('should correctly add a code', function () { const error = errors.createError({ title: 'Foo', description: 'Something happened', code: 'ENOENT', }); expect(error.code).to.equal('ENOENT'); }); it('should correctly add only a title', function () { const error = errors.createError({ title: 'Foo', }); expect(errors.getTitle(error)).to.equal('Foo'); expect(errors.getDescription(error)).to.equal(error.stack); }); it('should ignore an empty description', function () { const error = errors.createError({ title: 'Foo', description: '', }); expect(errors.getDescription(error)).to.equal(error.stack); }); it('should ignore a blank description', function () { const error = errors.createError({ title: 'Foo', description: ' ', }); expect(errors.getDescription(error)).to.equal(error.stack); }); it('should throw if no title', function () { expect(() => { // @ts-ignore errors.createError({}); }).to.throw('Invalid error title: undefined'); }); it('should throw if there is a description but no title', function () { expect(() => { // @ts-ignore errors.createError({ description: 'foo', }); }).to.throw('Invalid error title: undefined'); }); it('should throw if title is empty', function () { expect(() => { errors.createError({ title: '', }); }).to.throw('Invalid error title: '); }); it('should throw if title is blank', function () { expect(() => { errors.createError({ title: ' ', }); }).to.throw('Invalid error title: '); }); }); describe('.createUserError()', function () { it('should be an instance of Error', function () { const error = errors.createUserError({ title: 'Foo', description: 'Something happened', }); expect(error).to.be.an.instanceof(Error); }); it('should correctly add both a title and a description', function () { const error = errors.createUserError({ title: 'Foo', description: 'Something happened', }); expect(errors.getTitle(error)).to.equal('Foo'); expect(errors.getDescription(error)).to.equal('Something happened'); }); it('should correctly add only a title', function () { // @ts-ignore const error = errors.createUserError({ title: 'Foo', }); expect(errors.getTitle(error)).to.equal('Foo'); expect(errors.getDescription(error)).to.equal(error.stack); }); it('should correctly add a code', function () { // @ts-ignore const error = errors.createUserError({ title: 'Foo', code: 'ENOENT', }); // @ts-ignore expect(error.code).to.equal('ENOENT'); }); it('should ignore an empty description', function () { const error = errors.createUserError({ title: 'Foo', description: '', }); expect(errors.getDescription(error)).to.equal(error.stack); }); it('should ignore a blank description', function () { const error = errors.createUserError({ title: 'Foo', description: ' ', }); expect(errors.getDescription(error)).to.equal(error.stack); }); it('should throw if no title', function () { expect(() => { // @ts-ignore errors.createUserError({}); }).to.throw('Invalid error title: undefined'); }); it('should throw if title is empty', function () { expect(() => { // @ts-ignore errors.createUserError({ title: '', }); }).to.throw('Invalid error title: '); }); it('should throw if there is a description but no title', function () { expect(() => { // @ts-ignore errors.createUserError({ description: 'foo', }); }).to.throw('Invalid error title: undefined'); }); it('should throw if title is blank', function () { expect(() => { // @ts-ignore errors.createUserError({ title: ' ', }); }).to.throw('Invalid error title: '); }); }); describe('.toJSON()', function () { it('should convert a simple error', function () { const error = new Error('My error'); expect(errors.toJSON(error)).to.deep.equal({ code: undefined, description: undefined, message: 'My error', stack: error.stack, report: undefined, stderr: undefined, stdout: undefined, syscall: undefined, name: 'Error', errno: undefined, device: undefined, }); }); it('should convert an error with a description', function () { const error = new Error('My error'); // @ts-ignore error.description = 'My description'; expect(errors.toJSON(error)).to.deep.equal({ code: undefined, description: 'My description', message: 'My error', stack: error.stack, report: undefined, stderr: undefined, stdout: undefined, syscall: undefined, name: 'Error', errno: undefined, device: undefined, }); }); it('should convert an error with a code', function () { const error = new Error('My error'); // @ts-ignore error.code = 'ENOENT'; expect(errors.toJSON(error)).to.deep.equal({ code: 'ENOENT', description: undefined, message: 'My error', stack: error.stack, report: undefined, stderr: undefined, stdout: undefined, syscall: undefined, name: 'Error', errno: undefined, device: undefined, }); }); it('should convert an error with a description and a code', function () { const error = new Error('My error'); // @ts-ignore error.description = 'My description'; // @ts-ignore error.code = 'ENOENT'; expect(errors.toJSON(error)).to.deep.equal({ code: 'ENOENT', description: 'My description', message: 'My error', stack: error.stack, report: undefined, stderr: undefined, stdout: undefined, syscall: undefined, name: 'Error', errno: undefined, device: undefined, }); }); it('should convert an error with a report value', function () { const error = new Error('My error'); // @ts-ignore error.report = true; expect(errors.toJSON(error)).to.deep.equal({ code: undefined, description: undefined, message: 'My error', stack: error.stack, report: true, stderr: undefined, stdout: undefined, syscall: undefined, name: 'Error', errno: undefined, device: undefined, }); }); it('should convert an error without a message', function () { const error = new Error(); expect(errors.toJSON(error)).to.deep.equal({ code: undefined, description: undefined, message: '', stack: error.stack, report: undefined, stderr: undefined, stdout: undefined, syscall: undefined, name: 'Error', errno: undefined, device: undefined, }); }); }); describe('.fromJSON()', function () { it('should return an Error object', function () { const error = new Error('My error'); const result = errors.fromJSON(errors.toJSON(error)); 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)); expect(result.message).to.equal(error.message); // @ts-ignore expect(result.description).to.equal(error.description); // @ts-ignore expect(result.code).to.equal(error.code); expect(result.stack).to.equal(error.stack); // @ts-ignore expect(result.report).to.equal(error.report); }); it('should convert a JSON error with a description', function () { const error = new Error('My error'); // @ts-ignore error.description = 'My description'; const result = errors.fromJSON(errors.toJSON(error)); expect(result.message).to.equal(error.message); // @ts-ignore expect(result.description).to.equal(error.description); // @ts-ignore expect(result.code).to.equal(error.code); expect(result.stack).to.equal(error.stack); // @ts-ignore expect(result.report).to.equal(error.report); }); it('should convert a JSON error with a code', function () { const error = new Error('My error'); // @ts-ignore error.code = 'ENOENT'; const result = errors.fromJSON(errors.toJSON(error)); expect(result.message).to.equal(error.message); // @ts-ignore expect(result.description).to.equal(error.description); // @ts-ignore expect(result.code).to.equal(error.code); expect(result.stack).to.equal(error.stack); // @ts-ignore expect(result.report).to.equal(error.report); }); it('should convert a JSON error with a report value', function () { const error = new Error('My error'); // @ts-ignore error.report = false; const result = errors.fromJSON(errors.toJSON(error)); expect(result.message).to.equal(error.message); // @ts-ignore expect(result.description).to.equal(error.description); // @ts-ignore expect(result.code).to.equal(error.code); expect(result.stack).to.equal(error.stack); // @ts-ignore expect(result.report).to.equal(error.report); }); }); });