Summary of changes:

- updated copyright
 - several tempalate changes for the docs
 - added a config for grunt-contrib-watch
 - updated nock commit number
 - fixed the coverage script
 - removed the export_docs script
 - added error message for legacy "es" users who don't have a version locked and have not upgraded
 - host will now add auth to urls created with `#makeUrl()`
 - Log class no longer looks for `config.loggers`
 - The log class now properly escapes single quotes in trace logs
 - Removed compiled yaml_tests.js from the repo
 - Yaml suite will only log error and warning messages unless the VERBOSE env var is set
 - createDefer is now a global setting, changed by modifying Transport.createDefer fubction
 - wrote tests for Content-Type checking
 - callbacks will now return the body and status of the request (if the request has completed) when an error occurs
 - Stdio logger now adds "Elasticsearch " to the front of log messages to distinguish it from other output to stdout.
This commit is contained in:
Spencer Alger
2013-12-15 14:08:29 -07:00
parent 0c8bd328fe
commit 37cd2f4f6c
20 changed files with 229 additions and 34803 deletions

View File

@ -12,9 +12,8 @@ module.exports = function (grunt) {
banner: '/*! <%= package.name %> - v<%= package.version %> - ' +
'<%= grunt.template.today("yyyy-mm-dd") %>\n' +
'<%= package.homepage ? " * " + package.homepage + "\\n" : "" %>' +
' * Copyright (c) <%= grunt.template.today("yyyy") %> <%= package.author.name %>;' +
' Licensed <%= package.license %> */\n' +
' // built using browserify\n\n'
' * Copyright (c) <%= grunt.template.today("yyyy") %> <%= package.author.company %>;' +
' Licensed <%= package.license %> */\n'
}
}
});

View File

@ -27,7 +27,7 @@ We also provide builds of the elasticsearch.js client for use in the browser. If
- [Quick Start](http://spenceralger.github.io/elasticsearch-js/index.html#quick-start)
- [API](http://spenceralger.github.io/elasticsearch-js/api.html)
- [Configuration](http://spenceralger.github.io/elasticsearch-js/index.html#configuration)
- [Development/Contributions](http://spenceralger.github.io/elasticsearch-js/index.html#dev)
- [Development/Contributing](http://spenceralger.github.io/elasticsearch-js/index.html#dev)
- [Extending Core Components](http://spenceralger.github.io/elasticsearch-js/index.html#extending)
- [Logging](http://spenceralger.github.io/elasticsearch-js/index.html#logging)

View File

@ -1,8 +0,0 @@
module.exports = {
unit: {
src: ['test/unit/test_*.js'],
reporter: 'XUnit',
dest: './test-output-phantom-unit.xml',
run: true
}
};

15
grunt/config/watch.js Normal file
View File

@ -0,0 +1,15 @@
module.exports = {
source: {
files: [
'src/**/*.js',
'test/unit/**/*.js',
'grunt/**/*.js',
'Gruntfile.js'
],
interrupt: true,
tasks: [
// 'jshint',
'run:unit_tests'
]
}
};

View File

@ -37,7 +37,7 @@
"mocha-lcov-reporter": "0.0.1",
"blanket": "~1.1.5",
"sinon": "~1.7.3",
"nock": "git://github.com/spenceralger/nock.git#f28dc3c973651830b930793932b4006577260dc1",
"nock": "git://github.com/spenceralger/nock.git#5218548233983c594da5535bc07e7db36841987e",
"open": "0.0.4",
"testling": "git://github.com/spenceralger/testling.git",
"load-grunt-tasks": "~0.2.0",
@ -64,7 +64,7 @@
},
"scripts": {
"test": "grunt test",
"coverage": "mocha test/unit/test_*.js --require blanket -R html-cov > coverage.html && open -a \"Google Chrome\"./coverage.html",
"coverage": "mocha test/unit/test_*.js --require blanket -R html-cov > coverage.html && open -a \"Google Chrome\" ./coverage.html",
"blanket": {
"pattern": "src"
}

View File

@ -1,74 +0,0 @@
var path = require('path');
var argv = require('optimist')
.default({
outputDir: '.',
verbose: false
})
.alias({
o: 'outputDir',
v: 'verbose'
})
.argv;
require('./_steps')(argv, [
['runInModule', {
cmd: 'node',
args: ['scripts/generate', '--force']
}],
['copy', {
from: path.join(__dirname, '../docs/_methods.jade'),
to: path.join(argv.outputDir, '_methods.jade')
}],
['copy', {
from: path.join(__dirname, '../docs/_method_list.jade'),
to: path.join(argv.outputDir, '_method_list.jade')
}]
]);
// function runInModule(cmd, args, exitCb) {
// log('running', cmd, args.join(' '));
// var proc = cp.spawn(cmd, args, {
// stdio: argv.verbose ? 'inherit' : 'ignore'
// });
// proc.on('error', function (err) {
// console.error('Error! --', err.message);
// process.exit(1);
// });
// proc.on('exit', function (status) {
// if (status) {
// console.error('Error! --', cmd, 'exit status was', status);
// process.exit(1);
// } else {
// exitCb();
// }
// });
// }
// function copy(from, to, done) {
// log('copying', from, 'to', to);
// var read = fs.createReadStream(from);
// var write = fs.createWriteStream(to);
// read.pipe(write);
// read.on('error', function (err) {
// console.error('unable to read: ' + from);
// console.error(err.message);
// process.exit(1);
// });
// write.on('error', function (err) {
// console.error('unable to write to: ' + to);
// console.error(err.message);
// process.exit(1);
// });
// write.on('finish', function () {
// done();
// });
// }

View File

@ -7,14 +7,12 @@ var actionId = action.name.toLowerCase().replace(/[^\w]+/g, '-');
h2#<%= actionId %>.fn
span.name <%= action.name %>
span.args (params, [callback])
a.perma(href="api.html#<%= actionId %>", title="Permalink")
a.esdoc(href="<%= action.docUrl %>", title="Endpoint Docs")
//-
h4 Spec:
pre
code <%= JSON.stringify(action, null, ' ').split('\n').map(function (line, i) {
return (i > 0 ? ' | ' : '') + line;
}).join('\n') %>
include _descriptions/<%= action.name %>.jade
a.esdoc(href="<%= action.docUrl %>", title="<%= action.name %> at elasticsearch.org").
<%= action.docUrl %>
p.tight.
The default method is <code><%= action.spec.method || 'GET' %></code> and
the usual <a href="#api-conventions">params and return values</a> apply.
<% if (_.size(action.allParams)) { %>
h3 Params:
@ -26,10 +24,6 @@ dl.params.api
<%= indent(param.description || '', 4) %><%
}); %>
<% } %>
p.
Default method: <%= action.spec.method || 'GET' %><br>
Includes <a href="#api-conventions-return">the usual</a>
include _examples/<%= action.name %>.jade<%
});
%>

View File

@ -1,7 +1,13 @@
#!/bin/bash
# let the dust settle and ensure that es is ready for us.
sleep 15s
# generate the latest version of the yaml-tests
node scripts/generate/ --no-api 2>&1 > /dev/null
export VERBOSE="true"
# unit tests
./node_modules/.bin/mocha test/unit/test_*.js \
--require should \

View File

@ -5,6 +5,7 @@
* It will also instruct the client to use Angular's $http service for it's ajax requests
*/
var AngularConnector = require('./lib/connectors/angular');
var Transport = require('./lib/transport');
var Client = require('./lib/client');
process.angular_build = true;
@ -16,12 +17,14 @@ angular.module('elasticsearch.client', [])
AngularConnector.prototype.$http = $http;
AngularConnector.prototype.$q = $q;
// make the Transport return $q promisses instead
Transport.createDefer = function () {
return $q.defer();
};
var factory = function (config) {
config = config || {};
config.connectionClass = AngularConnector;
config.createDefer = function () {
return $q.defer();
};
return new Client(config);
};

View File

@ -1,9 +1,15 @@
var es = {
Client: require('./lib/client'),
ConnectionPool: require('./lib/connection_pool'),
Transport: require('./lib/transport'),
// In order to help people who were accidentally upgraded to this ES client,
// throw an error when they try to instanciate the exported function.
// previous "elasticsearch" module -> https://github.com/ncb000gt/node-es
function es() {
throw new Error('Looks like you are expecting the previous "elasticsearch" module. ' +
'It is now the "es" module. To create a client with this module use ' +
'`new es.Client(params)`.');
}
errors: require('./lib/errors')
};
es.Client = require('./lib/client');
es.ConnectionPool = require('./lib/connection_pool');
es.Transport = require('./lib/transport');
es.errors = require('./lib/errors');
module.exports = es;

View File

@ -36,4 +36,5 @@ AngularConnector.prototype.request = function (params, cb) {
// must be overwritten before this connection can be used
AngularConnector.prototype.$http = null;
// required in order to provide abort functionality
AngularConnector.prototype.$q = null;

View File

@ -128,7 +128,14 @@ Host.prototype.makeUrl = function (params) {
query = qs.stringify(this.query);
}
return this.protocol + '://' + this.host + port + path + (query ? '?' + query : '');
var auth = '';
if (params.auth) {
auth = params.auth + '@';
} else if (this.auth) {
auth = this.auth + '@';
}
return this.protocol + '://' + auth + this.host + port + path + (query ? '?' + query : '');
};
Host.prototype.toString = function () {

View File

@ -22,10 +22,6 @@ function Log(config) {
var i;
var outputs;
if (config.loggers) {
config.log = config.loggers;
}
if (config.log) {
if (_.isArrayOfStrings(config.log)) {
outputs = [{
@ -305,8 +301,7 @@ Log.prototype.trace = function (method, requestUrl, body, responseBody, response
function prettyJSON(body) {
try {
// TESTME
return JSON.stringify(JSON.parse(body), null, ' ').replace(/'/g, '\\\'');
return JSON.stringify(JSON.parse(body), null, ' ').replace(/'/g, '\\u0027');
} catch (e) {
return body || '';
}

File diff suppressed because one or more lines are too long

View File

@ -68,8 +68,7 @@ module.exports = {
],
log: {
type: process.browser ? 'console' : 'stdio',
level: 'trace',
color: false
level: process.env.VERBOSE ? 'trace' : 'warning'
}
});

View File

@ -122,8 +122,9 @@ describe('Host class', function () {
path: '/this and that',
query: {
param: 1
}
}).should.eql('http://localhost:9200/prefix/this and that?param=1&user_id=123');
},
auth: 'user:pass'
}).should.eql('http://user:pass@localhost:9200/prefix/this and that?param=1&user_id=123');
});
it('ensures that path starts with a forward-slash', function () {
@ -148,8 +149,8 @@ describe('Host class', function () {
host = new Host({ host: 'john', port: 80 });
host.makeUrl().should.eql('http://john/');
host = new Host({ host: 'italy', path: '/pie' });
host.makeUrl().should.eql('http://italy:9200/pie');
host = new Host({ host: 'italy', path: '/pie', auth: 'user:pass'});
host.makeUrl().should.eql('http://user:pass@italy:9200/pie');
});
});

View File

@ -188,7 +188,7 @@ describe('Http Connector', function () {
});
});
it('logs error events when an error occurs', function (done) {
it('does not log error events', function (done) {
var con = new HttpConnection(new Host('http://google.com'));
stub(con.log, 'error');
@ -205,8 +205,8 @@ describe('Http Connector', function () {
err.message.should.eql('actual error');
// logged the error and the trace log
con.log.error.callCount.should.eql(1);
con.log.trace.callCount.should.eql(1);
con.log.error.callCount.should.eql(0);
con.log.info.callCount.should.eql(0);
con.log.warning.callCount.should.eql(0);
con.log.debug.callCount.should.eql(0);
@ -227,9 +227,7 @@ describe('Http Connector', function () {
err.message.should.eql('actual error');
// logged the error
con.log.error.callCount.should.eql(1);
con.log.error.lastCall.args[0].message.should.eql('actual error');
con.log.error.callCount.should.eql(0);
done();
});
});
@ -250,13 +248,13 @@ describe('Http Connector', function () {
});
}
it('logs error event', function (done) {
it('does not log errors', function (done) {
var con = new HttpConnection(new Host('https://google.com'));
stub(con.log, 'error');
stub(https, 'request', makeStubReqWithMsgWhichErrorsMidBody());
con.request({}, function (err, resp, status) {
con.log.error.callCount.should.eql(1);
con.log.error.callCount.should.eql(0);
done();
});
});

View File

@ -45,7 +45,7 @@ describe('Stdio Logger', function () {
it('obeys the logger.color === false', function () {
var logger = makeLogger();
stub(process.stdout, 'write');
var withoutColor = 'INFO: ' + now + '\n something\n\n';
var withoutColor = 'Elasticsearch INFO: ' + now + '\n something\n\n';
logger.color = false;
logger.onInfo('something');
@ -56,7 +56,7 @@ describe('Stdio Logger', function () {
var logger = makeLogger();
stub(process.stdout, 'write');
var withoutColor = 'TRACE: ' + now + '\n curl\n msg\n\n';
var withoutColor = 'Elasticsearch TRACE: ' + now + '\n curl\n msg\n\n';
logger.color = true;
logger.onTrace('msg', 'curl');

View File

@ -33,20 +33,6 @@ describe('Transport Class', function () {
trans.log.should.be.an.instanceOf(CustomLogClass);
});
it('Accepts a "createDefer" function, which can be used to tie into other promise libs.', function () {
function CustomPromise() {
this.then = function () {};
}
var trans = new Transport({
createDefer: function () {
return new CustomPromise();
}
});
trans.createDefer().should.be.an.instanceOf(CustomPromise);
});
it('Accepts a connection pool class and intanciates it at this.connectionPool', function () {
function CustomConnectionPool() {}
var trans = new Transport({
@ -75,6 +61,33 @@ describe('Transport Class', function () {
}).should.throw(/invalid connectionpool/i);
});
it('calls sniff immediately if sniffOnStart is true', function () {
stub(Transport.prototype, 'sniff');
var trans = new Transport({
sniffOnStart: true
});
trans.sniff.callCount.should.eql(1);
});
it('schedules a sniff when sniffInterval is set', function () {
var clock = sinon.useFakeTimers('setTimeout');
stub(Transport.prototype, 'sniff');
var trans = new Transport({
sniffInterval: 25000
});
_.size(clock.timeouts).should.eql(1);
var id = _.keys(clock.timeouts).pop();
clock.tick(25000);
trans.sniff.callCount.should.eql(1);
_.size(clock.timeouts).should.eql(1);
_.keys(clock.timeouts).pop().should.not.eql(id);
clock.restore();
});
describe('host config', function () {
it('rejects non-strings/objects', function () {
(function () {
@ -195,6 +208,14 @@ describe('Transport Class', function () {
});
});
describe('::createDefer', function () {
it('returns a when.js promise by default', function () {
Transport.createDefer().constructor.should.be.exactly(when.defer().constructor);
});
});
describe('#sniff', function () {
var trans;
@ -275,27 +296,6 @@ describe('Transport Class', function () {
});
});
describe('#createDefer', function () {
it('returns a when.js promise by default', function () {
var trans = new Transport({
hosts: 'localhost'
});
trans.createDefer().constructor.should.be.exactly(when.defer().constructor);
});
it('is overridden by the createDefer option', function () {
var when = require('when');
var trans = new Transport({
hosts: 'localhost',
createDefer: function () {
return 'pasta';
}
});
trans.createDefer().should.be.exactly('pasta');
});
});
describe('#request', function () {
it('logs when it begins', function (done) {
var trans = new Transport();
@ -445,48 +445,47 @@ describe('Transport Class', function () {
});
describe('gets a connection err', function () {
function testRetries(retries, done) {
var randomSelector = require('../../src/lib/selectors/random');
var connections;
var attempts = 0;
function failRequest(params, cb) {
attempts++;
process.nextTick(function () {
cb(new Error('Unable to do that thing you wanted'));
});
}
var trans = new Transport({
hosts: _.map(new Array(retries + 1), function (i) {
return 'localhost/' + i;
}),
maxRetries: retries,
selector: function (_conns) {
connections = _conns;
return randomSelector(_conns);
// create a test that checks N retries
function testRetries(retries) {
return function (done) {
var randomSelector = require('../../src/lib/selectors/random');
var connections;
var attempts = 0;
function failRequest(params, cb) {
attempts++;
process.nextTick(function () {
cb(new Error('Unable to do that thing you wanted'));
});
}
});
// trigger a select so that we can harvest the connection list
trans.connectionPool.select(_.noop);
_.each(connections, function (conn) {
stub(conn, 'request', failRequest);
});
var trans = new Transport({
hosts: _.map(new Array(retries + 1), function (i) {
return 'localhost/' + i;
}),
maxRetries: retries,
selector: function (_conns) {
connections = _conns;
return randomSelector(_conns);
}
});
trans.request({}, function (err, resp, body) {
attempts.should.eql(retries + 1);
err.should.be.an.instanceOf(errors.ConnectionFault);
should.not.exist(resp);
should.not.exist(body);
done();
});
// trigger a select so that we can harvest the connection list
trans.connectionPool.select(_.noop);
_.each(connections, function (conn) {
stub(conn, 'request', failRequest);
});
trans.request({}, function (err, resp, body) {
attempts.should.eql(retries + 1);
err.should.be.an.instanceOf(errors.ConnectionFault);
should.not.exist(resp);
should.not.exist(body);
done();
});
};
}
it('retries when there are retries remaining', function (done) {
testRetries(30, done);
});
it('responds when there are no retries', function (done) {
testRetries(0, done);
});
it('retries when there are retries remaining', testRetries(_.random(25, 40)));
it('responds when there are no retries', testRetries(0));
});
describe('server responds', function () {
@ -505,16 +504,34 @@ describe('Transport Class', function () {
.reply(500, 'ah shit')
.get('/exists?')
.reply(200, '{"status":200}')
.reply(200, {
status: 200
})
.get('/give-me-someth')
.reply(200, '{"not":"valid')
.reply(200, '{"not":"valid', {
'Content-Type': 'application/json'
})
.get('/')
.reply(200, '{"the answer":42}')
.reply(200, {
'the answer': 42
})
.get('/huh?')
.reply(530, 'boo');
.reply(530, 'boo')
.get('/hottie-threads')
.reply(200, [
'he said',
'she said',
'he said',
'she said',
'he said',
'she said'
].join('\n'), {
'Content-Type': 'text/plain'
});
});
after(function () {
@ -532,8 +549,8 @@ describe('Transport Class', function () {
}, function (err, body, status) {
err.should.be.an.instanceOf(errors[400]);
err.should.be.an.instanceOf(errors.BadRequest);
should.not.exist(body);
should.not.exist(status);
body.should.eql('sorry bub');
status.should.eql(400);
done();
});
});
@ -568,8 +585,8 @@ describe('Transport Class', function () {
}, function (err, body, status) {
err.should.be.an.instanceOf(errors[404]);
err.should.be.an.instanceOf(errors.NotFound);
should.not.exist(body);
should.not.exist(status);
body.should.eql('nothing here');
status.should.eql(404);
done();
});
});
@ -587,14 +604,14 @@ describe('Transport Class', function () {
}, function (err, body, status) {
err.should.be.an.instanceOf(errors[500]);
err.should.be.an.instanceOf(errors.InternalServerError);
should.not.exist(body);
should.not.exist(status);
body.should.eql('ah shit');
status.should.eql(500);
done();
});
});
});
describe('with a 500 status code', function () {
describe('with a 530 status code', function () {
it('passes back a Generic error', function (done) {
var trans = new Transport({
hosts: 'localhost'
@ -604,8 +621,8 @@ describe('Transport Class', function () {
path: '/huh?'
}, function (err, body, status) {
err.should.be.an.instanceOf(errors.Generic);
should.not.exist(body);
should.not.exist(status);
body.should.eql('boo');
status.should.eql(530);
done();
});
});
@ -639,8 +656,8 @@ describe('Transport Class', function () {
path: '/give-me-someth',
}, function (err, body, status) {
err.should.be.an.instanceOf(errors.Serialization);
should.not.exist(body);
should.not.exist(status);
body.should.eql('{"not":"valid');
status.should.eql(200);
done();
});
});
@ -663,6 +680,22 @@ describe('Transport Class', function () {
});
});
});
describe('with plain text', function () {
it('notices the content-type header and returns the text', function (done) {
var trans = new Transport({
hosts: 'localhost'
});
trans.request({
path: '/hottie-threads',
}, function (err, body, status) {
should.not.exist(err);
body.should.match(/s?he said/g);
done();
});
});
});
});
describe('return value', function () {
@ -680,22 +713,44 @@ describe('Transport Class', function () {
when.isPromise(ret).should.be.ok;
ret.abort.should.have.type('function');
});
it('the promise is always pulled from the defer created by this.createDefer()', function () {
it('promise is always pulled from the defer created by this.createDefer()', function () {
var fakePromise = {};
var tran = new Transport({
createDefer: function () {
return {
resolve: _.noop,
reject: _.noop,
promise: fakePromise
};
}
});
var origCreate = Transport.createDefer;
Transport.createDefer = function () {
return {
resolve: _.noop,
reject: _.noop,
promise: fakePromise
};
};
var tran = new Transport({});
shortCircuitRequest(tran);
var ret = tran.request({});
Transport.createDefer = origCreate;
ret.should.be.exactly(fakePromise);
ret.abort.should.have.type('function');
});
it('resolves the promise it returns with an object containing status and body keys', function (done) {
var serverMock = nock('http://esbox.1.com')
.get('/')
.reply(200, {
good: 'day'
});
var tran = new Transport({
hosts: 'http://esbox.1.com'
});
tran.request({}).then(function (resp) {
resp.should.eql({
body: {
good: 'day'
},
status: 200
});
done();
});
});
});
describe('aborting', function () {

View File

@ -29,6 +29,7 @@ var suites = testXml.create('testsuites');
var suiteCount = 0;
var moment = require('moment');
var _ = require('lodash');
var chalk = require('chalk');
function makeJUnitXml(runnerName, testDetails) {
_.each(testDetails.suites, function serializeSuite(suiteInfo) {
@ -78,10 +79,10 @@ function makeJUnitXml(runnerName, testDetails) {
}
if (suiteInfo.stdout.trim()) {
suite.ele('system-out', {}).cdata(suiteInfo.stdout);
suite.ele('system-out', {}).cdata(chalk.stripColor(suiteInfo.stdout));
}
if (suiteInfo.stderr.trim()) {
suite.ele('system-err', {}).cdata(suiteInfo.stderr);
suite.ele('system-err', {}).cdata(chalk.stripColor(suiteInfo.stderr));
}
});