UNPKG

rollbar

Version:

Effortlessly track and debug errors in your JavaScript applications with Rollbar. This package includes advanced error tracking features and an intuitive interface to help you identify and fix issues more quickly.

634 lines (575 loc) 22.1 kB
/* globals expect */ /* globals describe */ /* globals it */ /* globals sinon */ var Queue = require('../src/queue'); function TestRateLimiterGenerator() { var TestRateLimiter = function () { this.shouldSendValue = true; this.handler = null; }; TestRateLimiter.prototype.shouldSend = function (item) { if (this.handler && typeof this.handler === 'function') { return this.handler(item); } return this.shouldSendValue; }; return TestRateLimiter; } function TestApiGenerator() { var TestApi = function (handler) { this.handler = handler; }; TestApi.prototype.postItem = function (item, callback) { if (this.handler && typeof this.handler === 'function') { this.handler(item, callback); } else { if (callback && typeof callback === 'function') { callback(new Error('BROKEN'), null); } } }; TestApi.prototype.configure = function () {}; return TestApi; } function TestLoggerGenerator() { var TestLogger = function () { this.calls = { log: [], error: [], info: [], }; }; TestLogger.prototype.log = function () { this.calls.log.push(arguments); }; TestLogger.prototype.error = function () { this.calls.error.push(arguments); }; TestLogger.prototype.info = function () { this.calls.info.push(arguments); }; return TestLogger; } describe('Queue()', function () { it('should have all of the expected methods', function (done) { var rateLimiter = new (TestRateLimiterGenerator())(); var api = new (TestApiGenerator())(); var logger = new (TestLoggerGenerator())(); var options = {}; var queue = new Queue(rateLimiter, api, logger, options); expect(queue).to.have.property('configure'); expect(queue).to.have.property('addPredicate'); expect(queue).to.have.property('addItem'); expect(queue).to.have.property('wait'); done(); }); }); describe('configure', function () { it('should update the options', function (done) { var rateLimiter = new (TestRateLimiterGenerator())(); var api = new (TestApiGenerator())(); var logger = new (TestLoggerGenerator())(); var options = { a: 1, b: 42 }; var queue = new Queue(rateLimiter, api, logger, options); expect(queue.options.a).to.eql(1); expect(queue.options.b).to.eql(42); queue.configure({ a: 2, c: 15 }); expect(queue.options.a).to.eql(2); expect(queue.options.b).to.eql(42); expect(queue.options.c).to.eql(15); done(); }); }); describe('addItem', function () { describe('not rate limited', function () { describe('api success', function () { it('should work with no callback', function (done) { var rateLimiter = new (TestRateLimiterGenerator())(); var api = new (TestApiGenerator())(); var logger = new (TestLoggerGenerator())(); var options = { transmit: true }; var queue = new Queue(rateLimiter, api, logger, options); var item = { mykey: 'myvalue' }; var serverResponse = { success: true }; rateLimiter.handler = function (i) { expect(i).to.eql(item); return { error: null, shouldSend: true, payload: null }; }; api.handler = function (i, cb) { expect(i).to.eql(item); cb(null, serverResponse); done(); }; queue.addItem({ mykey: 'myvalue' }); }); it('should work with a garbage callback', function (done) { var rateLimiter = new (TestRateLimiterGenerator())(); var api = new (TestApiGenerator())(); var logger = new (TestLoggerGenerator())(); var options = { transmit: true }; var queue = new Queue(rateLimiter, api, logger, options); var item = { mykey: 'myvalue' }; var serverResponse = { success: true }; rateLimiter.handler = function (i) { expect(i).to.eql(item); return { error: null, shouldSend: true, payload: null }; }; api.handler = function (i, cb) { expect(i).to.eql(item); cb(null, serverResponse); done(); }; queue.addItem({ mykey: 'myvalue' }, 'woops'); }); it('should work with no predicates', function (done) { var rateLimiter = new (TestRateLimiterGenerator())(); var api = new (TestApiGenerator())(); var logger = new (TestLoggerGenerator())(); var options = { transmit: true }; var queue = new Queue(rateLimiter, api, logger, options); var item = { mykey: 'myvalue' }; var serverResponse = { success: true }; rateLimiter.handler = function (i) { expect(i).to.eql(item); return { error: null, shouldSend: true, payload: null }; }; api.handler = function (i, cb) { expect(i).to.eql(item); cb(null, serverResponse); }; queue.addItem({ mykey: 'myvalue' }, function (err, resp) { expect(resp).to.eql(serverResponse); done(err); }); }); it('should call the logger if an error is about to be logged', function (done) { var rateLimiter = new (TestRateLimiterGenerator())(); var api = new (TestApiGenerator())(); var logger = new (TestLoggerGenerator())(); var options = { verbose: true, transmit: true }; var queue = new Queue(rateLimiter, api, logger, options); var item = { body: { trace: { exception: { message: 'hello' } } } }; var serverResponse = { success: true }; rateLimiter.handler = function (i) { expect(i).to.eql(item); return { error: null, shouldSend: true, payload: null }; }; api.handler = function (i, cb) { expect(i).to.eql(item); cb(null, serverResponse); }; queue.addItem(item, function (err, resp) { expect(resp).to.eql(serverResponse); expect(logger.calls.error[0][0]).to.eql('hello'); done(err); }); }); it('should call the logger if a message is about to be logged', function (done) { var rateLimiter = new (TestRateLimiterGenerator())(); var api = new (TestApiGenerator())(); var logger = new (TestLoggerGenerator())(); var options = { verbose: true, transmit: true }; var queue = new Queue(rateLimiter, api, logger, options); var item = { body: { message: { body: 'hello' } } }; var serverResponse = { success: true }; rateLimiter.handler = function (i) { expect(i).to.eql(item); return { error: null, shouldSend: true, payload: null }; }; api.handler = function (i, cb) { expect(i).to.eql(item); cb(null, serverResponse); }; queue.addItem(item, function (err, resp) { expect(resp).to.eql(serverResponse); expect(logger.calls.log[0][0]).to.eql('hello'); done(err); }); }); it('should not call the logger if verbose is false', function (done) { var rateLimiter = new (TestRateLimiterGenerator())(); var api = new (TestApiGenerator())(); var logger = new (TestLoggerGenerator())(); var options = { verbose: false, transmit: true }; var queue = new Queue(rateLimiter, api, logger, options); var item = { body: { message: { body: 'hello' } } }; var serverResponse = { success: true }; rateLimiter.handler = function (i) { expect(i).to.eql(item); return { error: null, shouldSend: true, payload: null }; }; api.handler = function (i, cb) { expect(i).to.eql(item); cb(null, serverResponse); }; queue.addItem(item, function (err, resp) { expect(resp).to.eql(serverResponse); expect(logger.calls.log.length).to.eql(0); done(err); }); }); it('should stop if a predicate returns false', function (done) { var rateLimiter = new (TestRateLimiterGenerator())(); var api = new (TestApiGenerator())(); var logger = new (TestLoggerGenerator())(); var options = { transmit: true }; var queue = new Queue(rateLimiter, api, logger, options); var item = { mykey: 'myvalue' }; var serverResponse = { success: true }; rateLimiter.handler = function (i) { expect(false).to.be.ok(); return { error: null, shouldSend: true, payload: null }; }; api.handler = function (i, cb) { expect(false).to.be.ok(); cb(null, serverResponse); }; queue.addPredicate(function (i, s) { return false; }); queue.addItem({ mykey: 'myvalue' }, function (err, resp) { expect(resp).to.not.be.ok(); done(err); }); }); it('should stop if a predicate returns an error', function (done) { var rateLimiter = new (TestRateLimiterGenerator())(); var api = new (TestApiGenerator())(); var logger = new (TestLoggerGenerator())(); var options = { transmit: true }; var queue = new Queue(rateLimiter, api, logger, options); var item = { mykey: 'myvalue' }; var serverResponse = { success: true }; rateLimiter.handler = function (i) { expect(false).to.be.ok(); return { error: null, shouldSend: true, payload: null }; }; api.handler = function (i, cb) { expect(false).to.be.ok(); cb(null, serverResponse); }; var predicateError = 'bork bork'; queue.addPredicate(function (i, s) { return { err: predicateError }; }); queue.addItem({ mykey: 'myvalue' }, function (err, resp) { expect(err).to.eql(predicateError); expect(resp).to.not.be.ok(); done(); }); }); it('should stop if any predicate returns an error', function (done) { var rateLimiter = new (TestRateLimiterGenerator())(); var api = new (TestApiGenerator())(); var logger = new (TestLoggerGenerator())(); var options = { transmit: true }; var queue = new Queue(rateLimiter, api, logger, options); var item = { mykey: 'myvalue' }; var serverResponse = { success: true }; rateLimiter.handler = function (i) { expect(false).to.be.ok(); return { error: null, shouldSend: true, payload: null }; }; api.handler = function (i, cb) { expect(false).to.be.ok(); cb(null, serverResponse); }; var predicateError = 'bork bork'; queue .addPredicate(function (i, s) { return true; }) .addPredicate(function (i, s) { return { err: predicateError }; }) .addPredicate(function (i, s) { return true; }); queue.addItem({ mykey: 'myvalue' }, function (err, resp) { expect(err).to.eql(predicateError); expect(resp).to.not.be.ok(); done(); }); }); it('should call wait if set', function (done) { var rateLimiter = new (TestRateLimiterGenerator())(); var api = new (TestApiGenerator())(); var logger = new (TestLoggerGenerator())(); var options = { transmit: true }; var queue = new Queue(rateLimiter, api, logger, options); var item = { mykey: 'myvalue' }; var serverResponse = { success: true }; rateLimiter.handler = function (i) { expect(false).to.be.ok(); return { error: null, shouldSend: true, payload: null }; }; api.handler = function (i, cb) { expect(false).to.be.ok(); cb(null, serverResponse); }; queue.wait(function () { done(); }); queue.addItem({ mykey: 'myvalue' }, function (err, resp) { expect(resp).to.be.ok(); }); }); it('should work if wait is called with a non-function', function (done) { var rateLimiter = new (TestRateLimiterGenerator())(); var api = new (TestApiGenerator())(); var logger = new (TestLoggerGenerator())(); var options = { transmit: true }; var queue = new Queue(rateLimiter, api, logger, options); var item = { mykey: 'myvalue' }; var serverResponse = { success: true }; rateLimiter.handler = function (i) { return { error: null, shouldSend: true, payload: null }; }; api.handler = function (i, cb) { cb(null, serverResponse); }; queue.wait({}); queue.addItem({ mykey: 'myvalue' }, function (err, resp) { expect(resp).to.be.ok(); done(err); }); }); it('should work if all predicates return true', function (done) { var rateLimiter = new (TestRateLimiterGenerator())(); var api = new (TestApiGenerator())(); var logger = new (TestLoggerGenerator())(); var options = { transmit: true }; var queue = new Queue(rateLimiter, api, logger, options); var item = { mykey: 'myvalue' }; var serverResponse = { success: true }; rateLimiter.handler = function (i) { expect(i).to.eql(item); return { error: null, shouldSend: true, payload: null }; }; api.handler = function (i, cb) { expect(i).to.eql(item); cb(null, serverResponse); }; queue .addPredicate(function (i, s) { return true; }) .addPredicate(function () { return true; }); queue.addItem({ mykey: 'myvalue' }, function (err, resp) { expect(resp).to.eql(serverResponse); done(err); }); }); }); describe('api failure', function () { it('should callback if the api throws an exception', function (done) { var rateLimiter = new (TestRateLimiterGenerator())(); var api = new (TestApiGenerator())(); var logger = new (TestLoggerGenerator())(); var options = { transmit: true }; var queue = new Queue(rateLimiter, api, logger, options); var item = { mykey: 'myvalue' }; var exception = 'boom!'; rateLimiter.handler = function (i) { expect(i).to.eql(item); return { error: null, shouldSend: true, payload: null }; }; api.handler = function (i, cb) { throw exception; }; queue .addPredicate(function (i, s) { return true; }) .addPredicate(function () { return true; }); queue.addItem({ mykey: 'myvalue' }, function (err, resp) { expect(resp).to.not.be.ok(); expect(err).to.eql(exception); done(); }); }); it('should callback with the api error if not retriable', function (done) { var rateLimiter = new (TestRateLimiterGenerator())(); var api = new (TestApiGenerator())(); var logger = new (TestLoggerGenerator())(); var options = { retryInterval: 1, transmit: true }; var queue = new Queue(rateLimiter, api, logger, options); var item = { mykey: 'myvalue' }; var apiError = { code: 'NOPE', message: 'borked' }; rateLimiter.handler = function (i) { return { error: null, shouldSend: true, payload: null }; }; api.handler = function (i, cb) { cb(apiError); }; queue.addItem({ mykey: 'myvalue' }, function (err, resp) { expect(err).to.eql(apiError); expect(resp).to.not.be.ok(); done(); }); }); it('should callback with the api error if no retryInterval is set', function (done) { var rateLimiter = new (TestRateLimiterGenerator())(); var api = new (TestApiGenerator())(); var logger = new (TestLoggerGenerator())(); var options = { transmit: true }; var queue = new Queue(rateLimiter, api, logger, options); var item = { mykey: 'myvalue' }; var apiError = { code: 'ENOTFOUND', message: 'No internet connection' }; rateLimiter.handler = function (i) { return { error: null, shouldSend: true, payload: null }; }; api.handler = function (i, cb) { cb(apiError); }; queue.addItem({ mykey: 'myvalue' }, function (err, resp) { expect(err).to.eql(apiError); expect(resp).to.not.be.ok(); done(); }); }); describe('if we get a retriable error', function () { it('should retry', function (done) { var rateLimiter = new (TestRateLimiterGenerator())(); var api = new (TestApiGenerator())(); var logger = new (TestLoggerGenerator())(); var options = { retryInterval: 1, transmit: true }; var queue = new Queue(rateLimiter, api, logger, options); var item = { mykey: 'myvalue' }; var serverResponse = { success: true }; var apiError = { code: 'ENOTFOUND', message: 'No internet connection', }; var apiRequestCount = 0; rateLimiter.handler = function (i) { return { error: null, shouldSend: true, payload: null }; }; api.handler = function (i, cb) { apiRequestCount++; if (apiRequestCount === 1) { cb(apiError); } else { cb(null, serverResponse); } }; queue.addItem({ mykey: 'myvalue' }, function (err, resp) { expect(err).to.not.be.ok(); expect(resp).to.eql(serverResponse); expect(apiRequestCount).to.eql(2); done(); }); }); it('should retry until maxRetries limit is reached', function (done) { var rateLimiter = new (TestRateLimiterGenerator())(); var api = new (TestApiGenerator())(); var logger = new (TestLoggerGenerator())(); var options = { retryInterval: 1, maxRetries: 2, transmit: true }; var queue = new Queue(rateLimiter, api, logger, options); var item = { mykey: 'myvalue' }; var serverResponse = { success: true }; var apiError = { code: 'ENOTFOUND', message: 'No internet connection', }; var apiRequestCount = 0; rateLimiter.handler = function (i) { return { error: null, shouldSend: true, payload: null }; }; api.handler = function (i, cb) { apiRequestCount++; cb({ ...apiError, retry: apiRequestCount }); }; queue.addItem({ mykey: 'myvalue' }, function (err, resp) { var numRequests = options.maxRetries + 1; expect(apiRequestCount).to.eql(numRequests); expect(err).to.eql({ ...apiError, retry: numRequests }); expect(resp).to.not.be.ok(); done(); }); }); }); }); describe('transmit disabled', function () { it('should not attempt to send', function (done) { var rateLimiter = new (TestRateLimiterGenerator())(); var api = new (TestApiGenerator())(); var logger = new (TestLoggerGenerator())(); var options = { transmit: false }; var queue = new Queue(rateLimiter, api, logger, options); var makeApiRequestStub = sinon.stub(queue, '_makeApiRequest'); queue.addItem({ mykey: 'myvalue' }, function (err) { expect(err.message).to.eql('Transmit disabled'); }); expect(makeApiRequestStub.called).to.eql(0); queue._makeApiRequest.restore(); done(); }); }); }); describe('rate limited', function () { it('should callback if the rate limiter says not to send and has an error', function (done) { var rateLimiter = new (TestRateLimiterGenerator())(); var api = new (TestApiGenerator())(); var logger = new (TestLoggerGenerator())(); var options = { transmit: true }; var queue = new Queue(rateLimiter, api, logger, options); var item = { mykey: 'myvalue' }; var rateLimitError = 'bork'; rateLimiter.handler = function (i) { expect(i).to.eql(item); return { error: rateLimitError, shouldSend: false, payload: null }; }; api.handler = function (i, cb) { cb(null, 'Good times'); }; queue .addPredicate(function (i, s) { return true; }) .addPredicate(function () { return true; }); queue.addItem({ mykey: 'myvalue' }, function (err, resp) { expect(resp).to.not.be.ok(); expect(err).to.eql(rateLimitError); done(); }); }); it('should callback if the rate limiter says not to send and has a payload', function (done) { var rateLimiter = new (TestRateLimiterGenerator())(); var api = new (TestApiGenerator())(); var logger = new (TestLoggerGenerator())(); var options = { transmit: true }; var queue = new Queue(rateLimiter, api, logger, options); var item = { mykey: 'myvalue' }; var rateLimitPayload = { something: 'went wrong' }; var serverResponse = { message: 'good times' }; rateLimiter.handler = function (i) { expect(i).to.eql(item); return { error: null, shouldSend: false, payload: rateLimitPayload }; }; api.handler = function (i, cb) { expect(i).to.eql(rateLimitPayload); cb(null, serverResponse); }; queue .addPredicate(function (i, s) { return true; }) .addPredicate(function () { return true; }); queue.addItem({ mykey: 'myvalue' }, function (err, resp) { expect(resp).to.eql(serverResponse); expect(err).to.not.be.ok(); done(); }); }); }); });