many tests

This commit is contained in:
Spencer Alger
2013-12-03 19:01:04 -07:00
parent 2ddde47972
commit 4e5f08a29c
19 changed files with 1011 additions and 201 deletions

View File

@ -57,6 +57,6 @@
},
"testling": {
"harness": "mocha",
"files": "test/unit/test_!(http_connector|stdio_logger).js"
"files": "test/unit/test_!(http_connector|stdio_logger|stream_logger|tracer_logger|file_logger).js"
}
}

View File

@ -5,28 +5,53 @@ var path = require('path');
var argv = require('optimist')
.usage([
'Runner for the Elasticsearch.js unit and integration tests in both node and the browser.',
'Specify --no-{{flag}} to negate it.'
'To negate a flag you can use --no-{{flag}}.',
'',
'Examples:',
'',
'# Before a commit (unit tests in Node & Phantom + integration in Chrome & Firefox):',
'npm test --unit --integration --browsers=chrome,firefox',
'',
'# After a change in the rest-spec:',
'npm test --no-unit --integration --browsers=chrome,firefox --check-upstream',
'',
'# During dev (just Node unit tests):',
'npm test --no-browsers',
''
].join('\n'))
.default({
server: true,
unit: true,
integration: false,
host: 'localhost',
port: 9200,
'xml-output': true,
'check-upstream': false,
'browsers': '*'
})
.describe({
host: 'hostname for elasticsearch instance used in integration tests',
'check-upstream': 'check for remote updates to the yaml test suite'
})
.alias({
u: 'unit',
i: 'integration',
b: 'browsers',
s: 'server',
x: 'xml-output'
.options({
server: {
default: true,
alias: 's'
},
unit: {
default: true,
alias: 'u'
},
integration: {
default: false,
alias: 'i'
},
host: {
default: 'localhost',
description: 'hostname for elasticsearch instance used in integration tests'
},
port: {
default: 9200,
alias: 'p'
},
browsers: {
default: '*',
alias: 'b'
},
'xml-output': {
default: true,
alias: 'x'
},
'check-upstream': {
default: false,
description: 'check for remote updates to the yaml test suite'
}
});
if (process.argv.indexOf('help') + process.argv.indexOf('--help') + process.argv.indexOf('-h') !== -3) {
@ -43,6 +68,7 @@ if (process.env.npm_config_argv) {
}
var commands = [];
var command;
if (argv['just-browser']) {
argv.server = false;
@ -50,12 +76,16 @@ if (argv['just-browser']) {
}
if (argv['check-upstream']) {
commands.push(['node', 'scripts/generate/yaml_tests/index.js']);
command = ['node', 'scripts/generate/yaml_tests/index.js'];
if (argv.force) {
command.push('--force');
}
commands.push(command);
}
if (argv.unit) {
if (argv.server) {
commands.push(['mocha', 'test/unit/test_*.js', '--require=should']);
commands.push(['mocha', 'test/unit/test_*.js', '--require should']);
}
if (argv.browsers) {
commands.push(['testling', '.']);

View File

@ -109,7 +109,11 @@ HttpConnector.prototype.request = function (params, cb) {
}
log.trace(params.method, reqParams, params.body, response, status);
cb(err, response, status);
if (err) {
cb(err);
} else {
cb(err, response, status);
}
}, this);
request = this.hand.request(reqParams, function (_incoming) {

View File

@ -12,7 +12,7 @@ function LoggerAbstract(log, config) {
_.makeBoundMethods(this);
// when the log closes, remove our event listeners
this.log.on('closing', this.bound.cleanUpListeners);
this.log.once('closing', this.bound.cleanUpListeners);
this.setupListeners(config.levels);
}
@ -62,14 +62,15 @@ LoggerAbstract.prototype.write = function () {
LoggerAbstract.prototype.setupListeners = function (levels) {
this.cleanUpListeners();
this.listeningLevels = levels;
this.listeningLevels = [];
_.each(this.listeningLevels, function (level) {
_.each(levels, function (level) {
var fnName = 'on' + _.ucfirst(level);
if (this.bound[fnName]) {
this.listeningLevels.push(level);
this.log.on(level, this.bound[fnName]);
} else {
throw new Error(fnName + ' is not a function');
throw new Error('Unable to listen for level "' + level + '"');
}
}, this);
};
@ -144,7 +145,7 @@ LoggerAbstract.prototype.onDebug = _.handler(function (msg) {
* @return {undefined}
*/
LoggerAbstract.prototype.onTrace = _.handler(function (message, curlCall) {
this.write('TRACE', message + '\n' + curlCall);
this.write('TRACE', curlCall + '\n' + message);
});

View File

@ -20,24 +20,11 @@ var LoggerAbstract = require('../logger');
var _ = require('../utils');
var defaultColors = {
error: function (txt) {
return chalk.red.bold(txt);
},
warning: function (txt) {
return chalk.yellow.bold(txt);
},
info: function (txt) {
return chalk.cyan.bold(txt);
},
debug: function (txt) {
return chalk.magenta.bold(txt);
},
trace: function (txt) {
return chalk.white.bold(txt);
},
traceStatus: function (status) {
return chalk[status >= 200 && status < 300 ? 'green' : 'red'].bold(status);
}
error: chalk.red.bold,
warning: chalk.yellow.bold,
info: chalk.cyan.bold,
debug: chalk.magenta.bold,
trace: chalk.white.bold
};
function Stdio(log, config) {

View File

@ -1,7 +1,7 @@
/**
* Logger that writes to a file
*
* @class Loggers.File
* @class Loggers.Stream
* @extends LoggerAbstract
* @constructor
* @see LoggerAbstract
@ -16,7 +16,6 @@ var LoggerAbstract = require('../logger');
var _ = require('../utils');
function Stream(log, config) {
// call my super
LoggerAbstract.call(this, log, config);
if (config.stream && config.stream.write && config.stream.end) {
@ -25,22 +24,21 @@ function Stream(log, config) {
throw new TypeError('Invalid stream, use an instance of stream.Writeable');
}
if (this.stream._writableState && this.stream._writableState.buffer) {
process.on('exit', this.bound.onProcessExit);
}
// else you should probably flush your stream
process.once('exit', this.bound.onProcessExit);
}
_.inherits(Stream, LoggerAbstract);
// flush the write buffer to stderr synchronously
Stream.prototype.onProcessExit = _.handler(function () {
// process is dying, lets manually flush the buffer synchronously to stderr.
var writeBuffer = this.stream._writableState.buffer;
if (writeBuffer && writeBuffer.length) {
console.error('Log stream did not get to finish writing. Flushing to stderr');
writeBuffer.forEach(function (buffered) {
console.error(buffered.chunk.toString());
});
if (this.stream._writableState && this.stream._writableState.buffer) {
var writeBuffer = this.stream._writableState.buffer;
if (writeBuffer.length) {
console.error('Log stream did not get to finish writing. Flushing to stderr');
writeBuffer.forEach(function (buffered) {
console.error(buffered.chunk.toString());
});
}
}
});

View File

@ -1,20 +1,25 @@
var _ = require('./utils');
var extractHostPartsRE = /\[([^:]+):(\d+)\]/;
var extractHostPartsRE = /\[\/*([^:]+):(\d+)\]/;
module.exports = function (nodes) {
var hosts = [];
_.each(nodes, function (node, id) {
var hostnameMatches = extractHostPartsRE.exec(node.http_address);
hosts.push({
host: hostnameMatches[1],
port: hostnameMatches[2],
_meta: {
id: id,
name: node.name,
hostname: node.hostname,
version: node.version
}
function makeNodeParser(hostProp) {
return function (nodes) {
var hosts = [];
_.each(nodes, function (node, id) {
var hostnameMatches = extractHostPartsRE.exec(node[hostProp]);
hosts.push({
host: hostnameMatches[1],
port: parseInt(hostnameMatches[2], 10),
_meta: {
id: id,
name: node.name,
hostname: node.hostname,
version: node.version
}
});
});
});
return hosts;
};
return hosts;
};
}
module.exports = makeNodeParser('http_address');
module.exports.thrift = makeNodeParser('transport_address');

View File

@ -28,6 +28,9 @@ function Transport(config) {
var Serializer = _.funcEnum(config, 'serializer', Transport.serializers, 'json');
this.serializer = new Serializer(config);
// setup the nodesToHostCallback
this.nodesToHostCallback = _.funcEnum(config, 'nodesToHostCallback', Transport.nodesToHostCallbacks, 'main');
// setup max retries
this.maxRetries = config.hasOwnProperty('maxRetries') ? config.maxRetries : 3;
@ -209,7 +212,8 @@ Transport.prototype.createDefer = function () {
* @param {Function} cb - Function to call back once complete
*/
Transport.prototype.sniff = function (cb) {
var self = this;
var connectionPool = this.connectionPool;
var nodesToHostCallback = this.nodesToHostCallback;
// make cb a function if it isn't
cb = typeof cb === 'function' ? cb : _.noop;
@ -217,14 +221,14 @@ Transport.prototype.sniff = function (cb) {
this.request({
path: '/_cluster/nodes',
method: 'GET'
}, function (err, resp) {
}, function (err, resp, status) {
if (!err && resp && resp.nodes) {
var hosts = _.map(self.nodesToHostCallback(resp.nodes), function (hostConfig) {
var hosts = _.map(nodesToHostCallback(resp.nodes), function (hostConfig) {
return new Host(hostConfig);
});
this.connectionPool.setHosts(hosts);
connectionPool.setHosts(hosts);
}
cb(err, resp);
cb(err, resp, status);
});
};

16
test/fixtures/short_node_list.json vendored Normal file
View File

@ -0,0 +1,16 @@
{
"id1": {
"name": "Headknocker",
"transport_address": "inet[/10.10.10.100:9300]",
"hostname": "Spencers-MacBook-Pro.local",
"version": "0.90.5",
"http_address": "inet[/10.10.10.100:9205]"
},
"id2": {
"name": "Buttknocker",
"transport_address": "inet[/10.10.10.101:9300]",
"hostname": "Johns-MacBook-Pro.local",
"version": "0.90.5",
"http_address": "inet[/10.10.10.101:9205]"
}
}

View File

@ -0,0 +1,32 @@
/**
* Simple Mock of the http.IncommingMessage. Just implmenents the methods the methods
* we use
*
* @type {[type]}
*/
module.exports = MockIncommingMessage;
var sinon = require('sinon');
var util = require('util');
var Readable = require('stream').Readable;
function MockIncommingMessage() {
var self = this;
Readable.call(self);
self.setEncoding = sinon.stub();
self._read = function () {};
}
util.inherits(MockIncommingMessage, Readable);
/**
* To make the message "talk" do something like this:
*
* process.nextTick(function () {
* if (resp) {
* incom.push(chunk);
* }
* incom.push(null);
* });
*/

12
test/mocks/request.js Normal file
View File

@ -0,0 +1,12 @@
module.exports = MockRequest;
var sinon = require('sinon');
var util = require('util');
var http = require('http');
function MockRequest() {
sinon.stub(this, 'end');
sinon.stub(this, 'write');
this.log = sinon.stub(this.log);
}
util.inherits(MockRequest, http.ClientRequest);

View File

@ -0,0 +1,18 @@
/**
* Just a buffer really, but one that implements the Writeable class
* @type {WritableStream}
*/
module.exports = MockWritableStream;
var Writable = require('stream').Writable;
var util = require('util');
function MockWritableStream(opts) {
Writable.call(this, opts);
this._write = function (chunk, encoding, cb) {
};
}
util.inherits(MockWritableStream, Writable);

View File

@ -3,6 +3,14 @@ var Host = require('../../src/lib/host');
var sinon = require('sinon');
var _ = require('lodash');
var stubs = [];
afterEach(function () {
var stub;
while (stub = stubs.pop()) {
stub.restore();
}
});
describe('Connection Abstract', function () {
var host = new Host('localhost:9200');
@ -75,6 +83,7 @@ describe('Connection Abstract', function () {
it('sets a timeout when set to dead, and removed when alive', function () {
var clock = sinon.useFakeTimers('setTimeout', 'clearTimeout');
stubs.push(clock);
var conn = new ConnectionAbstract(host);
var start = _.size(clock.timeouts);
@ -103,6 +112,7 @@ describe('Connection Abstract', function () {
it('should ping the connection after the deadTimeout, and set the status to "alive" on pong', function (done) {
var conn = new ConnectionAbstract(host);
var clock = sinon.useFakeTimers('setTimeout', 'clearTimeout');
stubs.push(clock);
// schedules the resuscitate
conn.setStatus('dead');
@ -128,6 +138,7 @@ describe('Connection Abstract', function () {
it('should ping the connection after the deadTimeout, and set the status to "dead" on error', function (done) {
var conn = new ConnectionAbstract(host);
var clock = sinon.useFakeTimers('setTimeout', 'clearTimeout');
stubs.push(clock);
// schedules the resuscitate
conn.setStatus('dead');
@ -142,7 +153,6 @@ describe('Connection Abstract', function () {
// will be called after the ping calls back
conn.setStatus = function (status) {
status.should.eql('dead');
clock.restore();
done();
};

View File

@ -2,8 +2,70 @@ describe('Http Connector', function () {
var should = require('should');
var Host = require('../../src/lib/host');
var errors = require('../../src/lib/errors');
var HttpConnection = require('../../src/lib/connectors/http');
var ConnectionAbstract = require('../../src/lib/connection');
var nock = require('nock');
var sinon = require('sinon');
var util = require('util');
var http = require('http');
var https = require('https');
var MockRequest = require('../mocks/request');
var MockIncommingMessage = require('../mocks/incomming_message');
nock.disableNetConnect();
afterEach(function () {
if (http.request.restore) {
http.request.restore();
}
if (https.request.restore) {
https.request.restore();
}
});
function makeStubReqMethod(prep) {
return function (params, cb) {
var req = new MockRequest();
if (prep) {
prep(req, params, cb);
}
return req;
};
}
function whereReqDies(withErr) {
return function (req) {
process.nextTick(function () {
// causes the request to quit and callback
req.emit('error', withErr || void 0);
});
};
}
function whichMocksMessage(prep) {
return function (req, params, cb) {
process.nextTick(function () {
var incom = new MockIncommingMessage();
if (prep) {
prep(incom);
}
cb(incom);
});
};
}
function whichErrorsAfterPartialBody(err) {
return function (incom) {
incom.statusCode = 200;
incom.push('{ "hits": { "hits": { "hits": { "hits": { "hits": { "hits": ');
setTimeout(function () {
incom.emit('error', err || new Error('Socket is dead now...'));
}, 20);
};
}
describe('Constructor', function () {
it('creates an object that extends ConnectionAbstract', function () {
@ -30,7 +92,6 @@ describe('Http Connector', function () {
});
describe('#makeReqParams', function () {
it('properly reads the host object', function () {
var host = new Host('john:dude@pizza.com:9200/pizza/cheese?shrooms=true');
var con = new HttpConnection(host, {});
@ -133,35 +194,9 @@ describe('Http Connector', function () {
});
describe('#request', function () {
var http = require('http');
var https = require('https');
var sinon = require('sinon');
var util = require('util');
function FakeRequest() {
sinon.stub(this, 'end');
sinon.stub(this, 'write');
this.log = sinon.stub(this.log);
}
util.inherits(FakeRequest, http.ClientRequest);
function reqMethodStub(params, cb) {
var req = new FakeRequest();
process.nextTick(function () {
// causes the request to quit and callback
req.emit('error');
});
return req;
}
beforeEach(function () {
sinon.stub(http, 'request', reqMethodStub);
sinon.stub(https, 'request', reqMethodStub);
});
afterEach(function () {
http.request.restore();
https.request.restore();
sinon.stub(http, 'request', makeStubReqMethod(whereReqDies()));
sinon.stub(https, 'request', makeStubReqMethod(whereReqDies()));
});
it('calls http based on the host', function (done) {
@ -188,14 +223,7 @@ describe('Http Connector', function () {
sinon.stub(con.log);
http.request.restore();
sinon.stub(http, 'request', function (params, cb) {
var req = new FakeRequest();
process.nextTick(function () {
// causes the request to quit and callback
req.emit('error', new Error('actual error'));
});
return req;
});
sinon.stub(http, 'request', makeStubReqMethod(whereReqDies(new Error('actual error'))));
con.request({}, function (err) {
// error should have been sent to the
@ -221,14 +249,7 @@ describe('Http Connector', function () {
sinon.stub(con.log);
http.request.restore();
sinon.stub(http, 'request', function (params, cb) {
var req = new FakeRequest();
process.nextTick(function () {
// causes the request to quit and callback
req.emit('error', new Error('actual error'));
});
return req;
});
sinon.stub(http, 'request', makeStubReqMethod(whereReqDies(new Error('actual error'))));
con.request({}, function (err) {
// error should have been sent to the
@ -246,25 +267,70 @@ describe('Http Connector', function () {
});
});
describe('Request Implementation', function () {
var server;
var nock = require('nock');
nock.disableNetConnect();
var host = new Host('http://esjs.com:9200');
describe('#request with incomming message error', function () {
function makeStubReqWithMsgWhichErrorsMidBody(err) {
return makeStubReqMethod(whichMocksMessage(whichErrorsAfterPartialBody(err)));
}
beforeEach(function () {
server = nock('http://esjs.com:9200');
it('logs error event', function (done) {
var con = new HttpConnection(new Host('https://google.com'));
sinon.stub(con.log, 'error');
sinon.stub(https, 'request', makeStubReqWithMsgWhichErrorsMidBody());
con.request({}, function (err, resp, status) {
con.log.error.callCount.should.eql(1);
done();
});
});
afterEach(function () {
server.done();
nock.restore();
it('and sets the connection to dead', function (done) {
var con = new HttpConnection(new Host('https://google.com'));
sinon.stub(https, 'request', makeStubReqWithMsgWhichErrorsMidBody());
con.request({}, function (err, resp, status) {
con.status.should.eql('dead');
done();
});
});
it('passes the original error on', function (done) {
var con = new HttpConnection(new Host('https://google.com'));
sinon.stub(https, 'request', makeStubReqWithMsgWhichErrorsMidBody(new Error('no more message :(')));
con.request({}, function (err, resp, status) {
should.exist(err);
err.message.should.eql('no more message :(');
done();
});
});
it('does not pass the partial body along', function (done) {
var con = new HttpConnection(new Host('https://google.com'));
sinon.stub(https, 'request', makeStubReqWithMsgWhichErrorsMidBody());
con.request({}, function (err, resp, status) {
should.not.exist(resp);
done();
});
});
it('does not pass the status code along', function (done) {
var con = new HttpConnection(new Host('https://google.com'));
sinon.stub(https, 'request', makeStubReqWithMsgWhichErrorsMidBody());
con.request({}, function (err, resp, status) {
should.not.exist(status);
done();
});
});
});
describe('#request\'s responder', function () {
it('collects the whole request body', function (done) {
var con = new HttpConnection(host);
var server = nock('http://esjs.com:9200');
var con = new HttpConnection(new Host('http://esjs.com:9200'));
var body = '{ "USER": "doc" }';
server
.get('/users/1')
.reply(200, body);
@ -273,34 +339,34 @@ describe('Http Connector', function () {
method: 'GET',
path: '/users/1'
}, function (err, resp, status) {
should(err).not.exist;
should.not.exist(err);
resp.should.eql(body);
status.should.eql(200);
server.done();
done();
});
});
it('Catches network errors and passes back the error', function () {
var con = new HttpConnection(host);
it('Ignores serialization errors', function (done) {
var server = nock('http://esjs.com:9200');
var con = new HttpConnection(new Host('http://esjs.com:9200'));
var body = '{ "USER":';
var body = '{ "USER": "doc" }';
// partial body
server
.get('/users/1')
.reply(200, {
});
.reply(200, body);
con.request({
method: 'GET',
path: '/users/1'
}, function (err, resp, status) {
should(err).not.exist;
should.not.exist(err);
resp.should.eql(body);
status.should.eql(200);
done();
});
});
});
});

250
test/unit/test_logger.js Normal file
View File

@ -0,0 +1,250 @@
describe('Logger Abstract', function () {
var sinon = require('sinon');
var now = new Date('2013-03-01T00:00:00Z');
var Log = require('../../src/lib/log');
var LoggerAbstract = require('../../src/lib/logger');
var parentLog;
var stubs = [];
function stub() {
stubs.push(sinon.stub.apply(sinon, arguments));
}
function makeLogger(levels) {
return new LoggerAbstract(parentLog, {
levels: levels || []
});
}
beforeEach(function () {
parentLog = new Log();
});
afterEach(function () {
var stub;
while (stub = stubs.pop()) {
stub.restore();
}
parentLog.close();
});
describe('Constuctor', function () {
it('calls setupListeners', function () {
stub(LoggerAbstract.prototype, 'setupListeners');
var logger = makeLogger();
logger.setupListeners.callCount.should.eql(1);
});
it('listens for the loggers\' "closing" event', function () {
var logger = makeLogger();
parentLog.listenerCount('closing').should.eql(1);
});
});
describe('#setupListeners', function () {
it('calls cleanUpListeners', function () {
var logger = makeLogger();
stub(logger, 'cleanUpListeners');
logger.setupListeners([]);
logger.cleanUpListeners.callCount.should.eql(1);
});
it('explicitly listens for the events specified', function () {
var logger = makeLogger();
logger.setupListeners(['error']);
parentLog.listenerCount('error').should.eql(1);
parentLog.listenerCount('warning').should.eql(0);
parentLog.listenerCount('info').should.eql(0);
parentLog.listenerCount('debug').should.eql(0);
parentLog.listenerCount('trace').should.eql(0);
logger.setupListeners(['warning', 'trace']);
parentLog.listenerCount('error').should.eql(0);
parentLog.listenerCount('warning').should.eql(1);
parentLog.listenerCount('info').should.eql(0);
parentLog.listenerCount('debug').should.eql(0);
parentLog.listenerCount('trace').should.eql(1);
logger.setupListeners(['debug', 'debug']);
parentLog.listenerCount('error').should.eql(0);
parentLog.listenerCount('warning').should.eql(0);
parentLog.listenerCount('info').should.eql(0);
parentLog.listenerCount('debug').should.eql(2);
parentLog.listenerCount('trace').should.eql(0);
});
it('sets the logLevel property to the new levels', function () {
var logger = makeLogger();
var levels = ['error'];
logger.setupListeners(levels);
logger.listeningLevels.should.eql(levels).and.not.be.exactly(levels);
levels = ['warning', 'trace'];
logger.setupListeners(levels);
logger.listeningLevels.should.eql(levels).and.not.be.exactly(levels);
levels = ['debug', 'debug'];
logger.setupListeners(levels);
logger.listeningLevels.should.eql(levels).and.not.be.exactly(levels);
});
it('rejects listening levels it can not listen to', function () {
var logger = makeLogger();
(function () {
logger.setupListeners(['scream']);
}).should.throw(/unable to listen/i);
});
});
describe('#timestamp', function () {
it('returns in the right format', function () {
stubs.push(sinon.useFakeTimers(now.getTime()));
var logger = makeLogger();
logger.timestamp().should.eql('2013-03-01T00:00:00Z');
});
});
describe('#formate', function () {
it('returns a single string with the message indented', function () {
stubs.push(sinon.useFakeTimers(now.getTime()));
var logger = makeLogger();
logger.format('LABEL', 'MSG').should.eql(
'LABEL: 2013-03-01T00:00:00Z\n' +
' MSG\n' +
'\n'
);
});
it('properly indents multi-line messages', function () {
stubs.push(sinon.useFakeTimers(now.getTime()));
var logger = makeLogger();
logger.format('LABEL', 'MSG\nwith\nseveral lines').should.eql(
'LABEL: 2013-03-01T00:00:00Z\n' +
' MSG\n' +
' with\n' +
' several lines\n' +
'\n'
);
});
});
describe('#write', function () {
it('requires that it is overwritten', function () {
(function () {
var logger = makeLogger();
logger.write();
}).should.throw(/overwritten/);
});
});
describe('#onError', function () {
it('uses the Error name when it is not just "Error"', function () {
var logger = makeLogger();
stub(logger, 'write', function (label, msg) {
label.should.eql('TypeError');
});
logger.onError(new TypeError('Typerr'));
logger.write.callCount.should.eql(1);
});
it('uses "ERROR" when the error name is "Error"', function () {
var logger = makeLogger();
stub(logger, 'write', function (label, msg) {
label.should.eql('ERROR');
});
logger.onError(new Error('thing'));
logger.write.callCount.should.eql(1);
});
});
describe('#onWarning', function () {
it('uses the "WARNING" label', function () {
var logger = makeLogger();
stub(logger, 'write', function (label, msg) {
label.should.eql('WARNING');
});
logger.onWarning('message');
logger.write.callCount.should.eql(1);
});
it('echos the message', function () {
var logger = makeLogger();
stub(logger, 'write', function (label, msg) {
msg.should.eql('message');
});
logger.onWarning('message');
logger.write.callCount.should.eql(1);
});
});
describe('#onInfo', function () {
it('uses the "INFO" label', function () {
var logger = makeLogger();
stub(logger, 'write', function (label, msg) {
label.should.eql('INFO');
});
logger.onInfo('message');
logger.write.callCount.should.eql(1);
});
it('echos the message', function () {
var logger = makeLogger();
stub(logger, 'write', function (label, msg) {
msg.should.eql('message');
});
logger.onInfo('message');
logger.write.callCount.should.eql(1);
});
});
describe('#onDebug', function () {
it('uses the "DEBUG" label', function () {
var logger = makeLogger();
stub(logger, 'write', function (label, msg) {
label.should.eql('DEBUG');
});
logger.onDebug('message');
logger.write.callCount.should.eql(1);
});
it('echos the message', function () {
var logger = makeLogger();
stub(logger, 'write', function (label, msg) {
msg.should.eql('message');
});
logger.onDebug('message');
logger.write.callCount.should.eql(1);
});
});
describe('#onTrace', function () {
it('uses the "TRACE" label', function () {
var logger = makeLogger();
stub(logger, 'write', function (label, msg) {
label.should.eql('TRACE');
});
logger.onTrace('message');
logger.write.callCount.should.eql(1);
});
it('joins the message and curl call with a newline', function () {
var logger = makeLogger();
stub(logger, 'write', function (label, msg) {
msg.should.eql('curlcall\nmessage');
});
logger.onTrace('message', 'curlcall');
logger.write.callCount.should.eql(1);
});
});
});

View File

@ -0,0 +1,32 @@
describe('Nodes to host callback', function () {
var callback = require('../../src/lib/nodes_to_host');
// example node list that would come back from "GET _cluster/nodes"
var nodes = require('../fixtures/short_node_list.json');
it('properly creates host objects', function () {
var hosts = callback(nodes);
hosts.should.have.lengthOf(2);
hosts[0].should.eql({
host: '10.10.10.100',
port: 9205,
_meta: {
id: 'id1',
name: 'Headknocker',
hostname: 'Spencers-MacBook-Pro.local',
version: '0.90.5'
}
});
hosts[1].should.eql({
host: '10.10.10.101',
port: 9205,
_meta: {
id: 'id2',
name: 'Buttknocker',
hostname: 'Johns-MacBook-Pro.local',
version: '0.90.5'
}
});
});
});

View File

@ -1,71 +1,212 @@
var es = require('../../src/elasticsearch');
var Log = require('../../src/lib/log');
var StdioLogger = require('../../src/lib/loggers/stdio');
var _ = require('../../src/lib/utils');
var expect = require('expect.js');
var EventEmitter = require('events').EventEmitter;
var sinon = require('sinon');
var stubs = [];
var parentLog;
beforeEach(function () {
parentLog = new Log();
});
afterEach(function () {
var stub;
while (stub = stubs.pop()) {
stub.restore();
}
parentLog.close();
});
function stub() {
stubs.push(sinon.stub.apply(sinon, arguments));
}
function makeLogger(colors) {
var config = {
levels: Log.parseLevels('trace')
};
if (colors !== void 0) {
config.colors = colors;
}
return new StdioLogger(parentLog, config);
}
describe('Stdio Logger', function () {
var log, logger;
// pulled from chalk's stripColor function.
var hasColorRE = /\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[m|K]/;
function listenerCount(emitter, event) {
if (EventEmitter.listenerCount) {
return EventEmitter.listenerCount(emitter, event);
} else {
return emitter.listeners(event).length;
}
}
describe('pays attention to the level setting', function () {
beforeEach(function () {
log = new Log();
log.emit = function (name/*, ...args */) {
log._emission = {
name: name,
args: Array.prototype.slice(arguments, 1)
};
};
// new logger in warning mode
logger = new StdioLogger(log, {
levels: Log.parseLevels('trace')
});
var logger = makeLogger();
stub(parentLog, 'emit');
});
afterEach(function () {
log.close();
});
it('listenes for all the events', function () {
listenerCount(log, 'error').should.eql(1);
listenerCount(log, 'warning').should.eql(1);
listenerCount(log, 'info').should.eql(1);
listenerCount(log, 'debug').should.eql(1);
listenerCount(log, 'trace').should.eql(1);
it('listens for all the events', function () {
parentLog.listenerCount('error').should.eql(1);
parentLog.listenerCount('warning').should.eql(1);
parentLog.listenerCount('info').should.eql(1);
parentLog.listenerCount('debug').should.eql(1);
parentLog.listenerCount('trace').should.eql(1);
});
it('emits events because something is listening', function () {
log.error(new Error('error message'));
log._emission.name.should.eql('error');
parentLog.error(new Error('error message'));
parentLog.emit.lastCall.args[0].should.eql('error');
log.warning('warning');
log._emission.name.should.eql('warning');
parentLog.warning('warning');
parentLog.emit.lastCall.args[0].should.eql('warning');
log.info('info');
log._emission.name.should.eql('info');
parentLog.info('info');
parentLog.emit.lastCall.args[0].should.eql('info');
log.debug('debug');
log._emission.name.should.eql('debug');
parentLog.debug('debug');
parentLog.emit.lastCall.args[0].should.eql('debug');
log.trace('GET', {}, '', '', 200);
log._emission.name.should.eql('trace');
parentLog.trace('GET', {}, '', '', 200);
parentLog.emit.lastCall.args[0].should.eql('trace');
});
});
describe('colorizing', function () {
var chalk = require('chalk');
var now = '2013-01-01T00:00:00Z';
var nowDate = new Date(now);
var nowTime = nowDate.getTime();
var clock;
beforeEach(function () {
stubs.push(sinon.useFakeTimers(nowTime));
});
it('uses colors when it\'s supported', function () {
var logger = makeLogger();
var hasColor = require('chalk').supportsColor;
logger.color.should.be.exactly(hasColor);
});
it('obeys the logger.color === false', function () {
var logger = makeLogger();
stub(process.stdout, 'write');
var withoutColor = 'INFO: ' + now + '\n something\n\n';
logger.color = false;
logger.onInfo('something');
process.stdout.write.lastCall.args[0].should.eql(withoutColor);
});
it('obeys the logger.color === true', function () {
var logger = makeLogger(false);
stub(process.stdout, 'write');
var withoutColor = 'TRACE: ' + now + '\n curl\n msg\n\n';
logger.color = true;
logger.onTrace('msg', 'curl');
process.stdout.write.lastCall.args[0].should.not.eql(withoutColor);
chalk.stripColor(process.stdout.write.lastCall.args[0]).should.eql(withoutColor);
});
});
describe('#onError', function () {
it('uses the Error name when it is not just "Error"', function () {
var logger = makeLogger();
stub(logger, 'write', function (to, label, colorize, msg) {
label.should.eql('TypeError');
});
logger.onError(new TypeError('Typerr'));
logger.write.callCount.should.eql(1);
});
it('uses "ERROR" when the error name is "Error"', function () {
var logger = makeLogger();
stub(logger, 'write', function (to, label, colorize, msg) {
label.should.eql('ERROR');
});
logger.onError(new Error('thing'));
logger.write.callCount.should.eql(1);
});
});
describe('#onWarning', function () {
it('uses the "WARNING" label', function () {
var logger = makeLogger();
stub(logger, 'write', function (to, label, colorize, msg) {
label.should.eql('WARNING');
});
logger.onWarning('message');
logger.write.callCount.should.eql(1);
});
it('echos the message', function () {
var logger = makeLogger();
stub(logger, 'write', function (to, label, colorize, msg) {
msg.should.eql('message');
});
logger.onWarning('message');
logger.write.callCount.should.eql(1);
});
});
describe('#onInfo', function () {
it('uses the "INFO" label', function () {
var logger = makeLogger();
stub(logger, 'write', function (to, label, colorize, msg) {
label.should.eql('INFO');
});
logger.onInfo('message');
logger.write.callCount.should.eql(1);
});
it('echos the message', function () {
var logger = makeLogger();
stub(logger, 'write', function (to, label, colorize, msg) {
msg.should.eql('message');
});
logger.onInfo('message');
logger.write.callCount.should.eql(1);
});
});
describe('#onDebug', function () {
it('uses the "DEBUG" label', function () {
var logger = makeLogger();
stub(logger, 'write', function (to, label, colorize, msg) {
label.should.eql('DEBUG');
});
logger.onDebug('message');
logger.write.callCount.should.eql(1);
});
it('echos the message', function () {
var logger = makeLogger();
stub(logger, 'write', function (to, label, colorize, msg) {
msg.should.eql('message');
});
logger.onDebug('message');
logger.write.callCount.should.eql(1);
});
});
describe('#onTrace', function () {
it('uses the "TRACE" label', function () {
var logger = makeLogger();
stub(logger, 'write', function (to, label, colorize, msg) {
label.should.eql('TRACE');
});
logger.onTrace('message');
logger.write.callCount.should.eql(1);
});
it('joins the message and curl call with a newline', function () {
var logger = makeLogger();
stub(logger, 'write', function (to, label, colorize, msg) {
msg.should.eql('curlcall\nmessage');
});
logger.onTrace('message', 'curlcall');
logger.write.callCount.should.eql(1);
});
});
});

View File

@ -0,0 +1,106 @@
var Log = require('../../src/lib/log');
var StreamLogger = require('../../src/lib/loggers/stream');
var MockWritableStream = require('../mocks/writable_stream');
var once = require('events').EventEmitter.prototype.once;
var write = require('stream').Writable.prototype.write;
var sinon = require('sinon');
var stubs = [];
var parentLog;
var stream = new MockWritableStream();
var _ = require('lodash');
var util = require('util');
beforeEach(function () {
parentLog = new Log();
stub(stream, 'write', function () { console.log('stubbed write'); });
stub(stream, 'end', function () { console.log('stubbed close'); });
});
afterEach(function () {
parentLog.close();
var stub;
while (stub = stubs.pop()) {
stub.restore();
}
});
function stub() {
stubs.push(sinon.stub.apply(sinon, arguments));
}
function makeLogger() {
var config = {
levels: Log.parseLevels('trace'),
stream: stream
};
return new StreamLogger(parentLog, config);
}
describe('Stream Logger', function () {
describe('pays attention to the level setting', function () {
beforeEach(function () {
var logger = makeLogger();
stub(parentLog, 'emit');
});
it('listens for all the events', function () {
parentLog.listenerCount('error').should.eql(1);
parentLog.listenerCount('warning').should.eql(1);
parentLog.listenerCount('info').should.eql(1);
parentLog.listenerCount('debug').should.eql(1);
parentLog.listenerCount('trace').should.eql(1);
});
it('emits events because something is listening', function () {
parentLog.error(new Error('error message'));
parentLog.emit.lastCall.args[0].should.eql('error');
parentLog.warning('warning');
parentLog.emit.lastCall.args[0].should.eql('warning');
parentLog.info('info');
parentLog.emit.lastCall.args[0].should.eql('info');
parentLog.debug('debug');
parentLog.emit.lastCall.args[0].should.eql('debug');
parentLog.trace('GET', {}, '', '', 200);
parentLog.emit.lastCall.args[0].should.eql('trace');
});
});
describe('buffer flushing', function () {
it('writes everything in the buffer to console.error', function () {
var onExitCallback;
sinon.stub(process, 'once', function (evName, cb) {
if (evName === 'exit') {
onExitCallback = cb;
process.once.restore();
} else {
once.call(process, evName, cb);
}
});
var logger = makeLogger();
var line = 'This string is repeated 10 times to create buffered output.\n';
_.times(10, function () {
write.call(stream, line);
});
var flushedOutput = '';
sinon.stub(console, 'error', function (str) {
flushedOutput += str;
});
onExitCallback();
console.error.restore();
// empty the buffer manually
stream._writableState.buffer.splice(0);
flushedOutput.match(new RegExp(line, 'g')).length.should.above(0);
});
});
});

View File

@ -1,9 +1,24 @@
var Transport = require('../../src/lib/transport');
var Host = require('../../src/lib/host');
var sinon = require('sinon');
var nodeList = require('../fixtures/short_node_list.json');
var stubs = [];
function stub() {
stubs.push(sinon.stub.apply(sinon, arguments));
}
afterEach(function () {
var stub;
while (stub = stubs.pop()) {
stub.restore();
}
});
describe('Transport Class', function () {
describe('Constructor', function () {
it('Accepts a log class and intanciates it at this.log', function () {
function CustomLogClass() {}
var trans = new Transport({
@ -73,7 +88,90 @@ describe('Transport Class', function () {
});
}).should.throw(/invalid logclass/i);
});
});
describe('#sniff', function () {
var trans;
beforeEach(function () {
trans = new Transport();
stub(trans, 'request', function (params, cb) {
process.nextTick(function () {
cb(void 0, {
ok: true,
cluster_name: 'clustername',
nodes: nodeList
}, 200);
});
});
stub(trans.connectionPool, 'setHosts');
});
it('works without a callback', function (done) {
trans.sniff();
setTimeout(function () {
trans.request.callCount.should.eql(1);
done();
}, 5);
});
it('calls the nodesToHostCallback with the list of nodes', function (done) {
trans.nodesToHostCallback = function (nodes) {
nodes.should.eql(nodeList);
done();
return [];
};
trans.sniff();
});
it('takes the host configs, converts them into Host objects, and passes them to connectionPool.setHosts',
function (done) {
trans.sniff(function () {
trans.connectionPool.setHosts.callCount.should.eql(1);
var hosts = trans.connectionPool.setHosts.lastCall.args[0];
hosts.should.have.length(2);
hosts[0].should.be.an.instanceOf(Host);
hosts[0].host.should.eql('10.10.10.100');
hosts[0].port.should.eql(9205);
hosts[0].should.be.an.instanceOf(Host);
hosts[1].host.should.eql('10.10.10.101');
hosts[1].port.should.eql(9205);
done();
});
});
it('passed back errors caught from the request', function (done) {
trans.request.func = function (params, cb) {
process.nextTick(function () {
cb(new Error('something funked up'));
});
};
trans.sniff(function (err) {
err.message.should.eql('something funked up');
done();
});
});
it('passed back the full server response', function (done) {
trans.sniff(function (err, resp, status) {
resp.should.include({
ok: true,
cluster_name: 'clustername'
});
done();
});
});
it('passed back the server response code', function (done) {
trans.sniff(function (err, resp, status) {
status.should.eql(200);
done();
});
});
});
});