| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247 |
- const Path = require('path')
- const SandboxedModule = require('sandboxed-module')
- const { expect } = require('chai')
- const sinon = require('sinon')
- const MODULE_PATH = Path.join(__dirname, '../../sentry-manager.js')
- describe('SentryManager', function () {
- beforeEach(function () {
- this.clock = sinon.useFakeTimers(Date.now())
- this.Sentry = {
- init: sinon.stub(),
- captureException: sinon.stub(),
- }
- this.SentryManager = SandboxedModule.require(MODULE_PATH, {
- requires: {
- '@sentry/node': this.Sentry,
- },
- })
- this.sentryManager = new this.SentryManager('test_dsn')
- })
- afterEach(function () {
- this.clock.restore()
- })
- describe('captureExceptionRateLimited', function () {
- it('should report a single error to sentry', function () {
- this.sentryManager.captureExceptionRateLimited({ foo: 'bar' }, 'message')
- expect(this.Sentry.captureException).to.have.been.calledOnce
- })
- it('should report the same error to sentry only once', function () {
- const error1 = new Error('this is the error')
- this.sentryManager.captureExceptionRateLimited(
- { foo: error1 },
- 'first message'
- )
- this.sentryManager.captureExceptionRateLimited(
- { bar: error1 },
- 'second message'
- )
- expect(this.Sentry.captureException).to.have.been.calledOnce
- })
- it('should report two different errors to sentry individually', function () {
- const error1 = new Error('this is the error')
- const error2 = new Error('this is the error')
- this.sentryManager.captureExceptionRateLimited(
- { foo: error1 },
- 'first message'
- )
- this.sentryManager.captureExceptionRateLimited(
- { bar: error2 },
- 'second message'
- )
- expect(this.Sentry.captureException).to.have.been.calledTwice
- })
- it('for multiple errors should only report a maximum of 5 errors to sentry', function () {
- for (let i = 0; i < 10; i++) {
- this.sentryManager.captureExceptionRateLimited(
- { foo: 'bar' },
- 'message'
- )
- }
- expect(this.Sentry.captureException).to.have.callCount(5)
- })
- it('for multiple errors with a minute delay should report 10 errors to sentry', function () {
- for (let i = 0; i < 10; i++) {
- this.sentryManager.captureExceptionRateLimited(
- { foo: 'bar' },
- 'message'
- )
- }
- expect(this.Sentry.captureException).to.have.callCount(5)
- // allow a minute to pass
- this.clock.tick(61 * 1000)
- for (let i = 0; i < 10; i++) {
- this.sentryManager.captureExceptionRateLimited(
- { foo: 'bar' },
- 'message'
- )
- }
- expect(this.Sentry.captureException).to.have.callCount(10)
- })
- })
- describe('captureException', function () {
- it('should remove the path from fs errors', function () {
- const fsError = new Error(
- "Error: ENOENT: no such file or directory, stat '/tmp/3279b8d0-da10-11e8-8255-efd98985942b'"
- )
- fsError.path = '/tmp/3279b8d0-da10-11e8-8255-efd98985942b'
- this.sentryManager.captureException({ err: fsError }, 'message', 'error')
- expect(this.Sentry.captureException).to.have.been.calledWith(
- sinon.match.has(
- 'message',
- 'Error: ENOENT: no such file or directory, stat'
- )
- )
- })
- it('should sanitize error', function () {
- const err = {
- name: 'CustomError',
- message: 'hello',
- _oErrorTags: [{ stack: 'here:1', info: { one: 1 } }],
- stack: 'here:0',
- info: { key: 'value' },
- code: 42,
- signal: 9,
- path: '/foo',
- }
- this.sentryManager.captureException({ err }, 'message', 'error')
- const expectedErr = {
- name: 'CustomError',
- message: 'hello',
- stack: 'here:0\nhere:1',
- code: 42,
- signal: 9,
- path: '/foo',
- }
- expect(this.Sentry.captureException).to.have.been.calledWith(
- sinon.match(expectedErr),
- sinon.match({
- tags: sinon.match.any,
- level: sinon.match.any,
- extra: {
- description: 'message',
- info: sinon.match({
- one: 1,
- key: 'value',
- }),
- },
- })
- )
- // Chai is very picky with comparing Error instances. Go the long way of comparing all the fields manually.
- const gotErr = this.Sentry.captureException.args[0][0]
- for (const [key, wanted] of Object.entries(expectedErr)) {
- expect(gotErr).to.have.property(key, wanted)
- }
- })
- it('should sanitize request', function () {
- const req = {
- ip: '1.2.3.4',
- method: 'GET',
- url: '/foo',
- headers: {
- referer: 'abc',
- 'content-length': 1337,
- 'user-agent': 'curl',
- authorization: '42',
- },
- }
- this.sentryManager.captureException({ req }, 'message', 'error')
- const expectedReq = {
- remoteAddress: '1.2.3.4',
- method: 'GET',
- url: '/foo',
- headers: {
- referer: 'abc',
- 'content-length': 1337,
- 'user-agent': 'curl',
- },
- }
- expect(this.Sentry.captureException).to.have.been.calledWith(
- sinon.match({
- message: 'message',
- }),
- sinon.match({
- tags: sinon.match.any,
- level: sinon.match.any,
- extra: {
- info: sinon.match.any,
- req: expectedReq,
- },
- })
- )
- expect(this.Sentry.captureException.args[0][1].extra.req).to.deep.equal(
- expectedReq
- )
- })
- it('should sanitize response', function () {
- const res = {
- statusCode: 417,
- body: Buffer.from('foo'),
- getHeader(key) {
- expect(key).to.be.oneOf(['content-length'])
- if (key === 'content-length') return 1337
- },
- }
- this.sentryManager.captureException({ res }, 'message', 'error')
- const expectedRes = {
- statusCode: 417,
- headers: {
- 'content-length': 1337,
- },
- }
- expect(this.Sentry.captureException).to.have.been.calledWith(
- sinon.match({
- message: 'message',
- }),
- sinon.match({
- tags: sinon.match.any,
- level: sinon.match.any,
- extra: {
- info: sinon.match.any,
- res: expectedRes,
- },
- })
- )
- expect(this.Sentry.captureException.args[0][1].extra.res).to.deep.equal(
- expectedRes
- )
- })
- describe('reportedToSentry', function () {
- it('should mark the error as reported to sentry', function () {
- const err = new Error()
- this.sentryManager.captureException({ err }, 'message')
- expect(this.Sentry.captureException).to.have.been.called
- expect(err.reportedToSentry).to.equal(true)
- })
- it('should mark two errors as reported to sentry', function () {
- const err1 = new Error()
- const err2 = new Error()
- this.sentryManager.captureException({ err: err1, err2 }, 'message')
- expect(this.Sentry.captureException).to.have.been.called
- expect(err1.reportedToSentry).to.equal(true)
- expect(err2.reportedToSentry).to.equal(true)
- })
- it('should not mark arbitrary objects as reported to sentry', function () {
- const err = new Error()
- const ctx = { foo: 'bar' }
- this.sentryManager.captureException({ err, ctx }, 'message')
- expect(this.Sentry.captureException).to.have.been.called
- expect(ctx.reportedToSentry).not.to.exist
- })
- })
- })
- })
|