|
| 1 | +/** |
| 2 | + * Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. |
| 3 | + */ |
| 4 | + |
| 5 | +'use strict'; |
| 6 | + |
| 7 | +require('should'); |
| 8 | +const { formatted } = require('lambda-runtime/XRayError.js'); |
| 9 | + |
| 10 | +describe('XRayFormattedCause', () => { |
| 11 | + it('should handle a basic error with stack trace', () => { |
| 12 | + const error = new Error('Something went wrong'); |
| 13 | + error.name = 'CustomError'; |
| 14 | + error.stack = `CustomError: Something went wrong |
| 15 | + at someFunction (/var/task/handler.js:10:15)`; |
| 16 | + |
| 17 | + const result = JSON.parse(formatted(error)); |
| 18 | + result.should.have.property('working_directory').which.is.a.String(); |
| 19 | + result.should.have.property('exceptions').with.length(1); |
| 20 | + result.exceptions[0].should.have.property('type', 'CustomError'); |
| 21 | + result.exceptions[0].should.have.property( |
| 22 | + 'message', |
| 23 | + 'Something went wrong', |
| 24 | + ); |
| 25 | + result.exceptions[0].stack.should.deepEqual([ |
| 26 | + { |
| 27 | + path: '/var/task/handler.js', |
| 28 | + line: 10, |
| 29 | + label: 'someFunction', |
| 30 | + }, |
| 31 | + ]); |
| 32 | + result.paths.should.deepEqual(['/var/task/handler.js']); |
| 33 | + }); |
| 34 | + |
| 35 | + it('should handle an error without stack trace', () => { |
| 36 | + const error = new Error('No stack here'); |
| 37 | + error.name = 'NoStackError'; |
| 38 | + error.stack = null; |
| 39 | + |
| 40 | + const result = JSON.parse(formatted(error)); |
| 41 | + result.exceptions[0].should.have.property('stack').with.length(0); |
| 42 | + result.paths.should.eql([]); |
| 43 | + }); |
| 44 | + |
| 45 | + it('should handle multiple stack frames', () => { |
| 46 | + const error = new Error('Complex error'); |
| 47 | + error.name = 'ComplexError'; |
| 48 | + error.stack = `ComplexError: Complex error |
| 49 | + at firstFunction (/var/task/one.js:1:100) |
| 50 | + at secondFunction (/var/task/two.js:2:200) |
| 51 | + at /var/task/three.js:3:300`; |
| 52 | + |
| 53 | + const result = JSON.parse(formatted(error)); |
| 54 | + result.exceptions[0].stack.should.deepEqual([ |
| 55 | + { path: '/var/task/one.js', line: 1, label: 'firstFunction' }, |
| 56 | + { path: '/var/task/two.js', line: 2, label: 'secondFunction' }, |
| 57 | + { path: '/var/task/three.js', line: 3, label: 'anonymous' }, |
| 58 | + ]); |
| 59 | + result.paths.should.eql([ |
| 60 | + '/var/task/one.js', |
| 61 | + '/var/task/two.js', |
| 62 | + '/var/task/three.js', |
| 63 | + ]); |
| 64 | + }); |
| 65 | + |
| 66 | + it('should encode invalid characters in name and message', () => { |
| 67 | + const error = new Error('\x7Fmessage'); |
| 68 | + error.name = 'Name\x7F'; |
| 69 | + |
| 70 | + error.stack = `Name\x7F: \x7Fmessage |
| 71 | + at anon (/var/task/bad.js:99:1)`; |
| 72 | + |
| 73 | + const result = JSON.parse(formatted(error)); |
| 74 | + result.exceptions[0].type.should.equal('Name%7F'); |
| 75 | + result.exceptions[0].message.should.equal('%7Fmessage'); |
| 76 | + }); |
| 77 | + |
| 78 | + it('should return empty string on circular reference', () => { |
| 79 | + class CircularError extends Error { |
| 80 | + constructor() { |
| 81 | + super('circular'); |
| 82 | + this.name = 'CircularError'; |
| 83 | + this.circular = this; |
| 84 | + } |
| 85 | + |
| 86 | + toString() { |
| 87 | + return 'CircularError: circular'; |
| 88 | + } |
| 89 | + } |
| 90 | + |
| 91 | + const error = new CircularError(); |
| 92 | + error.stack = `CircularError: circular |
| 93 | + at circularFunction (/var/task/circle.js:1:1)`; |
| 94 | + |
| 95 | + // Manually inject the circular object into a field that gets stringified |
| 96 | + const originalStack = error.stack; |
| 97 | + error.stack = { |
| 98 | + toString: () => { |
| 99 | + return originalStack; |
| 100 | + }, |
| 101 | + }; |
| 102 | + error.stack.circular = error.stack; |
| 103 | + |
| 104 | + const result = formatted(error); |
| 105 | + result.should.equal(''); |
| 106 | + }); |
| 107 | +}); |
0 commit comments