save point durring huge unorganized refactor

This commit is contained in:
Spencer Alger
2013-11-22 16:48:30 -07:00
parent 5bb70fbe58
commit 97ba084795
80 changed files with 46126 additions and 2410 deletions

40
test/unit/.jshintrc Normal file
View File

@ -0,0 +1,40 @@
{
"node": true,
"white": true,
"bitwise": false,
"curly": true,
"eqnull": true,
"eqeqeq": true,
"forin": true,
"immed": true,
"expr": true,
"indent": 2,
"latedef": "nofunc",
"newcap": true,
"noarg": true,
"noempty": true,
"undef": true,
"quotmark": "single",
"plusplus": false,
"boss": true,
"trailing": true,
"laxbreak": true,
"laxcomma": true,
"validthis": true,
"sub": true,
"maxlen": 140,
"-W084": true,
"maxerr": 10,
"-W030": true,
"-W068": true,
"globals": {
"describe": true,
"before": true,
"after": true,
"it": true,
"beforeEach": true,
"afterEach": true
}
}

View File

@ -1,16 +0,0 @@
var es = require('../../src/elasticsearch'),
api = require('../../src/lib/api'),
expect = require('expect.js');
describe('Client instances creation', function () {
var client;
beforeEach(function () {
client = new es.Client();
});
it('inherits the api', function () {
client.bulk.should.eql(api.bulk);
client.cluster.nodeStats.should.eql(api.cluster.prototype.nodeStats);
});
});

View File

@ -1,16 +0,0 @@
var es = require('../../src/elasticsearch');
describe('Connection Pool', function () {
var client, pool;
beforeEach(function () {
client = new es.Client();
pool = client.config.connectionPool;
});
describe('default settings', function () {
it('by default has one living connection and no dead connections', function () {
pool.connections.alive.should.have.lengthOf(1);
pool.connections.dead.should.have.lengthOf(0);
});
});
});

View File

@ -1,80 +0,0 @@
var EsServer = require('../mocks/es_server');
var _ = require('../../src/lib/utils');
var http = require('http');
describe('EsServer Mock', function () {
it('should emit an online event when ready, passing it\'s port number', function (done) {
var server = new EsServer();
server.on('online', function (port) {
port.should.have.type('number');
server.shutdown(done);
});
});
describe('when it\'s online', function () {
var server;
var port;
function makeRequest(opts, respCb) {
opts = _.defaults(opts || {}, {
host: 'localhost',
port: port
});
var response = null;
var req = http.request(opts, function (incomming) {
response = '';
incomming.on('data', function (chunk) {
response += chunk;
});
incomming.on('end', function () {
if (incomming.headers['content-type'] === 'application/json') {
try {
respCb(null, JSON.parse(response), incomming.statusCode);
} catch (e) {
respCb(e, response, incomming.statusCode);
}
} else {
respCb(null, response, incomming.statusCode);
}
});
});
req.on('error', respCb);
req.end();
}
before(function (done) {
server = new EsServer();
server.on('online', function (_port) {
port = _port;
done();
});
});
after(function (done) {
server.shutdown(done);
});
it('should respond with json to a ping', function (done) {
makeRequest({
path: '/'
}, function (err, resp, status) {
if (err) {
done(err);
} else {
status.should.be.exactly(200);
resp.version.number.should.match(/^\d+\.\d+\.\d+/);
done();
}
});
});
});
});

View File

@ -1,39 +0,0 @@
describe('Http Connector', function () {
var Host = require('../../src/lib/host');
var HttpConnection = require('../../src/lib/connectors/http');
var host = new Host('http://someesserver.com:9205/prefix');
var con;
describe('#makeReqParams', function () {
before(function () {
con = new HttpConnection(host, {});
});
it('creates the request params property', function () {
var reqParams = con.makeReqParams({
method: 'GET',
path: '/_cluster/nodes/stats',
query: {
jvm: true
}
});
reqParams.should.include({
method: 'GET',
protocol: 'http:',
auth: '',
hostname: 'someesserver.com',
port: '9205',
path: '/prefix/_cluster/nodes/stats?jvm=true'
});
Object.keys(reqParams).should.not.include([
'host', 'pathname', 'query'
]);
});
});
});

View File

@ -1,70 +0,0 @@
var Log = require('../../src/lib/log');
exports['Log::parseLevels'] = {
'parses a string': function (test) {
test.deepEqual(Log.parseLevels('warning'), ['error', 'warning']);
test.done();
},
'filters an array': function (test) {
test.deepEqual(Log.parseLevels(['trace', 'not a level']), ['trace']);
test.done();
},
'returns nothing as a defauls': function (test) {
test.ok(!Log.parseLevels());
test.done();
}
};
exports['Log::join'] = {
'joins strings': function (test) {
test.equal(Log.join(['one', 'two']), 'one two');
test.done();
},
'flattens nested arrays': function (test) {
test.equal(Log.join(['one', ['three', 'four']]), 'one three,four');
test.done();
},
'flattens arguments': function (test) {
(function() {
test.equal(Log.join(arguments), 'one two');
}('one', 'two'));
test.done();
}
};
/**
* Empty log bridge (no outputs)
* @type {Log}
*/
var log;
exports['Log instance with no outputs'] = {
setUp: function (done) {
log = new Log([]);
done();
},
tearDown: function (done) {
done();
},
'should not emit any events': function (test) {
log.emit = function () {
test.ok(false, 'Emit should not have been called');
};
log.error('Error Message');
test.done();
}
};

View File

@ -1,67 +0,0 @@
var es = require('../../src/elasticsearch'),
Stdio = require('../../src/lib/loggers/stdio'),
_ = require('../../src/lib/utils'),
expect = require('expect.js');
describe('Stdio Logger listening to levels warning and error', function () {
var client, log, logger;
before(function () {
client = new es.Client({
log: []
});
log = client.config.log;
});
beforeEach(function () {
if (logger) {
logger.cleanUpListeners();
}
// new logger in warning mode
logger = new Stdio({
levels: ['error', 'warning']
}, log);
});
it('logs error messages', function (done) {
logger.write = function (to, label, colorize, what) {
label.should.eql('ERROR');
what.should.have.type('string');
what.should.match(/^Error: Test Error Message/);
done();
};
log.error('Test Error Message');
});
it('logs warning messages', function (done) {
logger.write = function (to, label, colorize, what) {
expect(label).to.be('WARNING');
expect(what).to.be('Test Warning Message');
done();
};
log.warning('Test Warning', 'Message');
});
it('does not log info messages', function () {
if (log.info('Info')) {
throw new Error('There shouldn\'t be listeners for info logs');
}
});
it('does not log debug messages', function () {
if (log.debug('Debug')) {
throw new Error('There shouldn\'t be listeners for debug logs');
}
});
it('does not log trace messages', function () {
if (log.trace('curl "http://localhost:9200" -d "{ \"query\": ... }"')) {
throw new Error('There shouldn\'t be listeners for trace logs');
}
});
});

35
test/unit/test_client.js Normal file
View File

@ -0,0 +1,35 @@
var es = require('../../src/elasticsearch');
var api = require('../../src/lib/api');
describe('Client instances creation', function () {
var client;
beforeEach(function () {
if (client) {
client.close();
}
client = new es.Client();
});
it('inherits the api', function () {
client.bulk.should.eql(api.bulk);
client.cluster.nodeStats.should.eql(api.cluster.prototype.nodeStats);
});
it('closing the client causes it\'s transport to be closed', function () {
var called = false;
client.transport.close = function () {
called = true;
};
client.close();
called.should.be.exactly(true);
});
it('creates a warning level logger by default', function () {
client.transport.log.listenerCount('error').should.eql(1);
client.transport.log.listenerCount('warning').should.eql(1);
client.transport.log.listenerCount('info').should.eql(0);
client.transport.log.listenerCount('debug').should.eql(0);
client.transport.log.listenerCount('trace').should.eql(0);
});
});

View File

@ -0,0 +1,816 @@
var ca = require('../../src/lib/client_action');
var should = require('should');
var _ = require('lodash');
var when = require('when');
/**
* Creates a simple mock of the client, whose "transport" has a request
* function that just calls back with the parameters it received
*
* @return {Object}
*/
function mockClient() {
return {
transport: {
request: function (params, cb) {
if (typeof cb === 'function') {
process.nextTick(function () {
cb(void 0, params);
});
} else {
return when.resolve(params);
}
}
}
};
}
/**
* Creates a client action, ensuring that is has some default url specs, and binds it to
* a mock client.
*
* @param {Object} spec - the spec for the client action
* @return {Function} - the client action
*/
function makeClientAction(spec) {
spec = spec || {};
if (!spec.urls && !spec.url) {
spec.url = {
fmt: '/'
};
}
return _.bind(ca(spec), mockClient());
}
/**
* Calls ca.proxy and binds it to a mock client
* @param {Function} fn - the function to proxy
* @param {Object} spec - The spec for the proxy
* @return {Function} - the clientActionProxy
*/
function makeClientActionProxy(fn, spec) {
return _.bind(ca.proxy(fn, spec || {}), mockClient());
}
describe('Client Action runner', function () {
var action;
describe('argument juggling', function () {
it('creates an empty param set when no params are sent', function (done) {
action = makeClientAction();
// note: the first arg is the callback
action(function (err, params) {
params.query.should.eql({});
done();
});
});
});
describe('clientAction::proxy', function () {
it('proxies to the passed function', function () {
var action = makeClientActionProxy(function (params, cb) {
throw new Error('proxy function called');
});
(function () {
action({}, function () {});
}).should.throw('proxy function called');
});
it('provides the proper context', function (done) {
var client;
var action = makeClientActionProxy(function (params, cb) {
client = this;
process.nextTick(function () {
cb(void 0, params);
});
});
action({}, function (err, params) {
client.transport.request.should.be.type('function');
done();
});
});
it('handles passing just the callback', function () {
var action = makeClientActionProxy(function (params, cb) {
should(_.isObject(params)).be.ok;
cb.should.be.type('function');
});
action(function () {});
});
it('supports a param transformation function', function () {
var action = makeClientActionProxy(function (params, cb) {
params.should.have.property('transformed');
}, {
transform: function (params) {
params.transformed = true;
}
});
action(function () {});
});
it('returns the proxied function\'s return value', function () {
var football = {};
var action = makeClientActionProxy(function (params, cb) {
return football;
});
action().should.be.exactly(football);
});
});
describe('param casting', function () {
describe('duration', function () {
beforeEach(function () {
action = makeClientAction({
params: {
one: {
type: 'duration'
},
two: {
type: 'duration'
},
three: {
type: 'duration'
},
four: {
type: 'duration'
}
}
});
});
it('accepts a number, string, or interval', function (done) {
action({
one: 1500,
two: '500',
three: '15m'
}, function (err, params) {
if (err) { throw err; }
params.query.one.should.eql(1500);
params.query.two.should.eql('500');
params.query.three.should.eql('15m');
done();
});
});
it('rejects date values', function (done) {
action({
one: new Date()
}, function (err, params) {
err.should.be.an.instanceOf(TypeError);
done();
});
});
it('rejects array', function (done) {
action({
one: ['one'],
two: [ 1304 ]
}, function (err, params) {
err.should.be.an.instanceOf(TypeError);
done();
});
});
it('rejects object', function (done) {
action({
one: { but: 'duration' }
}, function (err, params) {
err.should.be.an.instanceOf(TypeError);
done();
});
});
});
describe('list', function () {
beforeEach(function () {
action = makeClientAction({
params: {
one: { type: 'list' },
two: { type: 'list' },
three: { type: 'list' }
}
});
});
it('accepts a string, number, or array', function (done) {
action({
one: 'some,strings',
two: 1430,
three: ['some', 'strings'],
}, function (err, params) {
if (err) { throw err; }
params.query.should.eql({
one: 'some,strings',
two: 1430,
three: 'some,strings'
});
done();
});
});
it('it rejects regexp', function (done) {
action({
one: /regexp!/g
}, function (err, params) {
err.should.be.an.instanceOf(TypeError);
done();
});
});
it('it rejects objects', function (done) {
action({
one: {
pasta: 'sauce'
}
}, function (err, params) {
err.should.be.an.instanceOf(TypeError);
done();
});
});
});
describe('enum', function () {
beforeEach(function () {
action = makeClientAction({
params: {
one: { type: 'enum', options: ['opt', 'other opt', '150'] }
}
});
});
it('accepts any value in the list', function (done) {
action({
one: 'opt'
}, function (err, params) {
if (err) { throw err; }
params.query.one.should.eql('opt');
done();
});
});
it('accepts any value kind of in the list', function (done) {
action({
one: 150
}, function (err, params) {
if (err) { throw err; }
params.query.one.should.be.exactly('150');
done();
});
});
it('it rejects things not in the list', function (done) {
action({
one: 'not an opt'
}, function (err, params) {
err.should.be.an.instanceOf(TypeError);
done();
});
});
});
describe('boolean', function () {
beforeEach(function () {
action = makeClientAction({
params: {
one: { type: 'boolean' },
two: { type: 'boolean' },
three: { type: 'boolean' },
four: { type: 'boolean' },
five: { type: 'boolean' },
six: { type: 'boolean' },
}
});
});
it('casts "off", "no", and other falsey things to false', function (done) {
action({
one: 'off',
two: 'no',
three: false,
four: ''
}, function (err, params) {
if (err) { throw err; }
should(params.query.one).be.exactly(false);
should(params.query.two).be.exactly(false);
should(params.query.three).be.exactly(false);
should(params.query.four).be.exactly(false);
done();
});
});
it('cast most everything else to true', function (done) {
action({
one: 'yes',
two: 'ok',
three: true,
four: 1,
five: new Date(),
six: {}
}, function (err, params) {
if (err) { throw err; }
should(params.query.one).be.exactly(true);
should(params.query.two).be.exactly(true);
should(params.query.three).be.exactly(true);
should(params.query.four).be.exactly(true);
should(params.query.five).be.exactly(true);
should(params.query.six).be.exactly(true);
done();
});
});
});
describe('number', function () {
beforeEach(function () {
action = makeClientAction({
params: {
one: { type: 'number' },
two: { type: 'number' },
three: { type: 'number' },
four: { type: 'number' },
five: { type: 'number' },
six: { type: 'number' },
}
});
});
it('casts integers properly', function (done) {
action({
one: '42',
two: '-69',
three: 15,
four: -100,
five: '0xFF',
six: 0xFFF
}, function (err, params) {
if (err) { throw err; }
params.query.one.should.equal(42);
params.query.two.should.equal(-69);
params.query.three.should.equal(15);
params.query.four.should.equal(-100);
params.query.five.should.equal(255);
params.query.six.should.equal(4095);
done();
});
});
it('casts floats properly', function (done) {
action({
one: '-1.6',
two: '4.536',
three: -2.6,
four: 3.1415,
five: 8e5,
six: '123e-2',
}, function (err, params) {
if (err) { throw err; }
params.query.one.should.equal(-1.6);
params.query.two.should.equal(4.536);
params.query.three.should.equal(-2.6);
params.query.four.should.equal(3.1415);
params.query.five.should.equal(800000);
params.query.six.should.equal(1.23);
done();
});
});
it('rejects dates', function (done) {
action({
one: new Date()
}, function (err, params) {
err.should.be.an.instanceOf(TypeError);
done();
});
});
it('rejects objects', function (done) {
action({
one: {}
}, function (err, params) {
err.should.be.an.instanceOf(TypeError);
done();
});
});
it('rejects arrays', function (done) {
action({
one: []
}, function (err, params) {
err.should.be.an.instanceOf(TypeError);
done();
});
});
it('rejects regexp', function (done) {
action({
one: /pasta/g
}, function (err, params) {
err.should.be.an.instanceOf(TypeError);
done();
});
});
});
describe('string', function () {
beforeEach(function () {
action = makeClientAction({
params: {
one: { type: 'string' },
two: { type: 'string' },
three: { type: 'string' },
four: { type: 'string' },
five: { type: 'string' },
six: { type: 'string' },
}
});
});
it('accepts numbers and strings', function (done) {
action({
one: '42',
two: '-69',
three: 15,
four: -100,
five: '0xFF',
six: 0xFFF
}, function (err, params) {
if (err) { throw err; }
params.query.one.should.equal('42');
params.query.two.should.equal('-69');
params.query.three.should.equal('15');
params.query.four.should.equal('-100');
params.query.five.should.equal('0xFF');
params.query.six.should.equal('4095');
done();
});
});
it('rejects dates', function (done) {
action({
one: new Date()
}, function (err, params) {
err.should.be.an.instanceOf(TypeError);
done();
});
});
it('rejects objects', function (done) {
action({
one: {}
}, function (err, params) {
err.should.be.an.instanceOf(TypeError);
done();
});
});
it('rejects arrays', function (done) {
action({
one: []
}, function (err, params) {
err.should.be.an.instanceOf(TypeError);
done();
});
});
it('rejects regexp', function (done) {
action({
one: /pasta/g
}, function (err, params) {
err.should.be.an.instanceOf(TypeError);
done();
});
});
});
describe('time', function () {
beforeEach(function () {
action = makeClientAction({
params: {
one: { type: 'time' },
two: { type: 'time' },
three: { type: 'time' },
four: { type: 'time' },
five: { type: 'time' },
six: { type: 'time' },
}
});
});
it('accepts numbers, strings, and dates', function (done) {
var now = new Date();
action({
one: '42',
two: '-69',
three: 15,
four: now,
five: new Date(999, 2399, 152433)
}, function (err, params) {
if (err) { throw err; }
params.query.one.should.equal('42');
params.query.two.should.equal('-69');
params.query.three.should.equal('15');
params.query.four.should.equal('' + now.getTime());
params.query.five.should.equal('-11162941200000');
done();
});
});
it('rejects objects', function (done) {
action({
one: {}
}, function (err, params) {
err.should.be.an.instanceOf(TypeError);
done();
});
});
it('rejects arrays', function (done) {
action({
one: []
}, function (err, params) {
err.should.be.an.instanceOf(TypeError);
done();
});
});
it('rejects regexp', function (done) {
action({
one: /pasta/g
}, function (err, params) {
err.should.be.an.instanceOf(TypeError);
done();
});
});
});
});
describe('passing of control params from spec', function () {
it('passes bulkBody', function (done) {
var action = makeClientAction({
bulkBody: true
});
action({}, function (err, params) {
params.bulkBody.should.be.exactly(true);
done();
});
});
it('passes castExists', function (done) {
var action = makeClientAction({
castExists: true
});
action({}, function (err, params) {
params.castExists.should.be.exactly(true);
done();
});
});
});
describe('body handling', function () {
var action = makeClientAction({
needsBody: true
});
it('passed the body when it is set', function (done) {
var body = '{"JSON":"PLEASE"}';
action({ body: body }, function (err, params) {
params.body.should.be.exactly(body);
done();
});
});
it('errors when the body is not set but required', function (done) {
action().then(function () {
done(new Error('Error should have been raised'));
}, function (err) {
err.should.be.an.instanceOf(TypeError);
done();
});
});
});
describe('passing of http method', function () {
it('uppercases and passed the default method', function (done) {
var action = makeClientAction({
method: 'POST'
});
action({method: 'get'}, function (err, params) {
params.method.should.be.exactly('GET');
done();
});
});
it('uppercases and passed the default method', function (done) {
var action = makeClientAction({
method: 'POST'
});
action({}, function (err, params) {
params.method.should.be.exactly('POST');
done();
});
});
});
describe('passing of ignore param', function () {
it('passes ignore as an array', function (done) {
var action = makeClientAction({});
action({ ignore: 404 }, function (err, params) {
params.ignore.should.eql([404]);
done();
});
});
});
describe('passing of timeout', function () {
it('passes the timeout', function (done) {
var action = makeClientAction({
timeout: 100
});
action({}, function (err, params) {
params.timeout.should.be.exactly(100);
done();
});
});
it('passes the provided value for timeout', function (done) {
var action = makeClientAction({
timeout: 100
});
action({ timeout: 3000 }, function (err, params) {
params.timeout.should.be.exactly(3000);
done();
});
});
it('uses 10000 as the default timeout', function (done) {
var action = makeClientAction({});
action({}, function (err, params) {
params.timeout.should.be.exactly(10000);
done();
});
});
});
describe('url resolver', function () {
var action = makeClientAction({
urls: [
{
fmt: '/<%=index%>/<%=type%>/<%=id%>/<%=thing%>',
req: {
index: {
type: 'list'
},
id: {
type: 'any'
}
},
opt: {
type: {
type: 'list',
default: '_all'
},
thing: {
type: 'any',
default: ''
}
}
}
]
});
// match a url to the parameters passed in.
it('rejects a url if it required params that are not present', function (done) {
action({
type: ['type1', 'type2']
}, function (err, params) {
err.should.be.an.instanceOf(TypeError);
done();
});
});
it('uses the default value for optional params', function (done) {
action({
index: 'index1',
id: '1'
}, function (err, params) {
if (err) { throw err; }
params.path.should.be.exactly('/index1/_all/1/');
done();
});
});
it('casts both optional and required args', function (done) {
action({
index: ['index1', 'index2'],
id: '123',
type: ['_all', '-pizza'],
thing: 'poo'
}, function (err, params) {
if (err) { throw err; }
params.path.should.be.exactly('/index1%2Cindex2/_all%2C-pizza/123/poo');
done();
});
});
});
describe('param collection', function () {
var action = makeClientAction({
params: {
a: { type: 'list', required: true },
b: { type: 'duration', default: '15m' },
q: { type: 'any' }
}
});
it('collects all of the params into params.query', function (done) {
action({
a: 'pizza',
b: '1M'
},
function (err, params) {
if (err) { throw err; }
params.query.should.eql({
a: 'pizza',
b: '1M'
});
done();
});
});
it('includes extra params', function (done) {
action({
a: 'pizza',
b: '3w',
c: 'popular',
},
function (err, params) {
if (err) { throw err; }
params.query.should.eql({
a: 'pizza',
b: '3w',
c: 'popular'
});
done();
});
});
it('excludes default values', function (done) {
action({
a: 'pizza',
b: '15m',
},
function (err, params) {
if (err) { throw err; }
params.query.should.eql({
a: 'pizza'
});
done();
});
});
it('does not include non-query param keys', function (done) {
action({
a: 'pizza',
b: '3w',
q: 'beep',
body: '{ "mmm": "json" }',
timeout: 1000,
method: 'head',
ignore: 201
},
function (err, params) {
if (err) { throw err; }
params.query.should.eql({
a: 'pizza',
b: '3w',
q: 'beep'
});
done();
});
});
it('enforces required params', function (done) {
action({
b: '3w'
},
function (err, params) {
err.should.be.an.instanceOf(TypeError);
done();
});
});
});
});

View File

@ -0,0 +1,154 @@
var ConnectionAbstract = require('../../src/lib/connection');
var Host = require('../../src/lib/host');
var sinon = require('sinon');
var _ = require('lodash');
describe('Connection Abstract', function () {
var host = new Host('localhost:9200');
it('constructs with defaults for deadTimeout, requestCount, host, and bound', function () {
var conn = new ConnectionAbstract(host);
conn.deadTimeout.should.eql(30000);
conn.requestCount.should.eql(0);
conn.host.should.be.exactly(host);
conn.bound.should.have.properties('resuscitate');
});
it('requires a valid host', function () {
(function () {
new ConnectionAbstract();
}).should.throw(TypeError);
(function () {
new ConnectionAbstract({});
}).should.throw(TypeError);
});
it('required that the request method is overridden', function () {
(function () {
var conn = new ConnectionAbstract(host);
conn.request();
}).should.throw(/overwrit/);
});
describe('#ping', function () {
it('requires a callback', function () {
(function () {
(new ConnectionAbstract(host)).ping();
}).should.throw(TypeError);
});
it('calls it\'s own request method', function () {
var conn = new ConnectionAbstract(host);
var football = {};
conn.request = function () {
return football;
};
conn.ping(function () {}).should.be.exactly(football);
});
});
describe('#setStatus', function () {
it('emits the "status set" event with `new`, `old` & `conn` args', function () {
var conn = new ConnectionAbstract(host);
var emitted = false;
conn.emit = function (eventName) {
emitted = {
name: eventName,
args: Array.prototype.slice.call(arguments, 1)
};
};
conn.setStatus('closed');
emitted.name.should.eql('status set');
emitted.args.should.eql(['closed', null, conn]);
});
it('stores the status in this.status', function () {
var conn = new ConnectionAbstract(host);
conn.setStatus('closed');
conn.status.should.eql('closed');
});
it('sets a timeout when set to dead, and removed when alive', function () {
var clock = sinon.useFakeTimers('setTimeout', 'clearTimeout');
var conn = new ConnectionAbstract(host);
var start = _.size(clock.timeouts);
conn.setStatus('dead');
_.size(clock.timeouts).should.be.eql(start + 1);
conn.setStatus('alive');
_.size(clock.timeouts).should.eql(start);
clock.restore();
});
});
describe('#resuscitate', function () {
it('should not ping the connection unless it is still dead', function () {
var conn = new ConnectionAbstract(host);
conn.setStatus('alive');
conn.ping = function () {
throw new Error('ping should not have been called');
};
conn.resuscitate();
});
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');
// schedules the resuscitate
conn.setStatus('dead');
// override the ping method to just callback without an error
conn.ping = function (cb) {
process.nextTick(function () {
cb();
});
};
// will be called after the ping calls back
conn.setStatus = function (status) {
status.should.eql('alive');
clock.restore();
done();
};
// fast forward the clock
clock.tick(conn.deadTimeout);
});
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');
// schedules the resuscitate
conn.setStatus('dead');
// override the ping method to just callback without an error
conn.ping = function (cb) {
process.nextTick(function () {
cb(new Error('server still down'));
});
};
// will be called after the ping calls back
conn.setStatus = function (status) {
status.should.eql('dead');
clock.restore();
done();
};
// fast forward the clock
clock.tick(conn.deadTimeout);
});
});
});

View File

@ -0,0 +1,237 @@
var ConnectionPool = require('../../src/lib/connection_pool');
var Host = require('../../src/lib/host');
var ConnectionAbstract = require('../../src/lib/connection');
var _ = require('lodash');
var EventEmitter = require('events').EventEmitter;
var should = require('should');
function listenerCount(emitter, event) {
if (EventEmitter.listenerCount) {
// newer node
return EventEmitter.listenerCount(emitter, event);
} else {
// older node
return emitter.listeners(event).length;
}
}
describe('Connection Pool', function () {
describe('Adding/Removing/Syncing Connections', function () {
var pool, host, connection, host2, connection2;
beforeEach(function () {
pool = new ConnectionPool({});
host = new Host({
port: 999
});
connection = new ConnectionAbstract(host);
host2 = new Host({
port: 2222
});
connection2 = new ConnectionAbstract(host2);
});
it('#addConnection only adds the connection if it doesn\'t already exist', function () {
_.keys(pool.index).length.should.eql(0);
pool.addConnection(connection);
_.keys(pool.index).should.eql([host.toString()]);
pool.connections.alive.should.eql([connection]);
pool.connections.dead.should.eql([]);
});
describe('#removeConnection', function () {
it('removes the connection if it exist', function () {
pool.addConnection(connection);
pool.removeConnection(connection2);
pool.connections.alive.should.eql([connection]);
pool.connections.dead.should.eql([]);
_.keys(pool.index).length.should.eql(1);
});
it('closes the connection when it removes it', function () {
pool.addConnection(connection);
connection.status.should.eql('alive');
listenerCount(connection, 'status set').should.eql(1);
pool.removeConnection(connection);
connection.status.should.eql('closed');
listenerCount(connection, 'status set').should.eql(0);
});
});
it('#setHosts syncs the list of Hosts with the connections in the index', function () {
// there should now be two connections
pool.setHosts([host, host2]);
pool.connections.alive.length.should.eql(2);
pool.connections.dead.length.should.eql(0);
// get the new connections
connection = pool.index[host.toString()];
connection2 = pool.index[host2.toString()];
// should remove the second connection
pool.setHosts([host]);
pool.connections.alive.should.eql([connection]);
pool.connections.dead.length.should.eql(0);
// should skip the first, but create a new for the second
pool.setHosts([host, host2]);
pool.connections.alive.length.should.eql(2);
pool.connections.dead.length.should.eql(0);
// a new connection should have been created
pool.index[host2.toString()].should.not.be.exactly(connection2);
});
});
describe('Connection selection', function () {
var pool, host, host2;
beforeEach(function () {
pool = new ConnectionPool({});
host = new Host('localhost:9200');
host2 = new Host('localhost:9201');
pool.setHosts([
host,
host2
]);
});
it('detects if the selector is async', function (done) {
pool.selector = function (list, cb) {
cb.should.have.type('function');
cb();
};
pool.select(function (err) {
if (err) { throw err; }
done();
});
});
it('detects if the selector is not async', function (done) {
pool.selector = function (list) {
arguments.should.have.length(1);
};
pool.select(function (err) {
if (err) { throw err; }
done();
});
});
it('sync selectors should still return async', function (done) {
pool.selector = function (list) {
return list[0];
};
var selected = null;
pool.select(function (err, selection) {
if (err) { throw err; }
selection.host.should.be.exactly(host);
selected = selection;
done();
});
should(selected).be.exactly(null);
});
it('should catch errors in sync selectors', function (done) {
pool.selector = function (list) {
return JSON.notAMethod();
};
pool.select(function (err, selection) {
should(err).Error;
done();
});
});
it('should automatically select the first dead connection when there no living connections', function (done) {
pool.connections.alive = [];
pool.connections.dead = [1, 2, 3];
pool.select(function (err, selection) {
selection.should.be.exactly(1);
done();
});
});
});
describe('Connection state management', function () {
var pool, host, host2, connection, connection2;
beforeEach(function () {
pool = new ConnectionPool({});
host = new Host('localhost:9200');
host2 = new Host('localhost:9201');
pool.setHosts([
host,
host2
]);
connection = pool.index[host2.toString()];
connection2 = pool.index[host2.toString()];
pool.connections.alive.should.have.length(2);
pool.connections.dead.should.have.length(0);
});
it('moves an alive connection to dead', function () {
connection.setStatus('dead');
pool.connections.alive.should.have.length(1);
pool.connections.dead.should.have.length(1);
});
it('moves a dead connection to the end of the dead list when it re-dies', function () {
connection.setStatus('dead');
connection2.setStatus('dead');
// connection is at the front of the line
pool.connections.dead[0].should.be.exactly(connection);
// it re-dies
connection.setStatus('dead');
// connection2 is now at the front of the list
pool.connections.dead[0].should.be.exactly(connection2);
});
it('moves a does nothing when a connection is re-alive', function () {
var last = pool.connections.alive[pool.connections.alive.length - 1];
var first = pool.connections.alive[0];
last.should.not.be.exactly(first);
// first re-alives
first.setStatus('alive');
pool.connections.alive[0].should.be.exactly(first);
pool.connections.alive[pool.connections.alive.length - 1].should.be.exactly(last);
// last re-alives
last.setStatus('alive');
pool.connections.alive[0].should.be.exactly(first);
pool.connections.alive[pool.connections.alive.length - 1].should.be.exactly(last);
});
it('removes all its connection when it closes, causing them to be closed', function () {
pool.close();
pool.connections.alive.should.have.length(0);
pool.connections.dead.should.have.length(0);
connection.status.should.eql('closed');
connection2.status.should.eql('closed');
});
});
});

25
test/unit/test_errors.js Normal file
View File

@ -0,0 +1,25 @@
var errors = require('../../src/lib/errors');
var _ = require('lodash');
_.each(errors, function (CustomError, name) {
if (name.charAt(0) !== '_') {
describe(name, function () {
it('extend the ErrorAbstract and Error classes', function () {
var err = new CustomError();
err.message.length.should.be.above(7);
err.should.be.an.instanceOf(Error).and.an.instanceOf(errors._Abstract);
});
});
}
});
describe('Error Abstract', function () {
it('provides a stack property in the browser', function () {
var isBrowser = process.browser;
process.browser = true;
var err = new errors._Abstract();
process.browser = isBrowser;
err.stack.should.be.exactly('');
});
});

134
test/unit/test_host.js Normal file
View File

@ -0,0 +1,134 @@
var Host = require('../../src/lib/host');
var _ = require('lodash');
var url = require('url');
describe('Host class', function () {
describe('construction', function () {
it('properly sets the defaults', function () {
var host = new Host();
host.should.eql({
protocol: 'http',
host: 'localhost',
port: 9200,
path: '/',
auth: null,
query: {}
});
});
it('accepts a string for query', function () {
var host = new Host({ query: 'beep=boop'});
host.query.should.eql({
beep: 'boop'
});
});
it('accepts other generic params', function () {
var headers = { 'X-Special-Routing-Header': 'pie' };
var host = new Host({ headers: headers });
host.headers.should.be.exactly(headers);
});
it('accepts a string for the entire url', function () {
var host = new Host('john:dude@pizza.com:420/pizza/cheese?shrooms=true');
host.should.eql({
protocol: 'http',
host: 'pizza.com',
port: 420,
path: '/pizza/cheese',
auth: 'john:dude',
query: {
shrooms: 'true'
}
});
});
describe('based on the output from url.parse', function () {
it('might cause weird things to happen', function () {
var parsedUrl = url.parse('pizza.com:888');
// I imagine most people don't expect
parsedUrl.should.include({
protocol: 'pizza.com:',
host: '888',
});
var host = new Host(parsedUrl);
host.protocol.should.eql('pizza.com');
host.host.should.eql('888');
});
it('will cause extra properties', function () {
var host = new Host(url.parse('https://joe:diner@pizza.com:888/path?query=yes'));
host.should.include({
protocol: 'https',
host: 'pizza.com',
port: 888,
path: '/path',
auth: 'joe:diner',
query: {
query: 'yes'
}
});
_.keys(host).should.include('slashes', 'hash', 'href', 'search');
});
});
it('ignores anything that\'s not a string or object-y', function () {
var host = new Host(1234);
host.should.eql({
protocol: 'http',
host: 'localhost',
port: 9200,
path: '/',
auth: null,
query: {}
});
});
});
describe('#makeUrl', function () {
it('merges parameters', function () {
var host = new Host({
path: '/prefix',
query: {
user_id: 123
}
});
host.makeUrl({
path: '/this and that',
query: {
param: 1
}
}).should.eql('http://localhost:9200/prefix/this and that?param=1&user_id=123');
});
it('ensures that path starts with a forward-slash', function () {
var host = new Host();
host.path = 'prefix';
host.makeUrl({ path: '/this and that'})
.should.eql('http://localhost:9200/prefix/this and that');
});
it('does not try to prevent double forward-slashes', function () {
var host = new Host({ path: 'prefix/' });
host.makeUrl({ path: '/this and that'})
.should.eql('http://localhost:9200/prefix//this and that');
});
});
describe('#toString', function () {
it('just calls makeUrl with no parameters', function () {
});
});
});

View File

@ -0,0 +1,58 @@
describe('Http Connector', function () {
var Host = require('../../src/lib/host');
var HttpConnection = require('../../src/lib/connectors/http');
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, {});
var reqParams = con.makeReqParams();
reqParams.should.eql({
method: 'GET',
protocol: 'http:',
auth: 'john:dude',
hostname: 'pizza.com',
port: 9200,
path: '/pizza/cheese?shrooms=true',
headers: host.headers,
agent: con.agent
});
});
it('accepts merges a query object with the hosts\'', function () {
var con = new HttpConnection(new Host({
query: {
user_id: 123
}
}));
var reqParams = con.makeReqParams({
query: {
jvm: 'yes'
}
});
reqParams.should.include({
path: '/?jvm=yes&user_id=123'
});
});
// it('works with an empty query', function () {
// var reqParams = con.makeReqParams();
// reqParams.should.include({
// method: 'GET',
// path: '/'
// });
// Object.keys(reqParams).should.not.include([
// 'host', 'pathname', 'query'
// ]);
// });
});
});

259
test/unit/test_log.js Normal file
View File

@ -0,0 +1,259 @@
var Log = require('../../src/lib/log');
var _ = require('lodash');
describe('Log class', function () {
describe('::parseLevels', function () {
it('accepts a string and returns it and the other levels below it', function () {
Log.parseLevels('trace').should.eql([
'error',
'warning',
'info',
'debug',
'trace'
]);
});
it('accepts and validates an array of levels', function () {
Log.parseLevels(['warning', 'info']).should.eql(['warning', 'info']);
});
it('throws an error when an invalid string is supplied', function () {
(function () {
Log.parseLevels('INVALID');
}).should.throw(/invalid logging level/);
});
it('throws an error when an invalid string is supplied in side an array', function () {
(function () {
Log.parseLevels(['error', 'INVALID']);
}).should.throw(/invalid logging level/);
});
});
describe('#addOutput', function () {
var log;
Log.loggers.stub = function (log, config) {
this.config = config;
};
beforeEach(function () {
log = new Log();
});
it('returns the newly created logger', function () {
log.addOutput({ type: 'stub' }).should.be.an.instanceOf(Log.loggers.stub);
});
it('Accepts a config object with `level: "{{level}}"`', function () {
var logger = log.addOutput({
type: 'stub',
level: 'warning'
});
logger.config.should.include({
levels: [
'error', 'warning'
]
});
});
it('Accepts a config object with `level: ["{{level}}"]`', function () {
var logger = log.addOutput({
type: 'stub',
level: ['warning']
});
logger.config.should.include({
levels: [
'warning'
]
});
});
it('Accepts a config object with `levels: "{{level}}"`', function () {
var logger = log.addOutput({
type: 'stub',
levels: 'warning'
});
logger.config.should.include({
levels: [
'error', 'warning'
]
});
});
it('Accepts a config object with `levels: ["{{level}}"]`', function () {
var logger = log.addOutput({
type: 'stub',
level: ['warning']
});
logger.config.should.include({
levels: [
'warning'
]
});
});
});
describe('#join', function () {
it('joins strings together with spaces', function () {
Log.join(['foo', 'bar']).should.eql('foo bar');
});
it('stringifies objects', function () {
Log.join([{ foo: 'bar' }]).should.eql('{ foo: \'bar\' }\n');
});
});
describe('instance without any outputs', function () {
var log;
beforeEach(function () {
log = new Log();
});
it('should not emit any events', function () {
log.emit = function () {
throw new Error('Emit should not be called');
};
log.error();
log.info();
log.warning();
log.debug();
log.trace();
});
});
describe('instance without one output listening to all events', function () {
var log, call;
beforeEach(function () {
call = void 0;
log = new Log({
log: [
{
type: function (log, config) {
log.on('error', _.noop);
log.on('warning', _.noop);
log.on('info', _.noop);
log.on('debug', _.noop);
log.on('trace', _.noop);
}
}
]
});
log.emit = function (eventName) {
call = {
event : eventName,
args: Array.prototype.slice.call(arguments, 1)
};
};
});
it('should emit an "error" event with an Error object arg', function () {
var err = new Error('error');
log.error(err);
call.event.should.eql('error');
call.args[0].should.be.exactly(err);
call = void 0;
log.error('error');
call.event.should.eql('error');
call.args[0].should.be.an.instanceOf(Error);
call.args[0].message.should.eql('error');
});
it('should emit a "warning" event with a single message arg for #warning calls', function () {
log.warning('shit!');
call.event.should.eql('warning');
call.args.should.have.length(1);
call.args[0].should.eql('shit!');
});
it('should emit a "info" event with a single message arg for #info calls', function () {
log.info('look out!');
call.event.should.eql('info');
call.args.should.have.length(1);
call.args[0].should.eql('look out!');
});
it('should emit a "debug" event with a single message arg for #debug calls', function () {
log.debug('here');
call.event.should.eql('debug');
call.args.should.have.length(1);
call.args[0].should.eql('here');
});
it('should emit a trace event for trace events, with message and curlCall args', function () {
log.trace('GET', 'http://localhost:9200/_cluster/nodes', '', '', 200);
call.event.should.eql('trace');
call.args.should.have.length(2);
call.args[0].should.match(/^<- 200/);
call.args[1].should.match(/^curl /);
});
});
describe('constructor', function () {
it('looks for output config options at config.log', function () {
var log = new Log({ log: { type: process.browser ? 'console' : 'stdio', level: 'error' } });
log.listenerCount('error').should.eql(1);
log.listenerCount('warning').should.eql(0);
log.listenerCount('info').should.eql(0);
log.listenerCount('debug').should.eql(0);
log.listenerCount('trace').should.eql(0);
});
it('accepts a string and treat it as a log level', function () {
var log = new Log({ log: 'error' });
log.listenerCount('error').should.eql(1);
log.listenerCount('warning').should.eql(0);
log.listenerCount('info').should.eql(0);
log.listenerCount('debug').should.eql(0);
log.listenerCount('trace').should.eql(0);
});
it('accepts an array of strings and treat it as a log level config', function () {
var log = new Log({ log: ['error', 'trace'] });
log.listenerCount('error').should.eql(1);
log.listenerCount('warning').should.eql(0);
log.listenerCount('info').should.eql(0);
log.listenerCount('debug').should.eql(0);
log.listenerCount('trace').should.eql(1);
});
it('accepts an array of output config objects', function () {
var log = new Log({ log: [{ level: 'error' }, { level: 'trace'}] });
log.listenerCount('error').should.eql(2);
log.listenerCount('warning').should.eql(1);
log.listenerCount('info').should.eql(1);
log.listenerCount('debug').should.eql(1);
log.listenerCount('trace').should.eql(1);
});
it('rejects numbers and other truthy data-types', function () {
(function () {
var log = new Log({ log: 1515 });
}).should.throw(/invalid logging output config/i);
(function () {
var log = new Log({ log: /regexp/ });
}).should.throw(/invalid logging output config/i);
(function () {
var log = new Log({ log: new Date() });
}).should.throw(/invalid logging output config/i);
(function () {
var log = new Log({ log: [1515] });
}).should.throw(/invalid logging output config/i);
(function () {
var log = new Log({ log: [/regexp/] });
}).should.throw(/invalid logging output config/i);
(function () {
var log = new Log({ log: [new Date()] });
}).should.throw(/invalid logging output config/i);
});
});
});

View File

@ -0,0 +1,19 @@
var randomSelector = require('../../src/lib/selectors/random');
var _ = require('lodash');
describe('Random Selector', function () {
it('chooses a selection by random', function () {
var log = { a: 0, b: 0, c: 0 };
var choices = _.keys(log);
_.times(1000, function () {
var choice = randomSelector(choices);
log[choice]++;
});
_.filter(log, function (count) {
return count < 200 || count > 400;
}).should.have.length(0);
});
});

View File

@ -0,0 +1,16 @@
var selector = require('../../src/lib/selectors/round_robin');
var _ = require('lodash');
describe('Round Robin Selector', function () {
it('chooses options in order', function () {
var options = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0];
var expected = _.clone(options);
var selections = [];
_.times(options.length, function () {
selections.push(selector(options));
});
selections.should.eql(expected);
});
});

View File

@ -0,0 +1,71 @@
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;
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')
});
});
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('emits events because something is listening', function () {
log.error(new Error('error message'));
log._emission.name.should.eql('error');
log.warning('warning');
log._emission.name.should.eql('warning');
log.info('info');
log._emission.name.should.eql('info');
log.debug('debug');
log._emission.name.should.eql('debug');
log.trace('GET', {}, '', '', 200);
log._emission.name.should.eql('trace');
});
});
});

294
test/unit/test_utils.js Normal file
View File

@ -0,0 +1,294 @@
var _ = require('../../src/lib/utils');
var should = require('should');
describe('Utils', function () {
describe('Additional Type Checkers', function () {
_.forEach({
Object: {
is: [[], /regexp/]
},
PlainObject: {
is: [{}, {}]
},
String: {
is: ['steamy', 'poop'],
not: {}
},
Array: {
is: [['im'], ['usefull']],
},
Finite: {
is: [11123, 666],
not: Infinity
},
Function: {
is: [console.error, console.log],
},
RegExp: {
is: [/.*/, new RegExp('a')],
}
},
function (thing, name) {
describe('#isArrayOf' + name, function (test) {
it('likes arrays of ' + name, function () {
should(_['isArrayOf' + name + 's'](thing.is)).be.ok;
});
it('dislikes when there is even one non ' + name, function () {
// notice a string in the array
thing.is.push(thing.not || ' not ');
should(_['isArrayOf' + name + 's'](thing.is)).not.be.ok;
});
});
});
describe('#isNumeric', function () {
it('likes integer literals', function () {
should(_.isNumeric('-10')).be.ok;
should(_.isNumeric('0')).be.ok;
should(_.isNumeric('5')).be.ok;
should(_.isNumeric(-16)).be.ok;
should(_.isNumeric(0)).be.ok;
should(_.isNumeric(32)).be.ok;
should(_.isNumeric('040')).be.ok;
should(_.isNumeric(0144)).be.ok;
should(_.isNumeric('0xFF')).be.ok;
should(_.isNumeric(0xFFF)).be.ok;
});
it('likes float literals', function () {
should(_.isNumeric('-1.6')).be.ok;
should(_.isNumeric('4.536')).be.ok;
should(_.isNumeric(-2.6)).be.ok;
should(_.isNumeric(3.1415)).be.ok;
should(_.isNumeric(8e5)).be.ok;
should(_.isNumeric('123e-2')).be.ok;
});
it('dislikes non-numeric stuff', function () {
should(_.isNumeric('')).not.be.ok;
should(_.isNumeric(' ')).not.be.ok;
should(_.isNumeric('\t\t')).not.be.ok;
should(_.isNumeric('abcdefghijklm1234567890')).not.be.ok;
should(_.isNumeric('xabcdefx')).not.be.ok;
should(_.isNumeric(true)).not.be.ok;
should(_.isNumeric(false)).not.be.ok;
should(_.isNumeric('bcfed5.2')).not.be.ok;
should(_.isNumeric('7.2acdgs')).not.be.ok;
should(_.isNumeric(undefined)).not.be.ok;
should(_.isNumeric(null)).not.be.ok;
should(_.isNumeric(NaN)).not.be.ok;
should(_.isNumeric(Infinity)).not.be.ok;
should(_.isNumeric(Number.POSITIVE_INFINITY)).not.be.ok;
should(_.isNumeric(Number.NEGATIVE_INFINITY)).not.be.ok;
should(_.isNumeric(new Date(2009, 1, 1))).not.be.ok;
should(_.isNumeric([])).not.be.ok;
should(_.isNumeric([1, 2, 3, 4])).not.be.ok;
should(_.isNumeric({})).not.be.ok;
should(_.isNumeric(function () {})).not.be.ok;
});
});
describe('#isInterval', function () {
_.forEach({
M: 'months',
w: 'weeks',
d: 'days',
h: 'hours',
m: 'minutes',
s: 'seconds',
y: 'years'
},
function (name, unit) {
it('likes ' + name, function () {
should(_.isInterval('1' + unit)).be.ok;
});
it('likes decimal ' + name, function () {
should(_.isInterval('1.5' + unit)).be.ok;
});
});
it('dislikes more than one unit', function () {
should(_.isInterval('1my')).not.be.ok;
});
it('dislikes spaces', function () {
should(_.isInterval('1 m')).not.be.ok;
});
});
});
describe('String Transformers', function () {
describe('#camelCase', function () {
it('find spaces, underscores, and other natural word breaks', function () {
_.camelCase('Neil Patrick.Harris-is_a.dog').should.eql('neilPatrickHarrisIsADog');
});
it('ignores abreviations', function () {
_.camelCase('Json_parser').should.eql('jsonParser');
});
it('handles trailing _', function () {
_.camelCase('_thing_one_').should.eql('thingOne');
});
});
describe('#studlyCase', function () {
it('find spaces, underscores, and other natural word breaks', function () {
_.studlyCase('Neil Patrick.Harris-is_a.dog').should.eql('NeilPatrickHarrisIsADog');
});
it('ignores abreviations', function () {
_.studlyCase('Json_parser').should.eql('JsonParser');
});
it('handles trailing _', function () {
_.studlyCase('_thing_one_').should.eql('ThingOne');
});
});
describe('#snakeCase', function () {
it('find spaces, underscores, and other natural word breaks', function () {
_.snakeCase('Neil Patrick.Harris-is_a.dog').should.eql('neil_patrick_harris_is_a_dog');
});
it('ignores abreviations', function () {
_.snakeCase('Json_parser').should.eql('json_parser');
});
it('handles trailing _', function () {
_.snakeCase('_thing_one_').should.eql('thing_one');
});
});
describe('#toLowerString', function () {
it('transforms normal strings', function () {
_.toLowerString('PASTA').should.eql('pasta');
});
it('ignores long form empty vals (null, false, undef)', function () {
_.toLowerString(null).should.eql('');
_.toLowerString(false).should.eql('');
_.toLowerString(void 0).should.eql('');
});
it('uses the objects own toString', function () {
_.toLowerString(['A', 'B']).should.eql('a,b');
});
it('sorta kinda works on objects', function () {
_.toLowerString({a: 'thing'}).should.eql('[object object]');
});
});
describe('#toUpperString', function () {
it('transforms normal strings', function () {
_.toUpperString('PASTA').should.eql('PASTA');
});
it('ignores long form empty vals (null, false, undef)', function () {
_.toUpperString(null).should.eql('');
_.toUpperString(false).should.eql('');
_.toUpperString(void 0).should.eql('');
});
it('uses the objects own toString', function () {
_.toUpperString(['A', 'B']).should.eql('A,B');
});
it('sorta kinda works on objects', function () {
_.toUpperString({a: 'thing'}).should.eql('[OBJECT OBJECT]');
});
});
describe('#repeat', function () {
it('repeats strings', function () {
_.repeat(' ', 5).should.eql(' ');
_.repeat('foobar', 2).should.eql('foobarfoobar');
});
});
describe('#ucfirst', function () {
it('only capitalized the first letter, lowercases everything else', function () {
_.ucfirst('ALGER').should.eql('Alger');
});
});
});
describe('#deepMerge', function () {
it('returns the same object that was passed', function () {
var obj = {
foo: 'bar'
};
_.deepMerge(obj, { bar: 'baz' }).should.eql(obj);
});
it('concats arrays', function () {
var obj = {
foo: ['bax', 'boz']
};
_.deepMerge(obj, { foo: ['boop'] });
obj.foo.should.have.a.lengthOf(3);
});
it('wont merge values of different types', function () {
var obj = {
foo: ['stop', 'foo', 'stahp']
};
_.deepMerge(obj, { foo: 'string' });
obj.foo.should.have.a.lengthOf(3);
});
it('works recursively', function () {
var obj = {
foo: 'bar',
bax: {
foo: ['bax', 'boz']
}
};
_.deepMerge(obj, { bax: { foo: ['poo'] }});
obj.bax.foo.should.have.a.lengthOf(3);
});
});
describe('#createArray', function () {
it('accepts an array of things and simply returns a copy of it', function () {
var inp = [{ a: 1 }, 'pizza'];
var out = _.createArray(inp);
out.should.eql(inp);
out.should.not.be.exactly(inp);
});
it('accepts a primitive value and calls the the transform function', function (done) {
var out = _.createArray('str', function (val) {
val.should.be.exactly('str');
done();
});
});
it('wraps any non-array in an array', function () {
_.createArray({}).should.eql([{}]);
_.createArray('').should.eql(['']);
_.createArray(123).should.eql([123]);
_.createArray(/abc/).should.eql([/abc/]);
_.createArray(false).should.eql([false]);
});
it('returns false when the transform function returns undefined', function () {
_.createArray(['str', 1], function (val) {
if (_.isString(val)) {
return {
val: val
};
}
}).should.be.exactly(false);
});
});
});

View File

@ -1,220 +0,0 @@
var _ = require('../../src/lib/utils')
, expect = require('expect.js');
describe('Utils', function () {
describe('Additional Type Checkers', function () {
_.forEach({
Object: {
is: [[], console.log]
},
PlainObject: {
is: [{}, {}]
},
String: {
is: ['steamy', 'poop'],
not: {}
},
Array: {
is: [['im'], ['usefull']],
},
Finite: {
is: [11123, 666],
not: Infinity
},
Function: {
is: [console.error, console.log],
},
RegExp: {
is: [/.*/, new RegExp('a')],
}
},
function (thing, name) {
describe('#isArrayOf' + name, function (test) {
it('likes arrays of ' + name, function () {
expect(_['isArrayOf' + name + 's'](thing.is)).to.be.true;
});
it('dislikes when there is even one non ' + name, function () {
// notice a string in the array
thing.is.push(thing.not || ' not ');
expect(_['isArrayOf' + name + 's'](thing.is)).to.be.false;
});
});
});
describe('#isNumeric', function () {
it('likes Infinity', function () {
expect(_.isNumeric(Infinity)).to.be.true;
});
it('likes strings', function () {
expect(_.isNumeric('100')).to.be.true;
});
it('likes integers', function () {
expect(_.isNumeric(100)).to.be.true;
});
it('likes floats', function () {
expect(_.isNumeric(100.1)).to.be.true;
});
it('likes exponentials', function () {
expect(_.isNumeric(100e1)).to.be.true;
});
it('likes hexidecimals', function () {
expect(_.isNumeric(0x100)).to.be.true;
});
it('likes imaginary numbers', function () {
expect(_.isNumeric('yeah right')).to.be.false;
});
it('dislikes strings with words', function () {
expect(_.isNumeric('100heat')).to.be.false;
});
it('dislikes strings with words even if they are seperate', function () {
expect(_.isNumeric('100 pasta')).to.be.false;
});
it('dislikes null', function () {
expect(_.isNumeric(null)).to.be.false;
});
});
describe('#isInterval', function () {
_.forEach({
M: 'months',
w: 'weeks',
d: 'days',
h: 'hours',
m: 'minutes',
s: 'seconds',
y: 'years'
},
function (name, unit) {
it('likes ' + name, function () { expect(_.isInterval('1' + unit)).to.be.true; });
it('likes decimal ' + name, function () { expect(_.isInterval('1.5' + unit)).to.be.true; });
});
it('dislikes more than one unit', function () {
expect(_.isInterval('1my')).to.be.false;
});
it('dislikes spaces', function () {
expect(_.isInterval('1 m')).to.be.false;
});
});
});
describe('String Transformers', function () {
describe('#camelCase', function () {
it('find spaces, underscores, and other natural word breaks', function () {
expect(_.camelCase('Neil Patrick.Harris-is_a.dog')).to.eql('neilPatrickHarrisIsADog');
});
it('ignores abreviations', function () {
expect(_.camelCase('Json_parser')).to.eql('jsonParser');
});
it('handles trailing _', function () {
expect(_.camelCase('_thing_one_')).to.eql('thingOne');
});
});
describe('#studlyCase', function () {
it('find spaces, underscores, and other natural word breaks', function () {
expect(_.studlyCase('Neil Patrick.Harris-is_a.dog')).to.eql('NeilPatrickHarrisIsADog');
});
it('ignores abreviations', function () {
expect(_.studlyCase('Json_parser')).to.eql('JsonParser');
});
it('handles trailing _', function () {
expect(_.studlyCase('_thing_one_')).to.eql('ThingOne');
});
});
describe('#snakeCase', function () {
it('find spaces, underscores, and other natural word breaks', function () {
expect(_.snakeCase('Neil Patrick.Harris-is_a.dog')).to.eql('neil_patrick_harris_is_a_dog');
});
it('ignores abreviations', function () {
expect(_.snakeCase('Json_parser')).to.eql('json_parser');
});
it('handles trailing _', function () {
expect(_.snakeCase('_thing_one_')).to.eql('thing_one');
});
});
describe('#toLowerString', function () {
it('transforms normal strings', function () {
expect(_.toLowerString('PASTA')).to.eql('pasta');
});
it('ignores long form empty vals (null, false, undef)', function () {
expect(_.toLowerString(null)).to.eql('');
expect(_.toLowerString(false)).to.eql('');
expect(_.toLowerString(void 0)).to.eql('');
});
it('uses the objects own toString', function () {
expect(_.toLowerString(['A', 'B'])).to.eql('a,b');
});
it('sorta kinda works on objects', function () {
expect(_.toLowerString({a: 'thing'})).to.eql('[object object]');
});
});
});
describe('#deepMerge', function () {
it('returns the same object that was passed', function () {
var obj = {
foo: 'bar'
};
expect(_.deepMerge(obj, { bar: 'baz' })).to.eql(obj);
});
it('concats arrays', function () {
var obj = {
foo: ['bax', 'boz']
};
_.deepMerge(obj, { foo: ['boop'] });
expect(obj.foo).to.have.length(3);
});
it('wont merge values of different types', function () {
var obj = {
foo: ['stop', 'foo', 'stahp']
};
_.deepMerge(obj, { foo: 'string' });
expect(obj.foo).to.have.length(3);
});
it('works recursively', function () {
var obj = {
foo: 'bar',
bax: {
foo: ['bax', 'boz']
}
};
_.deepMerge(obj, { bax: { foo: ['poo'] }});
expect(obj.bax.foo).to.have.length(3);
});
});
});