"use strict"; var assert = require('assert'); var util = require('util'); var vows = require('vows'); var Transport = require('../src/server/transport'); var t = new Transport(); vows.describe('transport') .addBatch({ 'post': { 'base data': { topic: function() { return { accessToken: 'abc123', options: {}, payload: { access_token: 'abc123', data: {a: 1} } }; }, 'with no payload': { topic: function(data) { var factory = transportFactory(null, '{"err": null, "result":"all good"}'); t.post(data.accessToken, data.options, null, this.callback, factory); }, 'should have an error': function(err, resp) { assert.ok(err); } }, 'with a payload and no error': { topic: function(data) { var factory = transportFactory(null, '{"err": null, "result":{"uuid":"42def", "message":"all good"}}', function() { assert.equal(this.options.headers['Content-Type'], 'application/json'); assert.isNumber(this.options.headers['Content-Length']); assert(this.options.headers['Content-Length'] > 0); assert.equal(this.options.headers['X-Rollbar-Access-Token'], data.accessToken); }); t.post(data.accessToken, data.options, data.payload, this.callback, factory); }, 'should not error': function(err, resp) { assert.ifError(err); }, 'should have the right response data': function(err, resp) { assert.equal(resp.message, 'all good'); } }, 'with a payload and an error in the response': { topic: function(data) { var factory = transportFactory(null, '{"err": "bork", "message":"things broke"}', function() { assert.equal(this.options.headers['Content-Type'], 'application/json'); assert.isNumber(this.options.headers['Content-Length']); assert(this.options.headers['Content-Length'] > 0); assert.equal(this.options.headers['X-Rollbar-Access-Token'], data.accessToken); }); t.post(data.accessToken, data.options, data.payload, this.callback, factory); }, 'should error': function(err, resp) { assert.ok(err); }, 'should have the message somewhere': function(err, resp) { assert.match(err.message, /things broke/); }, 'should not have a response': function(err, resp) { assert.ifError(resp); } }, 'with a payload and an error during sending': { topic: function(data) { var factory = transportFactory(new Error('bork'), null, function() { assert.equal(this.options.headers['Content-Type'], 'application/json'); assert.isNumber(this.options.headers['Content-Length']); assert(this.options.headers['Content-Length'] > 0); assert.equal(this.options.headers['X-Rollbar-Access-Token'], data.accessToken); }); t.post(data.accessToken, data.options, data.payload, this.callback, factory); }, 'should error': function(err, resp) { assert.ok(err); }, 'should have the message somewhere': function(err, resp) { assert.match(err.message, /bork/); }, 'should not have a response': function(err, resp) { assert.ifError(resp); } } }, 'with rate limiting': { topic: function() { return new Transport(); }, 'should transmit non-rate limited requests': function(t) { var response = new TestResponse({ statusCode: 200, headers: { 'x-rate-limit-remaining': '1', 'x-rate-limit-remaining-seconds': '100' } }); var error; assert.equal(t.rateLimitExpires, 0); t.handleResponse(response); var factory = transportFactory(null, '{"err": null, "result": "all good"}'); t.post('token', {}, 'payload', function(err){ error = err; }, factory); assert.equal(error, null); assert.isTrue(Math.floor(Date.now() / 1000) >= t.rateLimitExpires); }, 'should drop rate limited requests and set timeout': function(t) { var response = new TestResponse({ statusCode: 429, headers: { 'x-rate-limit-remaining': '0', 'x-rate-limit-remaining-seconds': '100' } }); var error; t.handleResponse(response); t.post('token', {}, 'payload', function(err){ error = err; }); assert.match(error.message, /Exceeded rate limit/); assert.isTrue(Math.floor(Date.now() / 1000) < t.rateLimitExpires); } } } }) .export(module, {error: false}); var TestTransport = function(options, error, response, assertions) { this.options = options; this.error = error; this.response = response; this.requestOpts = null; this.requestCallback = null; this.assertions = assertions; }; var TestRequest = function(error, response, transport) { this.error = error; this.responseData = response; this.data = []; this.events = {}; this.transport = transport; this.response = null; }; TestTransport.prototype.request = function(opts, cb) { this.requestOpts = opts; this.requestCallback = cb; return new TestRequest(this.error, this.response, this); }; TestRequest.prototype.write = function(data) { this.data.push(data); }; TestRequest.prototype.on = function(event, cb) { this.events[event] = cb; }; TestRequest.prototype.end = function() { if (this.transport.assertions) { this.transport.assertions(); } if (this.error) { if (this.events['error']) { this.events['error'](this.error); } } else { this.response = new TestResponse(); this.transport.requestCallback(this.response); if (this.response.events['data']) { this.response.events['data'](this.responseData); } if (this.response.events['end']) { this.response.events['end'](); } } }; var TestResponse = function(options = {}) { this.encoding = null; this.events = {}; this.headers = options.headers || {}; this.statusCode = options.statusCode || 200; }; TestResponse.prototype.setEncoding = function(s) { this.encoding = s; } TestResponse.prototype.on = function(event, cb) { this.events[event] = cb; }; var transportFactory = function(error, response, assertions) { return function(options) { return new TestTransport(options, error, response, assertions); }; };