/* globals expect */ /* globals describe */ /* globals it */ /* globals sinon */ var Rollbar = require('../src/browser/rollbar'); var t = require('../src/browser/transforms'); function TestClientGen() { var TestClient = function() { this.notifier = { addTransform: function() { return this.notifier; }.bind(this) }; this.queue = { addPredicate: function() { return this.queue; }.bind(this) }; }; return TestClient; } function itemFromArgs(args) { var client = new (TestClientGen())(); var rollbar = new Rollbar({autoInstrument: false}, client); var item = rollbar._createItem(args); item.level = 'debug'; return item; } function chromeMajorVersion() { return parseInt(navigator.userAgent.match(/Chrome\/([0-9]+)\./)[1]); } describe('handleDomException', function() { it('should do nothing if not a DOMException', function(done) { var err = new Error('test'); var args = ['a message', err]; var item = itemFromArgs(args); var options = {}; t.handleDomException(item, options, function(e, i) { expect(item.err).to.eql(item.err); expect(item.err.nested).to.not.be.ok(); done(e); }); }); it('should create nested exception for DOMException', function(done) { var err = new DOMException('dom error'); var args = ['a message', err]; var item = itemFromArgs(args); var options = {}; t.handleDomException(item, options, function(e, i) { expect(item.err.nested.constructor.name).to.eql('DOMException'); expect(item.err.constructor.name).to.eql('Error'); done(e); }); }); }); describe('handleItemWithError', function() { it('should do nothing if there is no err', function(done) { var args = ['a message']; var item = itemFromArgs(args); var options = {}; t.handleItemWithError(item, options, function(e, i) { expect(i).to.eql(item); done(e); }); }); it('should set stack info from error if it is already saved', function(done) { var err = new Error('bork'); var myTrace = {trace: {frames: [1,2,3]}}; err._savedStackTrace = myTrace; var args = ['a message', err]; var item = itemFromArgs(args); var options = {}; t.handleItemWithError(item, options, function(e, i) { expect(i.stackInfo).to.eql(myTrace); done(e); }); }); it('should set stack info from error', function(done) { var err; try { throw new Error('bork'); } catch (e) { err = e; } var args = ['a message', err]; var item = itemFromArgs(args); var options = {}; t.handleItemWithError(item, options, function(e, i) { expect(i.message).to.eql('a message'); expect(i.stackInfo).to.be.ok(); done(e); }); }); it('should handle bad errors and still set stackInfo', function(done) { var err = {description: 'bork'}; var args = ['a message', 'fuzz']; var item = itemFromArgs(args); item.err = err; var options = {}; t.handleItemWithError(item, options, function(e, i) { expect(i.stackInfo).to.be.ok(); expect(i.message).to.eql('a message'); done(e); }); }); it('should use most specific error name', function(done) { var err = new Error('bork'); var args = ['a message', err]; var options = {}; var names = [ {name: 'TypeError', constructor: 'EvalError', result: 'TypeError'}, {name: 'TypeError', constructor: 'Error', result: 'TypeError'}, {name: 'Error', constructor: 'TypeError', result: 'TypeError'}, {name: 'Error', constructor: '', result: 'Error'}, {name: '', constructor: 'Error', result: 'Error'}, {name: '', constructor: '', result: ''} ]; for(var i = 0; i < names.length; i++) { err.name = names[i].name; err.constructor = { name: names[i].constructor }; var item = itemFromArgs(args); var result = names[i].result; t.handleItemWithError(item, options, function(e, i) { expect(i.stackInfo.name).to.eql(result); }); }; done(); }); }); describe('ensureItemHasSomethingToSay', function() { it('should error if item has nothing', function(done) { var args = []; var item = itemFromArgs(args); var options = {}; t.ensureItemHasSomethingToSay(item, options, function(e, i) { expect(e).to.be.ok(); done(i); }); }); it('should do nothing if item has a message', function(done) { var args = []; var item = itemFromArgs(args); item.message = 'bork'; var options = {}; t.ensureItemHasSomethingToSay(item, options, function(e, i) { expect(i).to.be.ok(); done(e); }); }); it('should do nothing if item has stackInfo', function(done) { var args = []; var item = itemFromArgs(args); item.data = item.data || {}; item.stackInfo = {}; var options = {}; t.ensureItemHasSomethingToSay(item, options, function(e, i) { expect(i).to.be.ok(); done(e); }); }); it('should do nothing if item has custom data', function(done) { var args = []; var item = itemFromArgs(args); item.custom = {}; var options = {}; t.ensureItemHasSomethingToSay(item, options, function(e, i) { expect(i).to.be.ok(); done(e); }); }); }); describe('addBaseInfo', function() { it('should add all of the expected data', function(done) { var args = ['a message']; var item = itemFromArgs(args); item.level = 'critical'; var options = {}; t.addBaseInfo(item, options, function(e, i) { expect(i.data.level).to.eql('critical'); expect(i.data.platform).to.eql('browser'); expect(i.data.framework).to.eql('browser-js'); expect(i.data.language).to.eql('javascript'); expect(i.data.notifier.name).to.eql('rollbar-browser-js'); done(e); }); }); it('should pull data from options', function(done) { var args = ['a message']; var item = itemFromArgs(args); var options = { environment: 'dev', endpoint: 'api.rollbar.com', version: '42' }; t.addBaseInfo(item, options, function(e, i) { expect(i.data.environment).to.eql('dev'); expect(i.data.endpoint).to.eql('api.rollbar.com'); expect(i.data.notifier.version).to.eql('42'); done(e); }); }); it('should pull environment from payload options', function(done) { var args = ['a message']; var item = itemFromArgs(args); var options = { payload: {environment: 'dev'} }; t.addBaseInfo(item, options, function(e, i) { expect(i.data.environment).to.eql('dev'); done(e); }); }); }); describe('addRequestInfo', function() { it('should use window info to set request properties', function(done) { var args = ['a message']; var item = itemFromArgs(args); var options = { captureIp: 'anonymize' }; t.addRequestInfo(window)(item, options, function(e, i) { expect(i.data.request).to.be.ok(); expect(i.data.request.user_ip).to.eql('$remote_ip_anonymize'); done(e); }); }); it('should do nothing without window', function(done) { var args = ['a message']; var item = itemFromArgs(args); item.data = {}; var options = {}; var w = null; t.addRequestInfo(w)(item, options, function(e, i) { expect(i.data.request).to.not.be.ok(); done(e); }); }); it('should honor captureIp without window', function(done) { var args = ['a message']; var item = itemFromArgs(args); item.data = {}; var options = { captureIp: true }; var w = null; t.addRequestInfo(w)(item, options, function(e, i) { expect(i.data.request.url).to.not.be.ok(); expect(i.data.request.query_string).to.not.be.ok(); expect(i.data.request.user_ip).to.eql('$remote_ip'); done(e); }); }); }); describe('addClientInfo', function() { it('should do nothing without a window', function(done) { var args = ['a message']; var item = itemFromArgs(args); item.data = {}; var options = {}; var w = null; t.addClientInfo(w)(item, options, function(e, i) { expect(i.data.client).to.not.be.ok(); done(e); }); }); it('should use window info to set client properties', function(done) { var args = ['a message']; var item = itemFromArgs(args); var options = {}; t.addClientInfo(window)(item, options, function(e, i) { expect(i.data.client).to.be.ok(); expect(i.data.client.javascript).to.be.ok(); done(e); }); }); }); describe('addPluginInfo', function() { it('should do nothing without a window', function(done) { var args = ['a message']; var item = itemFromArgs(args); var options = {}; var w = null; t.addPluginInfo(w)(item, options, function(e, i) { expect(i.data && i.data.client && i.data.client.javascript && i.data.client.javascript.plugins).to.not.be.ok(); done(e); }); }); it('should add plugin data from the window', function(done) { var args = ['a message']; var item = itemFromArgs(args); var options = {}; var w = {navigator: {plugins: []}}; w.navigator.plugins.push({name: 'plugin 1', description: '1'}); w.navigator.plugins.push({name: 'plugin 2', description: '2'}); t.addPluginInfo(w)(item, options, function(e, i) { expect(i.data.client.javascript.plugins).to.be.ok(); expect(i.data.client.javascript.plugins.length).to.eql(2); expect(i.data.client.javascript.plugins[0].name).to.eql('plugin 1'); done(e); }); }); }); describe('addBody', function() { describe('with stackInfo', function() { it('should use the stackInfo to add a trace to the body', function(done) { var err; try { throw new Error('bork'); } catch (e) { err = e; } var args = ['a message', err, {custom: 'stuff'}]; var item = itemFromArgs(args); item.description = 'borked'; var options = {}; t.handleItemWithError(item, options, function(e, i) { expect(i.stackInfo).to.be.ok(); t.addBody(i, options, function(e, i) { expect(i.data.body.trace).to.be.ok(); done(e); }); }); }); it('should add a message with a bad stackInfo', function(done) { var args = ['a message']; var item = itemFromArgs(args); item.description = 'borked'; item.data = item.data || {}; item.stackInfo = {name: 'bork'}; var options = {}; t.addBody(item, options, function(e, i) { expect(i.data.body.trace).to.not.be.ok(); expect(i.data.body.message.body).to.be.ok(); done(e); }); }); }); describe('without stackInfo', function() { it('should add a message as the body', function(done) { var args = ['a message', {custom: 'stuff'}]; var item = itemFromArgs(args); var options = {}; t.addBody(item, options, function(e, i) { expect(i.data.body.message.body).to.be.ok(); done(e); }); }); it('should send message when sent without a message', function(done) { var args = [{custom: 'stuff'}]; var item = itemFromArgs(args); var options = {}; t.addBody(item, options, function(e, i) { expect(i.data.body.message.body).to.eql('Item sent with null or missing arguments.'); done(e); }); }); }); describe('without stackInfo.name', function() { it('should set error class unknown', function(done) { var err; try { throw new Error('bork'); } catch (e) { err = e; } var args = ['a message', err, {custom: 'stuff'}]; var item = itemFromArgs(args); item.description = 'borked'; var options = {}; t.handleItemWithError(item, options, function(e, i) { expect(i.stackInfo).to.be.ok(); i.stackInfo.name = null; // force alternate path to determine error class. t.addBody(i, options, function(e, i) { expect(i.data.body.trace.exception.class).to.eql('(unknown)'); expect(i.data.body.trace.exception.message).to.eql('bork'); done(e); }); }); }); describe('when config.guessErrorClass is set', function() { it('should guess error class ', function(done) { var err; try { throw new Error('GuessedError: bork'); } catch (e) { err = e; } var args = [err, {custom: 'stuff'}]; var item = itemFromArgs(args); item.description = 'borked'; var options = { guessErrorClass: true }; t.handleItemWithError(item, options, function(e, i) { expect(i.stackInfo).to.be.ok(); i.stackInfo.name = null; // force alternate path to determine error class. t.addBody(i, options, function(e, i) { expect(i.data.body.trace.exception.class).to.eql('GuessedError'); expect(i.data.body.trace.exception.message).to.eql('bork'); done(e); }); }); }); it('should set error class unknown', function(done) { var err; try { throw new Error('bork'); } catch (e) { err = e; } var args = [err, {custom: 'stuff'}]; var item = itemFromArgs(args); item.description = 'borked'; var options = { guessErrorClass: true }; t.handleItemWithError(item, options, function(e, i) { expect(i.stackInfo).to.be.ok(); i.stackInfo.name = null; // force alternate path to determine error class. t.addBody(i, options, function(e, i) { expect(i.data.body.trace.exception.class).to.eql('(unknown)'); expect(i.data.body.trace.exception.message).to.eql('bork'); done(e); }); }); }); }); }); describe('with nested error', function() { it('should create trace_chain', function(done) { var nestedErr = new Error('nested error'); var err = new Error('test error'); err.nested = nestedErr; var args = ['a message', err]; var item = itemFromArgs(args); var options = {}; t.handleItemWithError(item, options, function(e, i) { expect(i.stackInfo).to.be.ok(); }); t.addBody(item, options, function(e, i) { expect(i.data.body.trace_chain.length).to.eql(2); expect(i.data.body.trace_chain[0].exception.message).to.eql('test error'); expect(i.data.body.trace_chain[1].exception.message).to.eql('nested error'); done(e); }); }); it('should create add error context as custom data', function(done) { var nestedErr = new Error('nested error'); nestedErr.rollbarContext = { err1: 'nested context' }; var err = new Error('test error'); err.rollbarContext = { err2: 'error context' }; err.nested = nestedErr; var args = ['a message', err]; var item = itemFromArgs(args); var options = { addErrorContext: true }; t.handleItemWithError(item, options, function(e, i) { expect(i.stackInfo).to.be.ok(); }); t.addBody(item, options, function(e, i) { expect(i.data.body.trace_chain.length).to.eql(2); expect(i.data.custom.err1).to.eql('nested context'); expect(i.data.custom.err2).to.eql('error context'); done(e); }); }); }); describe('with error cause', function() { // Error cause was introduced in Chrome 93. if (chromeMajorVersion() < 93) return; it('should create trace_chain', function(done) { var causeErr = new Error('cause error'); var err = new Error('test error', { cause: causeErr}); var args = ['a message', err]; var item = itemFromArgs(args); var options = {}; t.handleItemWithError(item, options, function(e, i) { expect(i.stackInfo).to.be.ok(); }); t.addBody(item, options, function(e, i) { expect(i.data.body.trace_chain.length).to.eql(2); expect(i.data.body.trace_chain[0].exception.message).to.eql('test error'); expect(i.data.body.trace_chain[1].exception.message).to.eql('cause error'); done(e); }); }); it('should create add error context as custom data', function(done) { var causeErr = new Error('cause error'); causeErr.rollbarContext = { err1: 'cause context' }; var err = new Error('test error', { cause: causeErr}); err.rollbarContext = { err2: 'error context' }; var args = ['a message', err]; var item = itemFromArgs(args); var options = { addErrorContext: true }; t.handleItemWithError(item, options, function(e, i) { expect(i.stackInfo).to.be.ok(); }); t.addBody(item, options, function(e, i) { expect(i.data.body.trace_chain.length).to.eql(2); expect(i.data.custom.err1).to.eql('cause context'); expect(i.data.custom.err2).to.eql('error context'); done(e); }); }); }); }); describe('scrubPayload', function() { it('only scrubs payload data', function(done) { var args = ['a message', {scooby: 'doo', okay: 'fizz=buzz&fuzz=baz', user: {id: 42}}]; var item = itemFromArgs(args); var accessToken = 'abc123'; var options = { endpoint: 'api.rollbar.com/', scrubFields: ['access_token', 'accessToken', 'scooby', 'fizz', 'user'] }; var payload = { access_token: accessToken, data: item }; expect(payload.access_token).to.eql(accessToken); expect(payload.data.custom.scooby).to.eql('doo'); expect(payload.data.custom.okay).to.eql('fizz=buzz&fuzz=baz'); expect(payload.data.custom.user.id).to.eql(42); var scrub = require('../src/scrub'); (t.addScrubber(scrub))(payload, options, function(e, i) { expect(i.access_token).to.eql(accessToken); expect(i.data.custom.scooby).to.not.eql('doo'); expect(payload.data.custom.okay).to.not.eql('fizz=buzz&fuzz=baz'); expect(payload.data.custom.okay).to.match(/fizz=\*+&fuzz=baz/); expect(payload.data.custom.user.id).to.not.be.ok(); expect(payload.data.custom.user).to.match(/\*+/); expect(i.data.message).to.eql('a message'); done(e); }); }); });