UNPKG

toxy

Version:

Hackable HTTP proxy to simulate server failure scenarios and network conditions

324 lines (263 loc) 8.69 kB
const http = require('http') const sinon = require('sinon') const expect = require('chai').expect const toxy = require('..') const supertest = require('supertest') suite('toxy', function () { test('public static members', function () { expect(toxy.rules).to.be.an('object') expect(toxy.poisons).to.be.an('object') expect(toxy.Directive).to.be.a('function') expect(toxy.Poison).to.be.a('function') expect(toxy.Rule).to.be.a('function') expect(toxy.Rocky).to.be.a('function') expect(toxy.admin).to.be.a('function') expect(toxy.VERSION).to.be.a('string') }) test('use poison', function (done) { const proxy = toxy() const spy = sinon.spy() proxy.poison(function delay (req, res, next) { spy(req, res) setTimeout(next, 5) }) expect(proxy.isEnabled('delay')).to.be.true proxy.disable('delay') expect(proxy.isEnabled('delay')).to.be.false proxy.enable('delay') expect(proxy.isEnabled('delay')).to.be.true proxy._inPoisons.run(null, null, function () { expect(spy.calledOnce).to.be.true done() }) }) test('use poison phases', function (done) { const proxy = toxy() const spy = sinon.spy() const noop = function () {} const reqStub = { socket: { once: noop, uncork: noop }, once: noop } const resStub = { connection: { cork: noop } } proxy.poison(function delay (req, res, next) { spy(req, res) next() }) proxy.outgoingPoison(function delay (req, res, next) { spy(req, res) next() }) expect(proxy.isEnabled('delay')).to.be.true proxy.disable('delay') expect(proxy.isEnabled('delay')).to.be.false expect(proxy.isEnabledOutgoing('delay')).to.be.true proxy.enable('delay') expect(proxy.isEnabled('delay')).to.be.true proxy.disableOutgoing('delay') expect(proxy.isEnabled('delay')).to.be.true expect(proxy.isEnabledOutgoing('delay')).to.be.false proxy.enableOutgoing('delay') proxy._inPoisons.run(reqStub, resStub, function () { expect(spy.calledOnce).to.be.true }) proxy._outPoisons.run(reqStub, resStub, function () { expect(spy.calledTwice).to.be.true proxy.remove('delay') expect(proxy.isEnabled('delay')).to.be.false expect(proxy.isEnabledOutgoing('delay')).to.be.true proxy.removeOutgoing('delay') expect(proxy.isEnabled('delay')).to.be.false expect(proxy.isEnabledOutgoing('delay')).to.be.false done() }) }) test('use rule', function (done) { const proxy = toxy() var called = false proxy.rule(function delay (req, res, next) { called = true setTimeout(next, 5) }) expect(proxy.isRuleEnabled('delay')).to.be.true proxy.disableRule('delay') expect(proxy.isRuleEnabled('delay')).to.be.false proxy.enableRule('delay') expect(proxy.isRuleEnabled('delay')).to.be.true proxy._rules.run(null, null, function () { expect(called).to.be.true done() }) }) test('add custom directives', function () { function testPoison () {} toxy.addPoison(testPoison) expect(toxy.poisons.testPoison).to.be.equal(testPoison) function testRule () {} toxy.addRule(testRule) expect(toxy.rules.testRule).to.be.equal(testRule) toxy.addRule('nameRule', testRule) expect(toxy.rules.nameRule).to.be.equal(testRule) function errorType () { toxy.addRule(null) } expect(errorType).to.throw(/Directive must be a function/i) function errorName () { toxy.addRule(function () {}) } expect(errorName).to.throw(/Directive function must have a name/i) }) test('get directives', function () { const proxy = toxy() proxy.poison(function delay () {}) proxy.rule(function match () {}) expect(proxy.isEnabled('delay')).to.be.true expect(proxy.isRuleEnabled('match')).to.be.true const poison = proxy.getPoison('delay') expect(poison).to.be.an('object') expect(poison.isEnabled()).to.be.true poison.disable() expect(poison.isEnabled()).to.be.false const rule = proxy.getRule('match') expect(rule).to.be.an('object') expect(rule.isEnabled()).to.be.true rule.disable() expect(rule.isEnabled()).to.be.false }) test('flush directives', function () { const proxy = toxy() proxy.poison(function delay () {}) proxy.rule(function match () {}) expect(proxy.isEnabled('delay')).to.be.true expect(proxy.isRuleEnabled('match')).to.be.true proxy.flush() expect(proxy.isEnabled('delay')).to.be.false expect(proxy.getPoison('delay')).to.be.null expect(proxy.getPoisons()).to.have.length(0) proxy.flushRules() expect(proxy.isRuleEnabled('match')).to.be.false expect(proxy.getRule('match')).to.be.null expect(proxy.getRules()).to.have.length(0) }) test('basic proxy with poisons', function (done) { const proxy = toxy() const spy = sinon.spy() const server = createServer(9081, 200) const timeout = 100 proxy.poison(function delay (req, res, next) { spy(req, res) setTimeout(next, timeout) }) proxy.rule(function method (req, res, next) { spy(req, res) next(null, req.method !== 'GET') }) proxy.forward('http://localhost:9081') proxy.get('/foo') proxy.listen(9080) const init = Date.now() supertest('http://localhost:9080') .get('/foo') .expect(200) .expect('Content-Type', 'application/json') .expect({ hello: 'world' }) .end(assert) function assert (err) { expect(err).to.be.null expect(Date.now() - init).to.be.at.least(timeout - 1) expect(spy.calledTwice).to.be.true const req = spy.args[0][0] expect(req.url).to.be.equal('/foo') expect(req.method).to.be.equal('GET') server.close() proxy.close(done) } }) test('proxy with outgoing poisons', function (done) { const proxy = toxy() const spy = sinon.spy() const server = createServer(9081, 200) const timeout = 100 proxy.outgoingPoison(function delay (req, res, next) { spy(req, res) setTimeout(next, timeout) }) proxy.outgoingPoison(function capture (req, res, next) { spy(req, res) next() }) proxy.rule(function method (req, res, next) { spy(req, res) next(null, req.method !== 'GET') }) proxy.forward('http://localhost:9081') proxy.get('/foo') proxy.listen(9080) const init = Date.now() supertest('http://localhost:9080') .get('/foo') .expect(200) .expect('Content-Type', 'application/json') .expect({ hello: 'world' }) .end(assert) function assert (err) { expect(err).to.be.null expect(Date.now() - init).to.be.at.least(timeout - 1) expect(spy.calledThrice).to.be.true const req = spy.args[0][0] expect(req.url).to.be.equal('/foo') expect(req.method).to.be.equal('GET') const res = spy.args[0][1] expect(res.getHeader('server')).to.be.deep.equal('rocky') expect(res._originalBody.toString()).to.be.deep.equal('{"hello":"world"}') expect(res.body.toString()).to.be.deep.equal('{"hello":"world"}') server.close() proxy.close(done) } }) test('final route handler when no matches', function (done) { const proxy = toxy() const spy = sinon.spy() createServer(9081, 200) proxy.poison(function delay (req, res, next) { throw new Error('Should not be called') }) proxy.rule(function method (req, res, next) { spy(req, res) next(null, true) }) proxy.forward('http://localhost:9081') proxy.get('/foo') proxy.listen(9080) supertest('http://localhost:9080') .get('/foo') .expect(200) .expect('Content-Type', 'application/json') .expect({ hello: 'world' }) .end(assert) function assert (err) { expect(spy.calledOnce).to.be.true const req = spy.args[0][0] expect(req.url).to.be.equal('/foo') expect(req.method).to.be.equal('GET') done(err) } }) }) function createServer (port, code, assert) { const server = http.createServer(function (req, res) { const data = JSON.stringify({ 'hello': 'world' }) res.writeHead(code, { 'Content-Length': Buffer.byteLength(data), 'Content-Type': 'application/json' }) res.write(data) var body = '' req.on('data', function (data) { body += data }) req.on('end', function () { req.body = body end() }) function end () { if (assert) assert(req, res) res.end() } }) server.listen(port) return server }