| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413 |
- const { expect } = require('chai')
- const { promisify } = require('util')
- const OError = require('..')
- const {
- expectError,
- expectFullStackWithoutStackFramesToEqual,
- } = require('./support')
- describe('utils', function () {
- describe('OError.tag', function () {
- it('tags errors thrown from an async function', async function () {
- const delay = promisify(setTimeout)
- async function foo() {
- await delay(10)
- throw new Error('foo error')
- }
- async function bar() {
- try {
- await foo()
- } catch (error) {
- throw OError.tag(error, 'failed to bar', { bar: 'baz' })
- }
- }
- async function baz() {
- try {
- await bar()
- } catch (error) {
- throw OError.tag(error, 'failed to baz', { baz: 'bat' })
- }
- }
- try {
- await baz()
- expect.fail('should have thrown')
- } catch (error) {
- expectError(error, {
- name: 'Error',
- klass: Error,
- message: 'Error: foo error',
- firstFrameRx: /at foo/,
- })
- expectFullStackWithoutStackFramesToEqual(error, [
- 'Error: foo error',
- 'TaggedError: failed to bar',
- 'TaggedError: failed to baz',
- ])
- expect(OError.getFullInfo(error)).to.eql({
- bar: 'baz',
- baz: 'bat',
- })
- }
- })
- it('tags errors thrown from a promise rejection', async function () {
- function foo() {
- return new Promise((resolve, reject) => {
- setTimeout(() => {
- reject(new Error('foo error'))
- }, 10)
- })
- }
- async function bar() {
- try {
- await foo()
- } catch (error) {
- throw OError.tag(error, 'failed to bar', { bar: 'baz' })
- }
- }
- async function baz() {
- try {
- await bar()
- } catch (error) {
- throw OError.tag(error, 'failed to baz', { baz: 'bat' })
- }
- }
- try {
- await baz()
- expect.fail('should have thrown')
- } catch (error) {
- expectError(error, {
- name: 'Error',
- klass: Error,
- message: 'Error: foo error',
- firstFrameRx: /_onTimeout/,
- })
- expectFullStackWithoutStackFramesToEqual(error, [
- 'Error: foo error',
- 'TaggedError: failed to bar',
- 'TaggedError: failed to baz',
- ])
- expect(OError.getFullInfo(error)).to.eql({
- bar: 'baz',
- baz: 'bat',
- })
- }
- })
- it('tags errors yielded through callbacks', function (done) {
- function foo(cb) {
- setTimeout(() => {
- cb(new Error('foo error'))
- }, 10)
- }
- function bar(cb) {
- foo(err => {
- if (err) {
- return cb(OError.tag(err, 'failed to bar', { bar: 'baz' }))
- }
- cb()
- })
- }
- function baz(cb) {
- bar(err => {
- if (err) {
- return cb(OError.tag(err, 'failed to baz', { baz: 'bat' }))
- }
- cb()
- })
- }
- baz(err => {
- if (err) {
- expectError(err, {
- name: 'Error',
- klass: Error,
- message: 'Error: foo error',
- firstFrameRx: /_onTimeout/,
- })
- expectFullStackWithoutStackFramesToEqual(err, [
- 'Error: foo error',
- 'TaggedError: failed to bar',
- 'TaggedError: failed to baz',
- ])
- expect(OError.getFullInfo(err)).to.eql({
- bar: 'baz',
- baz: 'bat',
- })
- return done()
- }
- expect.fail('should have yielded an error')
- })
- })
- it('is not included in the stack trace if using capture', function () {
- if (!Error.captureStackTrace) return this.skip()
- const err = new Error('test error')
- OError.tag(err, 'test message')
- const stack = OError.getFullStack(err)
- expect(stack).to.match(/TaggedError: test message\n\s+at/)
- expect(stack).to.not.match(/TaggedError: test message\n\s+at [\w.]*tag/)
- })
- describe('without Error.captureStackTrace', function () {
- /* eslint-disable mocha/no-hooks-for-single-case */
- before(function () {
- this.originalCaptureStackTrace = Error.captureStackTrace
- Error.captureStackTrace = null
- })
- after(function () {
- Error.captureStackTrace = this.originalCaptureStackTrace
- })
- it('still captures a stack trace, albeit including itself', function () {
- const err = new Error('test error')
- OError.tag(err, 'test message')
- expectFullStackWithoutStackFramesToEqual(err, [
- 'Error: test error',
- 'TaggedError: test message',
- ])
- const stack = OError.getFullStack(err)
- expect(stack).to.match(/TaggedError: test message\n\s+at [\w.]*tag/)
- })
- })
- describe('with limit on the number of tags', function () {
- before(function () {
- this.originalMaxTags = OError.maxTags
- OError.maxTags = 3
- })
- after(function () {
- OError.maxTags = this.originalMaxTags
- })
- it('should not tag more than that', function () {
- const err = new Error('test error')
- OError.tag(err, 'test message 1')
- OError.tag(err, 'test message 2')
- OError.tag(err, 'test message 3')
- OError.tag(err, 'test message 4')
- OError.tag(err, 'test message 5')
- expectFullStackWithoutStackFramesToEqual(err, [
- 'Error: test error',
- 'TaggedError: test message 1',
- 'TaggedError: ... dropped tags',
- 'TaggedError: test message 4',
- 'TaggedError: test message 5',
- ])
- })
- it('should handle deep recursion', async function () {
- async function recursiveAdd(n) {
- try {
- if (n === 0) throw new Error('deep error')
- const result = await recursiveAdd(n - 1)
- return result + 1
- } catch (err) {
- throw OError.tag(err, `at level ${n}`)
- }
- }
- try {
- await recursiveAdd(10)
- } catch (err) {
- expectFullStackWithoutStackFramesToEqual(err, [
- 'Error: deep error',
- 'TaggedError: at level 0',
- 'TaggedError: ... dropped tags',
- 'TaggedError: at level 9',
- 'TaggedError: at level 10',
- ])
- }
- })
- it('should handle a singleton error', function (done) {
- const err = new Error('singleton error')
- function endpoint(callback) {
- helper(err => callback(err && OError.tag(err, 'in endpoint')))
- }
- function helper(callback) {
- libraryFunction(err => callback(err && OError.tag(err, 'in helper')))
- }
- function libraryFunction(callback) {
- callback(err)
- }
- endpoint(() => {
- endpoint(err => {
- expect(err).to.exist
- expectFullStackWithoutStackFramesToEqual(err, [
- 'Error: singleton error',
- 'TaggedError: in helper',
- 'TaggedError: ... dropped tags',
- 'TaggedError: in helper',
- 'TaggedError: in endpoint',
- ])
- done()
- })
- })
- })
- })
- })
- describe('OError.getFullInfo', function () {
- it('works when given null', function () {
- expect(OError.getFullInfo(null)).to.deep.equal({})
- })
- it('works on a normal error', function () {
- const err = new Error('foo')
- expect(OError.getFullInfo(err)).to.deep.equal({})
- })
- it('works on an error with tags', function () {
- const err = OError.tag(new Error('foo'), 'bar', { userId: 123 })
- expect(OError.getFullInfo(err)).to.deep.equal({ userId: 123 })
- })
- it('merges info from an error and its tags', function () {
- const err = new OError('foo').withInfo({ projectId: 456 })
- OError.tag(err, 'failed to foo', { userId: 123 })
- expect(OError.getFullInfo(err)).to.deep.equal({
- projectId: 456,
- userId: 123,
- })
- })
- it('merges info from a cause', function () {
- const err1 = new Error('foo')
- const err2 = new Error('bar')
- err1.cause = err2
- err2.info = { userId: 123 }
- expect(OError.getFullInfo(err1)).to.deep.equal({ userId: 123 })
- })
- it('merges info from a nested cause', function () {
- const err1 = new Error('foo')
- const err2 = new Error('bar')
- const err3 = new Error('baz')
- err1.cause = err2
- err2.info = { userId: 123 }
- err2.cause = err3
- err3.info = { foo: 42 }
- expect(OError.getFullInfo(err1)).to.deep.equal({
- userId: 123,
- foo: 42,
- })
- })
- it('merges info from cause with duplicate keys', function () {
- const err1 = new Error('foo')
- const err2 = new Error('bar')
- err1.info = { userId: 42, foo: 1337 }
- err1.cause = err2
- err2.info = { userId: 1 }
- expect(OError.getFullInfo(err1)).to.deep.equal({
- userId: 42,
- foo: 1337,
- })
- })
- it('merges info from tags with duplicate keys', function () {
- const err1 = OError.tag(new Error('foo'), 'bar', { userId: 123 })
- const err2 = OError.tag(err1, 'bat', { userId: 456 })
- expect(OError.getFullInfo(err2)).to.deep.equal({ userId: 456 })
- })
- it('works on an error with .info set to a string', function () {
- const err = new Error('foo')
- err.info = 'test'
- expect(OError.getFullInfo(err)).to.deep.equal({})
- })
- })
- describe('OError.getFullStack', function () {
- it('works when given null', function () {
- expect(OError.getFullStack(null)).to.equal('')
- })
- it('works on a normal error', function () {
- const err = new Error('foo')
- const fullStack = OError.getFullStack(err)
- expect(fullStack).to.match(/^Error: foo$/m)
- expect(fullStack).to.match(/^\s+at /m)
- })
- it('works on an error with a cause', function () {
- const err1 = new Error('foo')
- const err2 = new Error('bar')
- err1.cause = err2
- const fullStack = OError.getFullStack(err1)
- expect(fullStack).to.match(/^Error: foo$/m)
- expect(fullStack).to.match(/^\s+at /m)
- expect(fullStack).to.match(/^caused by:\n\s+Error: bar$/m)
- })
- it('works on both tags and causes', async function () {
- // Here's the actual error.
- function tryToFoo() {
- try {
- throw Error('foo')
- } catch (error) {
- throw OError.tag(error, 'failed to foo', { foo: 1 })
- }
- }
- // Inside another function that wraps it.
- function tryToBar() {
- try {
- tryToFoo()
- } catch (error) {
- throw new OError('failed to bar').withCause(error)
- }
- }
- // And it is in another try.
- try {
- try {
- tryToBar()
- expect.fail('should have thrown')
- } catch (error) {
- throw OError.tag(error, 'failed to bat', { bat: 1 })
- }
- } catch (error) {
- // We catch the wrapping error.
- expectError(error, {
- name: 'OError',
- klass: OError,
- message: 'OError: failed to bar',
- firstFrameRx: /tryToBar/,
- })
- // But the stack contains all of the errors and tags.
- expectFullStackWithoutStackFramesToEqual(error, [
- 'OError: failed to bar',
- 'TaggedError: failed to bat',
- 'caused by:',
- ' Error: foo',
- ' TaggedError: failed to foo',
- ])
- // The info from the wrapped cause should be picked up for logging.
- expect(OError.getFullInfo(error)).to.eql({ bat: 1, foo: 1 })
- // But it should still be recorded.
- expect(OError.getFullInfo(error.cause)).to.eql({ foo: 1 })
- }
- })
- })
- })
|