Added the browser based test suite, have it running automatically via PhantomJS with grunt, all tests are passing except one, which requires PhantomJS send a body with a DELETE request

This commit is contained in:
Spencer Alger
2013-11-05 09:57:56 -07:00
parent 4273ffc2c7
commit 7e6fa479ad
34 changed files with 58054 additions and 1008 deletions

View File

@ -973,10 +973,9 @@ api.exists = ca({
* @param {String} params.preference - Specify the node or shard the operation should be performed on (default: random)
* @param {String} params.q - Query in the Lucene query string syntax
* @param {String} params.routing - Specific routing value
* @param {String} params.source - The URL-encoded query definition (instead of using the request body)
* @param {String|ArrayOfStrings|Boolean} params._source - True or false to return the _source field or not, or a list of fields to return
* @param {String|ArrayOfStrings|Boolean} params._sourceExclude - A list of fields to exclude from the returned _source field
* @param {String|ArrayOfStrings|Boolean} params._sourceInclude - A list of fields to extract and return from the _source field
* @param {String|ArrayOfStrings|Boolean} params.source - True or false to return the _source field or not, or a list of fields to return
* @param {String|ArrayOfStrings|Boolean} params.sourceExclude - A list of fields to exclude from the returned _source field
* @param {String|ArrayOfStrings|Boolean} params.sourceInclude - A list of fields to extract and return from the _source field
* @param {String} params.id - The document ID
* @param {String} params.index - The name of the index
* @param {String} params.type - The type of the document
@ -1029,17 +1028,14 @@ api.explain = ca({
type: 'string'
},
source: {
type: 'string'
},
_source: {
type: 'list',
name: '_source'
},
_sourceExclude: {
sourceExclude: {
type: 'list',
name: '_source_exclude'
},
_sourceInclude: {
sourceInclude: {
type: 'list',
name: '_source_include'
}
@ -1073,9 +1069,9 @@ api.explain = ca({
* @param {Boolean} params.realtime - Specify whether to perform the operation in realtime or search mode
* @param {Boolean} params.refresh - Refresh the shard containing the document before performing the operation
* @param {String} params.routing - Specific routing value
* @param {String|ArrayOfStrings|Boolean} params._source - True or false to return the _source field or not, or a list of fields to return
* @param {String|ArrayOfStrings|Boolean} params._sourceExclude - A list of fields to exclude from the returned _source field
* @param {String|ArrayOfStrings|Boolean} params._sourceInclude - A list of fields to extract and return from the _source field
* @param {String|ArrayOfStrings|Boolean} params.source - True or false to return the _source field or not, or a list of fields to return
* @param {String|ArrayOfStrings|Boolean} params.sourceExclude - A list of fields to exclude from the returned _source field
* @param {String|ArrayOfStrings|Boolean} params.sourceInclude - A list of fields to extract and return from the _source field
* @param {String} params.id - The document ID
* @param {String} params.index - The name of the index
* @param {String} [params.type=_all] - The type of the document (use `_all` to fetch the first document matching the ID across all types)
@ -1103,15 +1099,15 @@ api.get = ca({
routing: {
type: 'string'
},
_source: {
source: {
type: 'list',
name: '_source'
},
_sourceExclude: {
sourceExclude: {
type: 'list',
name: '_source_exclude'
},
_sourceInclude: {
sourceInclude: {
type: 'list',
name: '_source_include'
}
@ -1984,6 +1980,63 @@ api.indices.prototype.getAliases = ca({
});
/**
* Perform a [indices.getFieldMapping](http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-get-field-mapping.html) request
*
* @param {Object} params - An object with parameters used to carry out this action
* @param {Boolean} params.includeDefaults - Whether the default mapping values should be returned as well
* @param {String|ArrayOfStrings|Boolean} params.index - A comma-separated list of index names
* @param {String|ArrayOfStrings|Boolean} params.type - A comma-separated list of document types
* @param {String|ArrayOfStrings|Boolean} params.field - A comma-separated list of fields
*/
api.indices.prototype.getFieldMapping = ca({
methods: [
'GET'
],
params: {
includeDefaults: {
type: 'boolean',
name: 'include_defaults'
}
},
urls: [
{
fmt: '/<%=index%>/<%=type%>/_mapping/field/<%=field%>',
req: {
index: {
type: 'list'
},
type: {
type: 'list'
},
field: {
type: 'list'
}
}
},
{
fmt: '/<%=index%>/_mapping/field/<%=field%>',
req: {
index: {
type: 'list'
},
field: {
type: 'list'
}
}
},
{
fmt: '/_mapping/field/<%=field%>',
req: {
field: {
type: 'list'
}
}
}
]
});
/**
* Perform a [indices.getMapping](http://www.elasticsearch.org/guide/reference/api/admin-indices-get-mapping/) request
*
@ -2867,9 +2920,9 @@ api.info = ca({
* @param {String} params.preference - Specify the node or shard the operation should be performed on (default: random)
* @param {Boolean} params.realtime - Specify whether to perform the operation in realtime or search mode
* @param {Boolean} params.refresh - Refresh the shard containing the document before performing the operation
* @param {String|ArrayOfStrings|Boolean} params._source - True or false to return the _source field or not, or a list of fields to return
* @param {String|ArrayOfStrings|Boolean} params._sourceExclude - A list of fields to exclude from the returned _source field
* @param {String|ArrayOfStrings|Boolean} params._sourceInclude - A list of fields to extract and return from the _source field
* @param {String|ArrayOfStrings|Boolean} params.source - True or false to return the _source field or not, or a list of fields to return
* @param {String|ArrayOfStrings|Boolean} params.sourceExclude - A list of fields to exclude from the returned _source field
* @param {String|ArrayOfStrings|Boolean} params.sourceInclude - A list of fields to extract and return from the _source field
* @param {String} params.index - The name of the index
* @param {String} params.type - The type of the document
*/
@ -2891,15 +2944,15 @@ api.mget = ca({
refresh: {
type: 'boolean'
},
_source: {
source: {
type: 'list',
name: '_source'
},
_sourceExclude: {
sourceExclude: {
type: 'list',
name: '_source_exclude'
},
_sourceInclude: {
sourceInclude: {
type: 'list',
name: '_source_include'
}
@ -3208,10 +3261,9 @@ api.scroll = ca({
* @param {String} params.searchType - Search operation type
* @param {Number} params.size - Number of hits to return (default: 10)
* @param {String|ArrayOfStrings|Boolean} params.sort - A comma-separated list of <field>:<direction> pairs
* @param {String} params.source - The URL-encoded request definition using the Query DSL (instead of using request body)
* @param {String|ArrayOfStrings|Boolean} params._source - True or false to return the _source field or not, or a list of fields to return
* @param {String|ArrayOfStrings|Boolean} params._sourceExclude - A list of fields to exclude from the returned _source field
* @param {String|ArrayOfStrings|Boolean} params._sourceInclude - A list of fields to extract and return from the _source field
* @param {String|ArrayOfStrings|Boolean} params.source - True or false to return the _source field or not, or a list of fields to return
* @param {String|ArrayOfStrings|Boolean} params.sourceExclude - A list of fields to exclude from the returned _source field
* @param {String|ArrayOfStrings|Boolean} params.sourceInclude - A list of fields to extract and return from the _source field
* @param {String|ArrayOfStrings|Boolean} params.stats - Specific 'tag' of the request for logging and statistical purposes
* @param {String} params.suggestField - Specify which field to use for suggestions
* @param {String} [params.suggestMode=missing] - Specify suggest mode
@ -3307,17 +3359,14 @@ api.search = ca({
type: 'list'
},
source: {
type: 'string'
},
_source: {
type: 'list',
name: '_source'
},
_sourceExclude: {
sourceExclude: {
type: 'list',
name: '_source_exclude'
},
_sourceInclude: {
sourceInclude: {
type: 'list',
name: '_source_include'
},
@ -3450,7 +3499,7 @@ api.suggest = ca({
* @param {Date|Number} params.timestamp - Explicit timestamp for the document
* @param {Duration} params.ttl - Expiration time for the document
* @param {Number} params.version - Explicit version number for concurrency control
* @param {Number} params.versionType - Explicit version number for concurrency control
* @param {String} params.versionType - Specific version type
* @param {String} params.id - Document ID
* @param {String} params.index - The name of the index
* @param {String} params.type - The type of the document
@ -3512,7 +3561,11 @@ api.update = ca({
type: 'number'
},
versionType: {
type: 'number',
type: 'enum',
options: [
'internal',
'external'
],
name: 'version_type'
}
},

View File

@ -64,10 +64,10 @@ var castType = {
}
},
time: function (param, val, name) {
if (val instanceof Date) {
return val.getTime();
} else if (_.isNumeric(val)) {
if (typeof val === 'string' || _.isNumeric(val)) {
return val;
} else if (val instanceof Date) {
return val.getTime();
} else {
throw new TypeError('Invalid ' + name + ': expected some sort of time.');
}
@ -211,15 +211,17 @@ function exec(transport, spec, params, cb) {
// build a key list on demand
spec.paramKeys = _.keys(spec.params);
}
var key, param;
var key, param, name;
for (i = 0; i < spec.paramKeys.length; i++) {
key = spec.paramKeys[i];
param = spec.params[key];
// param keys don't always match the param name, in those cases it's stored in the param def as "name"
name = param.name || key;
try {
if (params[key] != null) {
query[key] = castType[param.type] ? castType[param.type](param, params[key], key) : params[key];
if (param['default'] && query[key] === param['default']) {
delete query[key];
query[name] = castType[param.type] ? castType[param.type](param, params[key], key) : params[key];
if (param['default'] && query[name] === param['default']) {
delete query[name];
}
} else if (param.required) {
throw new TypeError('Missing required parameter ' + key);

View File

@ -4,7 +4,7 @@
*
* @class connections.Angular
*/
module.exports = AngularConnection;
module.exports = AngularConnector;
var _ = require('../utils');
var ConnectionAbstract = require('../connection');
@ -12,12 +12,12 @@ var ConnectionFault = require('../errors').ConnectionFault;
/* global angular */
function AngularConnection(host, config) {
function AngularConnector(host, config) {
ConnectionAbstract.call(this, host, config);
}
_.inherits(AngularConnection, ConnectionAbstract);
_.inherits(AngularConnector, ConnectionAbstract);
AngularConnection.prototype.request = function (params, cb) {
AngularConnector.prototype.request = function (params, cb) {
var timeoutId;
this.$http({
@ -35,4 +35,4 @@ AngularConnection.prototype.request = function (params, cb) {
};
// must be overwritten before this connection can be used
AngularConnection.prototype.$http = null;
AngularConnector.prototype.$http = null;

View File

@ -4,48 +4,48 @@
* @param client {Client} - The Client that this class belongs to
* @param config {Object} - Configuration options
* @param [config.protocol=http:] {String} - The HTTP protocol that this connection will use, can be set to https:
* @class HttpConnection
* @class HttpConnector
*/
module.exports = HttpConnection;
module.exports = HttpConnector;
var http = require('http');
var https = require('https');
var _ = require('../utils');
var errors = require('../errors');
var qs = require('querystring');
var KeepAliveAgent = require('agentkeepalive/lib/agent');
var ConnectionAbstract = require('../connection');
var defaultHeaders = {
'connection': 'keep-alive'
};
function HttpConnection(host, config) {
function HttpConnector(host, config) {
ConnectionAbstract.call(this, host, config);
this.agent = new http.Agent({
keepAlive: true,
// delay between the last data packet received and the first keepalive probe
keepAliveMsecs: 1000,
this.hand = require(this.host.protocol);
this.agent = new KeepAliveAgent({
maxSockets: 1,
maxFreeSockets: this.config.maxFreeSockets
maxKeepAliveRequests: 0, // max requests per keepalive socket, default is 0, no limit.
maxKeepAliveTime: 30000 // keepalive for 30 seconds
});
this.on('closed', this.bound.onClosed);
this.once('alive', this.bound.onAlive);
this.on('alive', this.bound.onAlive);
}
_.inherits(HttpConnection, ConnectionAbstract);
_.inherits(HttpConnector, ConnectionAbstract);
HttpConnection.prototype.onClosed = _.handler(function () {
HttpConnector.prototype.onClosed = _.handler(function () {
this.agent.destroy();
this.removeAllListeners();
});
HttpConnection.prototype.onAlive = _.handler(function () {
HttpConnector.prototype.onAlive = _.handler(function () {
// only set the agents max agents config once the connection is verified to be alive
this.agent.maxSockets = this.config.maxSockets;
});
HttpConnection.prototype.makeReqParams = function (params) {
HttpConnector.prototype.makeReqParams = function (params) {
var reqParams = {
method: params.method,
protocol: this.host.protocol + ':',
@ -56,14 +56,31 @@ HttpConnection.prototype.makeReqParams = function (params) {
headers: this.host.headers,
agent: this.agent
};
var query = this.host.query ? this.host.query : null;
var queryStr;
if (typeof query === 'string') {
query = qs.parse(query);
}
var query = qs.stringify(this.host.query ? _.defaults(params.query, this.host.query) : params.query);
reqParams.path += query ? '?' + query : '';
if (params.query) {
query = _.defaults({},
typeof params.query === 'string' ? qs.parse(params.query) : params.query,
query || {}
);
}
if (query) {
queryStr = qs.stringify(query);
}
if (queryStr) {
reqParams.path = reqParams.path + '?' + queryStr;
}
return reqParams;
};
HttpConnection.prototype.request = function (params, cb) {
HttpConnector.prototype.request = function (params, cb) {
var incoming;
var timeoutId;
var request;
@ -78,7 +95,7 @@ HttpConnection.prototype.request = function (params, cb) {
// general clean-up procedure to run after the request
// completes, has an error, or is aborted.
var cleanUp = function (err) {
var cleanUp = _.bind(function (err) {
clearTimeout(timeoutId);
request && request.removeAllListeners();
@ -88,22 +105,14 @@ HttpConnection.prototype.request = function (params, cb) {
err = void 0;
} else {
log.error(err);
if (err instanceof errors.RequestTimeout) {
request.on('error', function catchAbortError() {
request.removeListener('error', catchAbortError);
});
} else {
this.setStatus('dead');
}
this.setStatus('dead');
}
log.trace(params.method, reqParams, params.body, response, status);
cb(err, response, status);
};
}, this);
request = http.request(reqParams, function (_incoming) {
request = this.hand.request(reqParams, function (_incoming) {
incoming = _incoming;
status = incoming.statusCode;
incoming.setEncoding('utf8');

View File

@ -5,11 +5,11 @@
*
* @class {XhrConnection}
*/
module.exports = JqueryConnection;
module.exports = JqueryConnector;
function JqueryConnection() {}
function JqueryConnector() {}
JqueryConnection.prototype.request = function (params, cb) {
JqueryConnector.prototype.request = function (params, cb) {
var $xhr = jQuery.ajax(params).done(cb);
};

View File

@ -3,7 +3,7 @@
*
* @class connections.Xhr
*/
module.exports = XhrConnection;
module.exports = XhrConnector;
/* jshint browser:true */
@ -11,11 +11,12 @@ var _ = require('../utils');
var ConnectionAbstract = require('../connection');
var ConnectionFault = require('../errors').ConnectionFault;
var TimeoutError = require('../errors').RequestTimeout;
var asyncDefault = !(navigator && /PhantomJS/i.test(navigator.userAgent));
function XhrConnection(host, config) {
function XhrConnector(host, config) {
ConnectionAbstract.call(this, host, config);
}
_.inherits(XhrConnection, ConnectionAbstract);
_.inherits(XhrConnector, ConnectionAbstract);
/**
* Simply returns an XHR object cross browser
@ -46,30 +47,36 @@ if (!getXhr) {
throw new Error('getXhr(): XMLHttpRequest not available');
}
XhrConnection.prototype.request = function (params, cb) {
XhrConnector.prototype.request = function (params, cb) {
var xhr = getXhr();
var timeout = params.timeout ? params.timeout : 10000;
var timeoutId;
var url = this.host.makeUrl(params);
var log = this.config.log;
var async = params.async === false ? false : asyncDefault;
if (params.auth) {
xhr.open(params.method, url, true, params.auth.user, params.auth.pass);
xhr.open(params.method, url, async, params.auth.user, params.auth.pass);
} else {
xhr.open(params.method, url, true);
xhr.open(params.method, url, async);
}
xhr.onreadystatechange = function (e) {
if (xhr.readyState === 4) {
clearTimeout(timeoutId);
cb(xhr.status ? null : new ConnectionFault(), xhr.responseText, xhr.status);
log.trace(params.method, url, params.body, xhr.responseText, xhr.status);
var err = xhr.status ? void 0 : new ConnectionFault(xhr.statusText || 'Request failed to complete.');
cb(err, xhr.responseText, xhr.status);
}
};
if (params.timeout !== Infinity) {
if (timeout !== Infinity) {
timeoutId = setTimeout(function () {
xhr.onreadystatechange = _.noop;
xhr.abort();
cb(new TimeoutError());
}, params.timeout);
}, timeout);
}
xhr.send(params.body || null);
xhr.send(params.body || void 0);
};

View File

@ -5,7 +5,11 @@ function ErrorAbstract(msg, constructor) {
this.message = msg;
Error.call(this, this.message);
Error.captureStackTrace(this, constructor);
if (process.browser) {
this.stack = '';
} else {
Error.captureStackTrace(this, constructor);
}
}
_.inherits(ErrorAbstract, Error);

View File

@ -87,10 +87,6 @@ Host.prototype.makeUrl = function (params) {
// just stringify the hosts query
query = qs.stringify(this.query);
}
// prepend the ? if there is actually a valid query string
if (query) {
query = '?' + query;
}
return this.protocol + '://' + this.host + port + path + query;
return this.protocol + '://' + this.host + port + path + (query ? '?' + query : '');
};

View File

@ -246,7 +246,7 @@ Log.prototype.info = function (/* ...msg */) {
*/
Log.prototype.debug = function (/* ...msg */) {
if (EventEmitter.listenerCount(this, 'debug')) {
return this.emit('debug', Log.join(arguments) + _.getStackTrace(Log.prototype.debug));
return this.emit('debug', Log.join(arguments) /*+ _.getStackTrace(Log.prototype.debug)*/);
}
};
@ -264,10 +264,17 @@ Log.prototype.debug = function (/* ...msg */) {
*/
Log.prototype.trace = function (method, requestUrl, body, responseBody, responseStatus) {
if (EventEmitter.listenerCount(this, 'trace')) {
if (typeof requestUrl === 'object') {
requestUrl = url.format(requestUrl);
if (typeof requestUrl === 'string') {
requestUrl = url.parse(requestUrl, true, true);
}
return this.emit('trace', method, requestUrl, body, responseBody, responseStatus);
requestUrl = _.defaults({
host: 'localhost:9200',
query: _.defaults({
pretty: true
}, requestUrl.query)
}, requestUrl);
delete requestUrl.auth;
return this.emit('trace', method, url.format(requestUrl), body, responseBody, responseStatus);
}
};

View File

@ -49,10 +49,9 @@ Console.prototype.setupListeners = function (levels) {
*/
Console.prototype.onError = _.handler(function (e) {
if (console.error && console.trace) {
console.error(e.name === 'Error' ? 'ERROR' : e.name);
console.trace();
console.error(e.name === 'Error' ? 'ERROR' : e.name, e.stack || e.message);
} else {
console.log(e.name === 'Error' ? 'ERROR' : e.name, e.stack);
console.log(e.name === 'Error' ? 'ERROR' : e.name, e.stack || e.message);
}
});
@ -64,7 +63,7 @@ Console.prototype.onError = _.handler(function (e) {
* @param {String} msg - The message to be logged
* @return {undefined}
*/
Console.prototype.onWarning = console[console.warn ? 'warn' : 'log'].bind(console, 'WARNING');
Console.prototype.onWarning = _.bindKey(console, console.warn ? 'warn' : 'log', 'WARNING');
/**
* Handler for the bridges "info" event
@ -74,7 +73,7 @@ Console.prototype.onWarning = console[console.warn ? 'warn' : 'log'].bind(consol
* @param {String} msg - The message to be logged
* @return {undefined}
*/
Console.prototype.onInfo = console[console.info ? 'info' : 'log'].bind(console, 'INFO');
Console.prototype.onInfo = _.bindKey(console, console.info ? 'info' : 'log', 'INFO');
/**
* Handler for the bridges "debug" event
@ -84,7 +83,7 @@ Console.prototype.onInfo = console[console.info ? 'info' : 'log'].bind(console,
* @param {String} msg - The message to be logged
* @return {undefined}
*/
Console.prototype.onDebug = console[console.debug ? 'debug' : 'log'].bind(console, 'DEBUG');
Console.prototype.onDebug = _.bindKey(console, console.debug ? 'debug' : 'log', 'DEBUG');
/**
* Handler for the bridges "trace" event
@ -99,5 +98,5 @@ Console.prototype.onTrace = _.handler(function (method, url, body, responseBody,
message += ' -d "' + body.replace(/"/g, '\\"') + '"';
}
message += '\n<- ' + responseStatus + '\n' + responseBody;
console.log('TRACE', message);
console.log('TRACE:\n' + message + '\n\n');
});

View File

@ -71,7 +71,7 @@ TransportRequest.prototype._sendReqWithCon = _.handler(function (err, con) {
TransportRequest.prototype._checkRespForFail = _.handler(function (err, body, status) {
if (err && this._remainingRetries) {
this._remainingRetries--;
this._log.info('Connection error, retrying');
this._log.error(err.message, '-- retrying');
this._connectionPool.select(this.bound._sendReqWithCon);
} else {
this._log.info('Request complete');

View File

@ -156,6 +156,10 @@ function adjustWordCase(firstWordCap, otherWordsCap, sep) {
if (word.length) {
words.push(word);
}
// add the leading underscore back to strings the had it originally
if (words.lenth && string.charAt(0) === '_') {
words[0] = '_' + words[0];
}
return words.join(sep);
};
}
@ -409,10 +413,15 @@ _.makeBoundMethods = function (obj, methods) {
_.noop = function () {};
_.getStackTrace = function (callee) {
var e = {};
Error.captureStackTrace(e, callee || _.getStackTrace);
return '\n' + e.stack.split('\n').slice(1).join('\n');
};
// _.getStackTrace = function (callee) {
// var e = {};
// if (typeof Error.captureStackTrace === 'function') {
// Error.captureStackTrace(e, callee || _.getStackTrace);
// } else {
// e.stack = (new Error()).stack;
// console.log(e.stack);
// }
// return '\n' + e.stack.split('\n').slice(1).join('\n');
// };
module.exports = utils;