Skip to content

Commit

Permalink
Add tests for server.js
Browse files Browse the repository at this point in the history
  • Loading branch information
mantoni committed Aug 21, 2018
1 parent b7739ec commit 97f1e10
Show file tree
Hide file tree
Showing 3 changed files with 225 additions and 15 deletions.
8 changes: 3 additions & 5 deletions lib/linter.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ function translateOptions(cliOptions, cwd) {

const eslintCache = new LRU(10);

function linter(cwd, args, text) {
exports.lint = function (cwd, args, text) {
process.chdir(cwd);
let cwdDeps = eslintCache.get(cwd);
if (!cwdDeps) {
Expand Down Expand Up @@ -116,9 +116,9 @@ function linter(cwd, args, text) {
output += '\n# exit 1';
}
return output;
}
};

linter.getStatus = function () {
exports.getStatus = function () {
const { keys } = eslintCache;
if (keys.length === 0) {
return 'Running, no instances cached';
Expand All @@ -128,5 +128,3 @@ linter.getStatus = function () {
}
return `Running, ${keys.length} instances cached`;
};

module.exports = linter;
21 changes: 11 additions & 10 deletions lib/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,19 @@ const net = require('net');
const portfile = require('./portfile');
const linter = require('./linter');

function forceClose(con) {
try {
con.end('Server is stopping...\n# exit 1');
} catch (ignore) {
// Nothing we can do
}
}

exports.start = function () {

const token = crypto.randomBytes(8).toString('hex');

let openConnections = [];

function forceClose(con) {
con.write('Server is stopping...\n# exit 1');
con.end();
}

const server = net.createServer({
allowHalfOpen: true
}, (con) => {
Expand All @@ -38,14 +40,13 @@ exports.start = function () {
}
data = data.substring(p + 1);
if (data === 'stop') {
openConnections.forEach(forceClose);
con.end();
server.close();
openConnections.forEach(forceClose);
return;
}
if (data === 'status') {
con.write(linter.getStatus());
con.end();
con.end(linter.getStatus());
return;
}
let cwd, args, text;
Expand All @@ -65,7 +66,7 @@ exports.start = function () {
args = parts.slice(1);
}
try {
con.write(linter(cwd, args, text));
con.write(linter.lint(cwd, args, text));
} catch (e) {
con.write(`${e.toString()}\n# exit 1`);
}
Expand Down
211 changes: 211 additions & 0 deletions test/server-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
/*eslint-env mocha*/
'use strict';

const net = require('net');
const crypto = require('crypto');
const EventEmitter = require('events');
const { assert, refute, sinon } = require('@sinonjs/referee-sinon');
const server = require('../lib/server');
const linter = require('../lib/linter');
const portfile = require('../lib/portfile');

function createConnection() {
const connection = new EventEmitter();
connection.write = sinon.fake();
connection.end = sinon.fake();
return connection;
}

describe('server', () => {
const token = 'c2d003e2b9de9e70';
let net_server;
let connection;

beforeEach(() => {
net_server = new EventEmitter();
net_server.listen = sinon.fake();
net_server.close = sinon.fake();
net_server.address = sinon.fake.returns({ port: 8765 });
connection = createConnection();
sinon.replace(net, 'createServer', sinon.fake.returns(net_server));
sinon.replace(portfile, 'write', sinon.fake());
sinon.replace(crypto, 'randomBytes',
sinon.fake.returns(Buffer.from(token, 'hex')));
});

afterEach(() => {
sinon.restore();
});

function start() {
server.start();
net_server.listen.callback();
}

function connect(connection) {
net_server.emit('connection', connection);
net.createServer.callback(connection);
}

function request(connection, text) {
connect(connection);
if (text) {
connection.emit('data', text);
}
connection.emit('end');
}

it('starts server and listens on random port', () => {
const instance = server.start();

assert.equals(instance, net_server);
assert.calledOnceWith(net.createServer, {
allowHalfOpen: true
}, sinon.match.func);
assert.calledOnceWith(net_server.listen, 0, '127.0.0.1', sinon.match.func);
});

it('writes portfile when listen yields', () => {
server.start();

net_server.listen.callback();

assert.calledOnceWith(crypto.randomBytes, 8);
assert.calledOnceWith(portfile.write, 8765, token);
});

it('closes connection without writing anything for empty request', () => {
start();

request(connection);

refute.called(connection.write);
assert.calledOnce(connection.end);
});

describe('stop', () => {

it('closes connection and server for "stop" command', () => {
start();

request(connection, `${token} stop`);

refute.called(connection.write);
assert.calledOnce(connection.end);
assert.calledOnce(net_server.close);
});

it('closes any other pending connection on "stop" command', () => {
start();
const one = createConnection();
const two = createConnection();
connect(one);
connect(two);

request(connection, `${token} stop`);

assert.calledOnceWith(one.end, 'Server is stopping...\n# exit 1');
assert.calledOnceWith(two.end, 'Server is stopping...\n# exit 1');
assert.calledOnce(connection.end);
assert.calledWithExactly(connection.end);
});

it('ignores failures when attempting to close client connection', () => {
start();
const one = createConnection();
one.end = sinon.fake.throws(new Error());
const two = createConnection();
two.end = sinon.fake.throws(new Error());

connect(one);
connect(two);

request(connection, `${token} stop`);

assert.calledOnce(one.end);
assert.calledOnce(two.end);
assert.calledOnce(connection.end);
assert.calledOnce(net_server.close);
});

it('does not process "stop" if token is invalid', () => {
start();

request(connection, '123456789abcdef status');

assert.calledOnce(connection.end);
assert.calledWithExactly(connection.end);
refute.called(connection.write);
refute.called(net_server.close);
});

});

describe('status', () => {

it('prints linter status and closes connection', () => {
sinon.replace(linter, 'getStatus', sinon.fake.returns('Oh, hi!\n'));
start();

request(connection, `${token} status`);

assert.calledOnceWith(connection.end, 'Oh, hi!\n');
});

it('does not process "status" if token is invalid', () => {
sinon.replace(linter, 'getStatus', sinon.fake());
start();

request(connection, '123456789abcdef status');

assert.calledOnce(connection.end);
assert.calledWithExactly(connection.end);
refute.called(connection.write);
refute.called(linter.getStatus);
});

});

describe('lint', () => {
const json = {
cwd: '/some/path',
args: ['--some', '--args'],
text: '"Some text"'
};

it('invokes linter with JSON arguments', () => {
sinon.replace(linter, 'lint', sinon.fake.returns('Oh, hi!\n'));
start();

request(connection, `${token} ${JSON.stringify(json)}`);

assert.calledOnceWith(linter.lint, json.cwd, json.args, json.text);
assert.calledOnceWith(connection.write, 'Oh, hi!\n');
assert.calledOnce(connection.end);
});

it('invokes linter with plain text arguments', () => {
sinon.replace(linter, 'lint', sinon.fake.returns('Oh, hi!\n'));
start();

request(connection,
`${token} ${json.cwd} ${json.args.join(' ')}\n${json.text}`);

assert.calledOnceWith(linter.lint, json.cwd, json.args, json.text);
assert.calledOnceWith(connection.write, 'Oh, hi!\n');
assert.calledOnce(connection.end);
});

it('handles exception from linter', () => {
sinon.replace(linter, 'lint', sinon.fake.throws(new Error('Whatever')));
start();

request(connection, `${token} ${JSON.stringify(json)}`);

assert.calledOnceWith(connection.write, 'Error: Whatever\n# exit 1');
assert.calledOnce(connection.end);
});

});

});

0 comments on commit 97f1e10

Please sign in to comment.