Refactor socket.io controller

This commit is contained in:
Calvin Montgomery 2017-08-01 19:29:11 -07:00
parent 107155a661
commit 0118a6fb15
10 changed files with 480 additions and 253 deletions

153
test/io/ioserver.js Normal file
View file

@ -0,0 +1,153 @@
const assert = require('assert');
const IOServer = require('../../lib/io/ioserver').IOServer;
describe('IOServer', () => {
let server;
let socket;
beforeEach(() => {
server = new IOServer();
socket = {
context: {
ipAddress: '9.9.9.9'
},
client: {
request: {
connection: {
remoteAddress: '127.0.0.1'
},
headers: {
'x-forwarded-for': '1.2.3.4'
}
}
}
};
socket.request = socket.client.request;
});
describe('#ipProxyMiddleware', () => {
it('proxies from a trusted address', done => {
server.ipProxyMiddleware(socket, error => {
assert(!error);
assert.strictEqual(socket.context.ipAddress, '1.2.3.4');
done();
});
});
it('does not proxy from a non-trusted address', done => {
socket.client.request.connection.remoteAddress = '5.6.7.8';
server.ipProxyMiddleware(socket, error => {
assert(!error);
assert.strictEqual(socket.context.ipAddress, '5.6.7.8');
done();
});
});
it('sets context.torConnection = true for Tor exits', () => {
// TODO
});
});
describe('#ipBanMiddleware', () => {
// TODO
});
describe('#ipThrottleMiddleware', () => {
it('throttles connections', done => {
let i = 0;
function callback(error) {
if (i < 5) {
assert(!error);
} else {
assert.strictEqual(error.message, 'Rate limit exceeded');
done();
}
}
function next() {
server.ipThrottleMiddleware(socket, error => {
callback(error);
if (++i < 6) next();
});
}
next();
});
});
describe('#ipConnectionLimitMiddleware', () => {
beforeEach(() => {
socket.once = (event, callback) => {
socket[`on_${event}`] = callback;
};
});
it('allows IPs before the limit', done => {
server.ipConnectionLimitMiddleware(socket, error => {
if (error) {
throw error;
}
done();
});
});
it('rejects IPs at the limit', done => {
server.ipCount.set(socket.context.ipAddress,
require('../../lib/config').get('io.ip-connection-limit'));
server.ipConnectionLimitMiddleware(socket, error => {
assert(error, 'Expected an error to be returned');
assert.strictEqual(error.message,
'Too many connections from your IP address');
done();
});
});
it('manages the ipCount map correctly', done => {
const ip = socket.context.ipAddress;
assert(!server.ipCount.has(ip), 'Test precondition failed: ipCount.has(ip)');
server.ipConnectionLimitMiddleware(socket, error => {
if (error) {
throw error;
}
assert.strictEqual(server.ipCount.get(ip), 1);
socket.on_disconnect();
assert.strictEqual(server.ipCount.get(ip), 0);
done();
});
});
});
describe('#cookieParsingMiddleware', () => {
it('parses cookies', done => {
socket.request.headers.cookie = 'flavor=chocolate%20chip';
server.cookieParsingMiddleware(socket, () => {
assert.strictEqual(socket.request.cookies.flavor, 'chocolate chip');
done();
});
});
it('defaults to empty objects if no cookies', done => {
server.cookieParsingMiddleware(socket, () => {
assert.deepStrictEqual(socket.request.cookies, {});
assert.deepStrictEqual(socket.request.signedCookies, {});
done();
});
});
});
describe('#ipSessionCookieMiddleware', () => {
// TODO
});
describe('#authUserMiddleware', () => {
// TODO
});
});

View file

@ -6,6 +6,10 @@ var fs = require('fs');
var path = require('path');
describe('JSONFileMetricsReporter', function () {
before(() => {
Metrics.clearReportHooks();
});
describe('#report', function () {
it('reports metrics to file', function (done) {
const outfile = path.resolve(os.tmpdir(),

58
test/util/token-bucket.js Normal file
View file

@ -0,0 +1,58 @@
const assert = require('assert');
const TokenBucket = require('../../lib/util/token-bucket').TokenBucket;
describe('TokenBucket', () => {
describe('#throttle', () => {
let bucket;
beforeEach(() => {
bucket = new TokenBucket(5, 5);
});
it('consumes capacity and then throttles', () => {
assert(!bucket.throttle(), 'should not be empty yet');
assert(!bucket.throttle(), 'should not be empty yet');
assert(!bucket.throttle(), 'should not be empty yet');
assert(!bucket.throttle(), 'should not be empty yet');
assert(!bucket.throttle(), 'should not be empty yet');
assert(bucket.throttle(), 'should be empty now');
});
it('refills tokens', () => {
bucket.count = 0;
const oldRefill = bucket.lastRefill = Date.now() - 1000;
assert(!bucket.throttle(), 'should have refilled');
assert(bucket.lastRefill >= oldRefill + 1000, 'should have updated lastRefill');
});
it('refills at most {capacity} tokens', () => {
bucket.count = 0;
bucket.lastRefill = Date.now() - 10000;
bucket.throttle();
assert.strictEqual(bucket.count, 4);
});
it('does a partial refill', () => {
bucket.count = 0;
bucket.lastRefill = Date.now() - 400;
bucket.throttle();
assert.strictEqual(bucket.count, 1);
});
it('skips refilling if delta = 0', () => {
bucket.count = 0;
const oldRefill = bucket.lastRefill;
bucket.throttle();
assert.strictEqual(bucket.count, 0);
assert.strictEqual(bucket.lastRefill, oldRefill);
});
it('handles fractional refill rates', () => {
bucket = new TokenBucket(5, 0.1);
bucket.count = 0;
assert(bucket.throttle());
bucket.lastRefill = Date.now() - 10000;
assert(!bucket.throttle());
assert.strictEqual(bucket.count, 0);
});
});
});