/* * 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('.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 prioritize 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 prioritize 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 prioritize 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 not be a user error', function () { const error = errors.createError({ title: 'Foo', description: 'Something happened' }) m.chai.expect(errors.isUserError(error)).to.be.false }) it('should be a user error if `options.report` is false', function () { const error = errors.createError({ title: 'Foo', description: 'Something happened', report: false }) m.chai.expect(errors.isUserError(error)).to.be.true }) it('should be a user error if `options.report` evaluates to false', function () { const error = errors.createError({ title: 'Foo', description: 'Something happened', report: 0 }) m.chai.expect(errors.isUserError(error)).to.be.true }) it('should not be a user error if `options.report` is true', function () { const error = errors.createError({ title: 'Foo', description: 'Something happened', report: true }) m.chai.expect(errors.isUserError(error)).to.be.false }) it('should not be a user error if `options.report` evaluates to true', function () { const error = errors.createError({ title: 'Foo', description: 'Something happened', report: 1 }) m.chai.expect(errors.isUserError(error)).to.be.false }) it('should be an instance of Error', function () { const error = errors.createError({ title: 'Foo', description: 'Something happened' }) m.chai.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' }) m.chai.expect(errors.getTitle(error)).to.equal('Foo') m.chai.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: 'HELLO' }) m.chai.expect(error.code).to.equal('HELLO') }) 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({ title: 'Foo', description: '' }) m.chai.expect(errors.getDescription(error)).to.equal(error.stack) }) it('should ignore a blank description', function () { const error = errors.createError({ title: 'Foo', description: ' ' }) m.chai.expect(errors.getDescription(error)).to.equal(error.stack) }) it('should throw if no title', function () { m.chai.expect(() => { errors.createError({}) }).to.throw('Invalid error title: undefined') }) it('should throw if there is a description but no title', function () { m.chai.expect(() => { 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 title is blank', function () { m.chai.expect(() => { errors.createError({ title: ' ' }) }).to.throw('Invalid error title: ') }) }) describe('.createUserError()', function () { it('should be a user error', function () { const error = errors.createUserError({ title: 'Foo', description: 'Something happened' }) m.chai.expect(errors.isUserError(error)).to.be.true }) it('should be an instance of Error', function () { const error = errors.createUserError({ title: 'Foo', description: 'Something happened' }) m.chai.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' }) 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 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 correctly add a code', function () { const error = errors.createUserError({ title: 'Foo', code: 'HELLO' }) m.chai.expect(error.code).to.equal('HELLO') }) it('should ignore an empty description', function () { 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({ title: 'Foo', description: ' ' }) m.chai.expect(errors.getDescription(error)).to.equal(error.stack) }) it('should throw if no title', function () { m.chai.expect(() => { errors.createUserError({}) }).to.throw('Invalid error title: undefined') }) it('should throw if title is empty', function () { m.chai.expect(() => { errors.createUserError({ title: '' }) }).to.throw('Invalid error title: ') }) it('should throw if there is a description but no title', function () { m.chai.expect(() => { 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: ') }) }) describe('.isUserError()', function () { _.each([ 0, '', false ], (value) => { it(`should return true if report equals ${value}`, function () { const error = new Error('foo bar') error.report = value m.chai.expect(errors.isUserError(error)).to.be.true }) }) _.each([ undefined, null, true, 1, 3, 'foo' ], (value) => { it(`should return false if report equals ${value}`, function () { const error = new Error('foo bar') error.report = value m.chai.expect(errors.isUserError(error)).to.be.false }) }) }) 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) }) }) })