save point durring huge unorganized refactor
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@ -1,4 +1,5 @@
|
||||
dist
|
||||
npm-debug.log
|
||||
node_modules
|
||||
scripts/scratch*
|
||||
test/integration/yaml_suite/log
|
||||
@ -6,4 +7,5 @@ test/integration/yaml_suite/log
|
||||
## generated files
|
||||
scripts/last_rest_spec_update.sha
|
||||
test/browser_integration/yaml_tests.js
|
||||
test/**/test-output-*.xml
|
||||
test-output-*.xml
|
||||
coverage.html
|
||||
|
||||
240
Gruntfile.js
240
Gruntfile.js
@ -3,17 +3,6 @@
|
||||
|
||||
module.exports = function (grunt) {
|
||||
|
||||
var _ = require('lodash');
|
||||
|
||||
var sharedBrowserfyExclusions = [
|
||||
'when',
|
||||
'src/lib/connectors/http.js',
|
||||
'src/lib/loggers/file.js',
|
||||
'src/lib/loggers/stdio.js',
|
||||
'src/lib/loggers/stream.js',
|
||||
'src/lib/loggers/stream.js'
|
||||
];
|
||||
|
||||
// Project configuration.
|
||||
grunt.initConfig({
|
||||
distDir: 'dist',
|
||||
@ -31,20 +20,6 @@ module.exports = function (grunt) {
|
||||
src: ['<%= distDir %>']
|
||||
}
|
||||
},
|
||||
mochaTest: {
|
||||
unit: 'test/unit/**/*.test.js',
|
||||
yaml_suite: {
|
||||
src: 'test/integration/yaml_suite/index.js',
|
||||
options: {
|
||||
reporter: require('./test/integration/yaml_suite/reporter')
|
||||
}
|
||||
},
|
||||
options: {
|
||||
require: 'should',
|
||||
reporter: 'dot',
|
||||
timeout: 11e3
|
||||
}
|
||||
},
|
||||
jshint: {
|
||||
source: {
|
||||
src: [
|
||||
@ -58,42 +33,6 @@ module.exports = function (grunt) {
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
source: {
|
||||
files: [
|
||||
'src/**/*',
|
||||
'test/**/*',
|
||||
'Gruntfile.js'
|
||||
],
|
||||
tasks: [
|
||||
'jshint:source'
|
||||
]
|
||||
},
|
||||
options: {
|
||||
interupt: true
|
||||
}
|
||||
},
|
||||
run: {
|
||||
generate_js_api: {
|
||||
args: [
|
||||
'scripts/generate/js_api'
|
||||
]
|
||||
},
|
||||
generate_yaml_tests: {
|
||||
args: [
|
||||
'scripts/generate/yaml_tests'
|
||||
]
|
||||
},
|
||||
integration_server: {
|
||||
args: [
|
||||
'test/browser_integration/server.js'
|
||||
],
|
||||
options: {
|
||||
wait: false,
|
||||
ready: /server listening/
|
||||
}
|
||||
}
|
||||
},
|
||||
browserify: {
|
||||
client: {
|
||||
files: {
|
||||
@ -101,43 +40,34 @@ module.exports = function (grunt) {
|
||||
},
|
||||
options: {
|
||||
standalone: 'elasticsearch',
|
||||
ignore: _.union(sharedBrowserfyExclusions, [
|
||||
ignore: [
|
||||
'src/lib/connectors/jquery.js',
|
||||
'src/lib/connectors/angular.js'
|
||||
])
|
||||
]
|
||||
}
|
||||
},
|
||||
angular_client: {
|
||||
angular: {
|
||||
files: {
|
||||
'<%= distDir %>/elasticsearch.angular.js': ['src/elasticsearch.angular.js']
|
||||
},
|
||||
options: {
|
||||
standalone: 'elasticsearch',
|
||||
ignore: _.union(sharedBrowserfyExclusions, [
|
||||
ignore: [
|
||||
'src/lib/connectors/jquery.js',
|
||||
'src/lib/connectors/xhr.js'
|
||||
])
|
||||
}
|
||||
},
|
||||
yaml_suite: {
|
||||
files: {
|
||||
'test/browser_integration/yaml_tests.js': ['test/integration/yaml_suite/index.js']
|
||||
},
|
||||
options: {
|
||||
external: [
|
||||
'optimist'
|
||||
'src/lib/connectors/xhr.js',
|
||||
'when'
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
concat: {
|
||||
dist_banners: {
|
||||
},
|
||||
jquery: {
|
||||
files: {
|
||||
'<%= distDir %>/elasticsearch.js': ['<%= distDir %>/elasticsearch.js'],
|
||||
'<%= distDir %>/elasticsearch.angular.js': ['<%= distDir %>/elasticsearch.angular.js']
|
||||
'<%= distDir %>/elasticsearch.jquery.js': ['src/elasticsearch.jquery.js']
|
||||
},
|
||||
options: {
|
||||
banner: '<%= meta.banner %>'
|
||||
ignore: [
|
||||
'src/lib/connectors/angular.js',
|
||||
'src/lib/connectors/xhr.js',
|
||||
'when'
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -145,145 +75,41 @@ module.exports = function (grunt) {
|
||||
dist: {
|
||||
files: {
|
||||
'<%= distDir %>/elasticsearch.min.js': '<%= distDir %>/elasticsearch.js',
|
||||
'<%= distDir %>/elasticsearch.angular.min.js': '<%= distDir %>/elasticsearch.angular.js'
|
||||
'<%= distDir %>/elasticsearch.angular.min.js': '<%= distDir %>/elasticsearch.angular.js',
|
||||
'<%= distDir %>/elasticsearch.jquery.min.js': '<%= distDir %>/elasticsearch.jquery.js'
|
||||
}
|
||||
}
|
||||
},
|
||||
concat: {
|
||||
dist_banners: {
|
||||
files: {
|
||||
'<%= distDir %>/elasticsearch.angular.js': '<%= distDir %>/elasticsearch.angular.js',
|
||||
'<%= distDir %>/elasticsearch.angular.min.js': '<%= distDir %>/elasticsearch.angular.min.js',
|
||||
'<%= distDir %>/elasticsearch.jquery.js': '<%= distDir %>/elasticsearch.jquery.js',
|
||||
'<%= distDir %>/elasticsearch.jquery.min.js': '<%= distDir %>/elasticsearch.jquery.min.js',
|
||||
'<%= distDir %>/elasticsearch.js': '<%= distDir %>/elasticsearch.js',
|
||||
'<%= distDir %>/elasticsearch.min.js': '<%= distDir %>/elasticsearch.min.js'
|
||||
},
|
||||
options: {
|
||||
report: 'min',
|
||||
banner: '<%= meta.banner %>'
|
||||
},
|
||||
global_defs: {
|
||||
process: {
|
||||
browser: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// load plugins
|
||||
grunt.loadNpmTasks('grunt-run');
|
||||
grunt.loadNpmTasks('grunt-open');
|
||||
grunt.loadNpmTasks('grunt-browserify');
|
||||
grunt.loadNpmTasks('grunt-mocha-test');
|
||||
grunt.loadNpmTasks('grunt-contrib-clean');
|
||||
grunt.loadNpmTasks('grunt-contrib-watch');
|
||||
grunt.loadNpmTasks('grunt-contrib-concat');
|
||||
grunt.loadNpmTasks('grunt-contrib-uglify');
|
||||
grunt.loadNpmTasks('grunt-contrib-jshint');
|
||||
|
||||
// Default task.
|
||||
// Default task runs the build process.
|
||||
grunt.registerTask('default', [
|
||||
'generate',
|
||||
'test',
|
||||
'build'
|
||||
'clean:dist',
|
||||
'browserify',
|
||||
'uglify:dist',
|
||||
'concat:dist_banners'
|
||||
]);
|
||||
|
||||
// generates the parts of the yaml test suite and api.
|
||||
grunt.registerTask('generate', [
|
||||
'run:generate_yaml_tests',
|
||||
'run:generate_js_api'
|
||||
]);
|
||||
|
||||
// runs the tests, must be run after generate
|
||||
grunt.registerTask('test', function () {
|
||||
grunt.task.requires('generate');
|
||||
grunt.task.run([
|
||||
'jshint',
|
||||
'mochaTest:unit',
|
||||
'mochaTest:yaml_suite'
|
||||
]);
|
||||
});
|
||||
|
||||
// runs the build process.
|
||||
grunt.registerTask('build', function () {
|
||||
grunt.task.requires('generate');
|
||||
grunt.task.run([
|
||||
'clean:dist',
|
||||
'browserify',
|
||||
'uglify:dist',
|
||||
'concat:dist_banners'
|
||||
]);
|
||||
});
|
||||
|
||||
var browsers = {
|
||||
safari: {
|
||||
darwin: 'Safari'
|
||||
},
|
||||
chrome: {
|
||||
darwin: 'Google Chrome',
|
||||
win32: 'Google Chrome',
|
||||
executable: 'google-chrome'
|
||||
},
|
||||
chromium: {
|
||||
executable: 'chromium-browser',
|
||||
},
|
||||
firefox: {
|
||||
darwin: 'Firefox',
|
||||
win32: 'Firefox',
|
||||
executable: 'firefox'
|
||||
},
|
||||
opera: {
|
||||
darwin: 'Opera',
|
||||
win32: 'Opera',
|
||||
executable: 'opera'
|
||||
}
|
||||
};
|
||||
|
||||
// creates browser_tests:{{browser}} tasks, for the browsers listed directly above
|
||||
Object.keys(browsers).forEach(function (browser) {
|
||||
var appName = browsers[browser][process.platform];
|
||||
// on other platforms, open expects app to be the name of the executale...
|
||||
if (!appName && process.platform !== 'darwin' && process.platform !== 'win32') {
|
||||
appName = browsers[browser].executable;
|
||||
}
|
||||
|
||||
if (!appName) {
|
||||
// this browser doesn't run on this arch
|
||||
return;
|
||||
}
|
||||
|
||||
grunt.config.set('__open_browser_tests.' + browser, {
|
||||
appName: appName
|
||||
});
|
||||
|
||||
grunt.registerTask('browser_tests:' + browser, [
|
||||
'generate',
|
||||
'build',
|
||||
'run:integration_server',
|
||||
'__open_browser_tests:' + browser
|
||||
]);
|
||||
});
|
||||
|
||||
/**
|
||||
* USE browser_tests:{{browser}} to run this task
|
||||
*
|
||||
* Change the port/host that the client connects to with the ES_HOST and ES_PORT environment variables
|
||||
*
|
||||
* You must always run the build task first, to ensure that the lastest API and yaml tests are available.
|
||||
* This is run in the default and browser_tests:{{browser}} tests.
|
||||
*/
|
||||
grunt.registerMultiTask('__open_browser_tests', function () {
|
||||
var host = grunt.option('host') || 'localhost';
|
||||
var port = grunt.option('port') || 9200;
|
||||
var taskData = this.data;
|
||||
|
||||
grunt.task.requires([
|
||||
'generate',
|
||||
'build',
|
||||
'run:integration_server'
|
||||
]);
|
||||
|
||||
grunt.config.set('open.yaml_suite_' + this.target, {
|
||||
path: 'http://localhost:8888?es_hostname=' + encodeURIComponent(host) +
|
||||
'&es_port=' + encodeURIComponent(port) +
|
||||
'&browser=' + encodeURIComponent(this.target),
|
||||
app: taskData.appName
|
||||
});
|
||||
|
||||
grunt.task.run([
|
||||
'open:yaml_suite_' + this.target,
|
||||
'wait:integration_server'
|
||||
]);
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
0
LICENSE.md
Normal file
0
LICENSE.md
Normal file
197
README.md
197
README.md
@ -60,200 +60,9 @@ bower install elasticsearch-jquery
|
||||
```
|
||||
|
||||
## Docs
|
||||
- [Configuration](#configuration)
|
||||
- [Configuration](docs/configuration.md)
|
||||
- [Examples](docs/examples.md)
|
||||
- [API](docs/api.md)
|
||||
- [Replacing Core Components](docs/replacing_core_components.md)
|
||||
- [Errors](docs/errors.md)
|
||||
- [Setting Up Logging](docs/setting_up_logging.md)
|
||||
- [FAQ](#faq)
|
||||
|
||||
## Configuration
|
||||
|
||||
The `Client` constructor accepts a single object as it's argument, and the following keys can be used to configure that client instance:
|
||||
|
||||
```js
|
||||
var elasticsearch = require('elasticsearch');
|
||||
var es = new elasticsearch.Client({
|
||||
...
|
||||
});
|
||||
```
|
||||
|
||||
### hosts
|
||||
Type: `String`, `String[]` or `Object[]`
|
||||
|
||||
Default:
|
||||
```js
|
||||
hosts: [
|
||||
{
|
||||
host: 'localhost', port: '9200', protocol: 'http'
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
Specify the list of hosts that this client will connect to. If sniffing is enabled, or you call sniff, this list will be used as seeds for discovery of the rest of the cluster.
|
||||
|
||||
### log
|
||||
Type: `String`, `String[]`, `Object`, `Object[]`, or `Constructor`
|
||||
|
||||
Default:
|
||||
```js
|
||||
log: {
|
||||
type: 'stdio',
|
||||
levels: ['error', 'warning']
|
||||
}
|
||||
```
|
||||
|
||||
Unless a constructor is specified, this sets the output settings for the bundled logger. See [setting up logging](docs/setting_up_logging.md) for more information.
|
||||
|
||||
### connectionClass
|
||||
Type: `String`, `Constructor`
|
||||
|
||||
Default:
|
||||
- Node: `'http'`
|
||||
- Browser: `'xhr'`
|
||||
- Angular Build: `'angular'`
|
||||
- jQuery Build: `'jquery'`
|
||||
|
||||
Defines the class that will be used to create connections. If you are looking to implement a protocol besides HTTP you will probably start by writing a Connection class and specifying it here.
|
||||
|
||||
### selector
|
||||
Type: `String`, `Function`
|
||||
|
||||
Default: `'roundRobin'`
|
||||
|
||||
Options:
|
||||
- `'roundRobin'`
|
||||
- `'random'`
|
||||
|
||||
This function will be used to select a connection from the ConnectionPool. It should received a single argument, the list of "active" connections, and return the connection to use. Use this selector to implement special logic for your client such as preferring nodes in a certain rack or data-center.
|
||||
|
||||
To make this function asynchronous, accept a second argument which will be the callback to use. The callback should be called Node-style, with a possible error like `cb(err, selectedConnection)`.
|
||||
|
||||
### sniffOnStart
|
||||
Type: `Boolean`
|
||||
|
||||
Default: `false`
|
||||
|
||||
Should the client attempt to detect the rest of the cluster when it is first instantiated?
|
||||
|
||||
### sniffAfterRequests
|
||||
Type: `Number` or `false`
|
||||
|
||||
Default: `false`
|
||||
|
||||
After `n` requests, perform a sniff operation and ensure our list of nodes is up to date.
|
||||
|
||||
|
||||
### sniffOnConnectionFail
|
||||
Type: `Boolean`
|
||||
|
||||
Default: `false`
|
||||
|
||||
Should the client immediately sniff for a more current list of nodes when a connection dies? (see [node death](#node-death))
|
||||
|
||||
### maxRetries
|
||||
Type: `Number`
|
||||
|
||||
Defailt: `3`
|
||||
|
||||
How many times should the client try to connect to other nodes before returning a [ConnectionFault](docs/error.md#connectionfault) error. (see [node death](#node-death))
|
||||
|
||||
### timeout
|
||||
Type: `Number`
|
||||
|
||||
Default: 10000
|
||||
|
||||
How many milliseconds can the connection take before the request is aborted and retried. (TODO: timeout errors shouldn't cause a retry).
|
||||
|
||||
### deadTimeout
|
||||
Type: `Number`
|
||||
|
||||
Default: 30000
|
||||
|
||||
How many milliseconds should a dead connection/node sit and wait before it is ping-ed? (see [node death](#node-death))
|
||||
|
||||
### maxSockets
|
||||
Type: `Number`
|
||||
|
||||
Default: 10
|
||||
|
||||
How many sockets should a connection keep to it's corresponding Elasticsearch node? These sockets are currently kept alive ***forever*** (not like nodes current "keep alive" sockets).
|
||||
|
||||
### nodesToHostCallback
|
||||
Type: `Function`
|
||||
|
||||
Default: simple, not much going on [here](src/lib/client_config.js#L65).
|
||||
|
||||
This function will receive a list of nodes received during a sniff. The list of nodes should be transformed into an array of objects which will each be used to create [Host](src/lib/host.js) objects. (TODO: allow this function to be async).
|
||||
|
||||
## API
|
||||
|
||||
To maintain consistency across all the low-level clients ([PHP](https://github.com/elasticsearch/elasticsearch-php), [Python](https://github.com/elasticsearch/elasticsearch-ph), [Ruby](https://github.com/elasticsearch/elasticsearch-ruby), [Perl](https://github.com/elasticsearch/elasticsearch-perl)) all API methods accept an object with parameters and a callback. If you don't pass the callback, the functions will return a promise.
|
||||
|
||||
For full details on the API, check out [api.md](docs/api.md).
|
||||
|
||||
### Examples
|
||||
|
||||
#### create the client
|
||||
```js
|
||||
var es = new elasticsearch.Client({
|
||||
hosts: [
|
||||
'localhost:9200'
|
||||
],
|
||||
log: 'trace',
|
||||
sniffOnStart: true
|
||||
});
|
||||
```
|
||||
|
||||
#### call an endpoint
|
||||
```js
|
||||
es.cluster.nodeInfo({
|
||||
clear: true,
|
||||
jvm: true,
|
||||
os: ture
|
||||
}, function (err, resp, status) {
|
||||
// do your thing
|
||||
})
|
||||
```
|
||||
|
||||
#### skip the callback to get a promise back
|
||||
```js
|
||||
es.search({
|
||||
q: 'pants'
|
||||
}).then(function (resp) {
|
||||
// use resp.body and resp.status
|
||||
}, function (err) {
|
||||
// freak out!
|
||||
})
|
||||
```
|
||||
|
||||
#### abort a request
|
||||
```js
|
||||
var req = es.search({
|
||||
q: 'robots'
|
||||
}, function (err, body, status) {
|
||||
clearTimeout(timeout);
|
||||
// do something
|
||||
});
|
||||
|
||||
var timeout = setTimeout(function () {
|
||||
req.abort();
|
||||
}, 200);
|
||||
```
|
||||
|
||||
#### or just use the timeout param
|
||||
```js
|
||||
es.search({
|
||||
q: '*',
|
||||
timeout: 200
|
||||
}).then(function (resp) {
|
||||
// Iterate all the hits
|
||||
})
|
||||
```
|
||||
|
||||
## FAQ
|
||||
|
||||
### dead nodes
|
||||
Q: When is a connection/node considered dead?
|
||||
|
||||
A: A connection is considered dead when a request to it does not complete properly. If the server responds with any status, even 500, it is not considered dead.
|
||||
- [Customize Logging](docs/customize_logging.md)
|
||||
|
||||
117
docs/configuration.md
Normal file
117
docs/configuration.md
Normal file
@ -0,0 +1,117 @@
|
||||
|
||||
The `Client` constructor accepts a single object as it's argument, and the following keys can be used to configure that client instance:
|
||||
|
||||
```js
|
||||
var elasticsearch = require('elasticsearch');
|
||||
var es = new elasticsearch.Client({
|
||||
...
|
||||
});
|
||||
```
|
||||
|
||||
### hosts
|
||||
Type: `String`, `String[]` or `Object[]`
|
||||
|
||||
Default:
|
||||
```js
|
||||
hosts: [
|
||||
{
|
||||
host: 'localhost', port: '9200', protocol: 'http'
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
Specify the list of hosts that this client will connect to. If sniffing is enabled, or you call sniff, this list will be used as seeds for discovery of the rest of the cluster.
|
||||
|
||||
### log
|
||||
Type: `String`, `String[]`, `Object`, `Object[]`, or `Constructor`
|
||||
|
||||
Default:
|
||||
```js
|
||||
log: {
|
||||
type: 'stdio',
|
||||
levels: ['error', 'warning']
|
||||
}
|
||||
```
|
||||
|
||||
Unless a constructor is specified, this sets the output settings for the bundled logger. See [setting up logging](docs/setting_up_logging.md) for more information.
|
||||
|
||||
### connectionClass
|
||||
Type: `String`, `Constructor`
|
||||
|
||||
Default:
|
||||
- Node: `'http'`
|
||||
- Browser: `'xhr'`
|
||||
- Angular Build: `'angular'`
|
||||
- jQuery Build: `'jquery'`
|
||||
|
||||
Defines the class that will be used to create connections. If you are looking to implement a protocol besides HTTP you will probably start by writing a Connection class and specifying it here.
|
||||
|
||||
### selector
|
||||
Type: `String`, `Function`
|
||||
|
||||
Default: `'roundRobin'`
|
||||
|
||||
Options:
|
||||
- `'roundRobin'`
|
||||
- `'random'`
|
||||
|
||||
This function will be used to select a connection from the ConnectionPool. It should received a single argument, the list of "active" connections, and return the connection to use. Use this selector to implement special logic for your client such as preferring nodes in a certain rack or data-center.
|
||||
|
||||
To make this function asynchronous, accept a second argument which will be the callback to use. The callback should be called Node-style, with a possible error like `cb(err, selectedConnection)`.
|
||||
|
||||
### sniffOnStart
|
||||
Type: `Boolean`
|
||||
|
||||
Default: `false`
|
||||
|
||||
Should the client attempt to detect the rest of the cluster when it is first instantiated?
|
||||
|
||||
### sniffAfterRequests
|
||||
Type: `Number` or `false`
|
||||
|
||||
Default: `false`
|
||||
|
||||
After `n` requests, perform a sniff operation and ensure our list of nodes is up to date.
|
||||
|
||||
|
||||
### sniffOnConnectionFail
|
||||
Type: `Boolean`
|
||||
|
||||
Default: `false`
|
||||
|
||||
Should the client immediately sniff for a more current list of nodes when a connection dies? (see [node death](#node-death))
|
||||
|
||||
### maxRetries
|
||||
Type: `Number`
|
||||
|
||||
Defailt: `3`
|
||||
|
||||
How many times should the client try to connect to other nodes before returning a [ConnectionFault](docs/error.md#connectionfault) error. (see [node death](#node-death))
|
||||
|
||||
### timeout
|
||||
Type: `Number`
|
||||
|
||||
Default: 10000
|
||||
|
||||
How many milliseconds can the connection take before the request is aborted and retried. (TODO: timeout errors shouldn't cause a retry).
|
||||
|
||||
### deadTimeout
|
||||
Type: `Number`
|
||||
|
||||
Default: 30000
|
||||
|
||||
How many milliseconds should a dead connection/node sit and wait before it is ping-ed? (see [node death](#node-death))
|
||||
|
||||
### maxSockets
|
||||
Type: `Number`
|
||||
|
||||
Default: 10
|
||||
|
||||
How many sockets should a connection keep to it's corresponding Elasticsearch node? These sockets are currently kept alive ***forever*** (not like nodes current "keep alive" sockets).
|
||||
|
||||
### nodesToHostCallback
|
||||
Type: `Function`
|
||||
|
||||
Default: simple, not much going on [here](src/lib/client_config.js#L65).
|
||||
|
||||
This function will receive a list of nodes received during a sniff. The list of nodes should be transformed into an array of objects which will each be used to create [Host](src/lib/host.js) objects. (TODO: allow this function to be async).
|
||||
@ -1,3 +1,3 @@
|
||||
# Setting Up Logging
|
||||
# Customize Logging
|
||||
|
||||
TODO: what are loggers, how to use bunyan/winston
|
||||
52
docs/examples.md
Normal file
52
docs/examples.md
Normal file
@ -0,0 +1,52 @@
|
||||
### Examples
|
||||
#### create the client
|
||||
```js
|
||||
var es = new elasticsearch.Client({
|
||||
hosts: [
|
||||
'localhost:9200'
|
||||
],
|
||||
log: 'trace',
|
||||
sniffOnStart: true
|
||||
});
|
||||
```
|
||||
#### call an endpoint
|
||||
```js
|
||||
es.cluster.nodeInfo({
|
||||
clear: true,
|
||||
jvm: true,
|
||||
os: ture
|
||||
}, function (err, resp, status) {
|
||||
// do your thing
|
||||
})
|
||||
```
|
||||
#### skip the callback to get a promise back
|
||||
```js
|
||||
es.search({
|
||||
q: 'pants'
|
||||
}).then(function (resp) {
|
||||
// use resp.body and resp.status
|
||||
}, function (err) {
|
||||
// freak out!
|
||||
})
|
||||
```
|
||||
#### abort a request
|
||||
```js
|
||||
var req = es.search({
|
||||
q: 'robots'
|
||||
}, function (err, body, status) {
|
||||
clearTimeout(timeout);
|
||||
// do something
|
||||
});
|
||||
var timeout = setTimeout(function () {
|
||||
req.abort();
|
||||
}, 200);
|
||||
```
|
||||
#### or just use the timeout param
|
||||
```js
|
||||
es.search({
|
||||
q: '*',
|
||||
timeout: 200
|
||||
}).then(function (resp) {
|
||||
// Iterate all the hits
|
||||
})
|
||||
```
|
||||
29
package.json
29
package.json
@ -6,6 +6,10 @@
|
||||
"name": "elasticsearch-js",
|
||||
"homepage": "https://github.com/elasticsearch/elasticsearch-js",
|
||||
"version": "0.0.1",
|
||||
"browser": {
|
||||
"./src/lib/connectors/index.js": "./src/lib/connectors/browser_index.js",
|
||||
"./src/lib/loggers/index.js": "./src/lib/loggers/browser_index.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"tar": "~0.1.18",
|
||||
"mocha": "~1.14.0",
|
||||
@ -25,19 +29,34 @@
|
||||
"grunt-contrib-clean": "~0.5.0",
|
||||
"grunt-contrib-uglify": "~0.2.7",
|
||||
"grunt-contrib-concat": "~0.3.0",
|
||||
"grunt-open": "~0.2.2",
|
||||
"grunt-run": "~0.1.0",
|
||||
"xmlbuilder": "~0.4.3",
|
||||
"grunt-contrib-watch": "~0.5.3"
|
||||
"grunt-contrib-watch": "~0.5.3",
|
||||
"coveralls": "~2.3.0",
|
||||
"mocha-lcov-reporter": "0.0.1",
|
||||
"blanket": "~1.1.5",
|
||||
"sinon": "~1.7.3",
|
||||
"nock": "~0.23.0",
|
||||
"open": "0.0.4",
|
||||
"testling": "https://github.com/spenceralger/testling/tarball/master"
|
||||
},
|
||||
"license": "Apache License",
|
||||
"dependencies": {
|
||||
"when": "~2.6.0",
|
||||
"lodash": "~2.3.0",
|
||||
"agentkeepalive": "~0.1",
|
||||
"chalk": "~0.3.0"
|
||||
"chalk": "~0.3.0",
|
||||
"inherits": "~2.0.1"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "grunt generate test"
|
||||
"test": "node scripts/run_tests.js",
|
||||
"build": "grunt",
|
||||
"generate": "node scripts/generate/js_api && node scripts/generate/yaml_tests",
|
||||
"blanket": {
|
||||
"pattern": "src"
|
||||
}
|
||||
},
|
||||
"testling": {
|
||||
"harness": "mocha",
|
||||
"files": "test/unit/test_!(http_connection|stdio_logger).js"
|
||||
}
|
||||
}
|
||||
|
||||
@ -95,9 +95,13 @@ function transformFile(entry) {
|
||||
});
|
||||
});
|
||||
|
||||
spec.urls = _.map(_.sortBy(urls, 'sortOrder'), function (url) {
|
||||
return _.omit(url, 'sortOrder');
|
||||
});
|
||||
if (urls.length > 1) {
|
||||
spec.urls = _.map(_.sortBy(urls, 'sortOrder'), function (url) {
|
||||
return _.omit(url, 'sortOrder');
|
||||
});
|
||||
} else {
|
||||
spec.url = urls[0];
|
||||
}
|
||||
|
||||
spec.params = _.transform(spec.params, function (note, param, name) {
|
||||
// param.name = name;
|
||||
@ -106,6 +110,10 @@ function transformFile(entry) {
|
||||
]);
|
||||
}, {});
|
||||
|
||||
if (_.size(spec.params) === 0) {
|
||||
delete spec.params;
|
||||
}
|
||||
|
||||
// escape method names with "special" keywords
|
||||
var location = spec.name.split('.').join('.prototype.')
|
||||
.replace(/(^|\.)(delete|default)(\.|$)/g, '[\'$2\']');
|
||||
@ -114,6 +122,7 @@ function transformFile(entry) {
|
||||
spec: _.pick(spec, [
|
||||
'methods',
|
||||
'params',
|
||||
'url',
|
||||
'urls',
|
||||
'needBody',
|
||||
'bulkBody',
|
||||
@ -126,6 +135,61 @@ function transformFile(entry) {
|
||||
allParams: allParams
|
||||
};
|
||||
|
||||
function hasMethod(/* ...methods */) {
|
||||
for (var i = 0; i < arguments.length; i++) {
|
||||
if (~action.spec.methods.indexOf(arguments[i])) {
|
||||
continue;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
function methodsAre(/* ...methods */) {
|
||||
return hasMethod.apply(null, arguments) && arguments.length === action.spec.methods.length;
|
||||
}
|
||||
|
||||
var method;
|
||||
|
||||
if (action.spec.methods.length === 1) {
|
||||
method = action.spec.methods[0];
|
||||
} else {
|
||||
// we need to define what the default method(s) will be
|
||||
if (hasMethod('DELETE', 'POST')) {
|
||||
method = 'POST';
|
||||
}
|
||||
else if (methodsAre('DELETE')) {
|
||||
method = 'DELETE';
|
||||
}
|
||||
else if (methodsAre('POST', 'PUT')) {
|
||||
method = 'POST';
|
||||
}
|
||||
else if (methodsAre('GET', 'POST')) {
|
||||
method = 'POST';
|
||||
}
|
||||
else if (methodsAre('GET', 'HEAD')) {
|
||||
if (action.spec.castExists) {
|
||||
method = 'HEAD';
|
||||
} else {
|
||||
method = 'GET';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (method) {
|
||||
if (method !== 'GET') {
|
||||
action.spec.method = method;
|
||||
}
|
||||
delete action.spec.methods;
|
||||
} else {
|
||||
throw new Error('unable to pick a method for ' + JSON.stringify(action, null, ' '));
|
||||
}
|
||||
|
||||
if (action.name === 'create') {
|
||||
action.proxy = 'index';
|
||||
action.transformBody = 'params.op_type = \'create\';';
|
||||
}
|
||||
|
||||
if (actions.push(action) === specCount && doneParsing) {
|
||||
module.exports.emit('ready', action);
|
||||
}
|
||||
|
||||
@ -17,12 +17,21 @@ function download() {
|
||||
}
|
||||
}));
|
||||
|
||||
// seperate the proxy actions
|
||||
var groups = _.groupBy(actions, function (action) {
|
||||
return action.proxy ? 'proxies' : 'normal';
|
||||
});
|
||||
|
||||
clean(outputPath);
|
||||
|
||||
console.log('writing', actions.length, 'api actions to', outputPath);
|
||||
|
||||
fs.writeFileSync(outputPath, templates.apiFile({
|
||||
actions: actions,
|
||||
actions: groups.normal,
|
||||
proxies: groups.proxies,
|
||||
namespaces: _.unique(namespaces.sort(), true)
|
||||
}));
|
||||
|
||||
fs.writeFileSync(docOutputPath, templates.apiDocs({
|
||||
actions: actions
|
||||
}));
|
||||
@ -30,7 +39,7 @@ function download() {
|
||||
}
|
||||
|
||||
restSpecUpdated(function (err, updated) {
|
||||
if (process.env.FORCE_GEN || err || updated) {
|
||||
if (process.env.FORCE_GEN || process.env.npm_config_force || err || updated) {
|
||||
download();
|
||||
}
|
||||
});
|
||||
|
||||
@ -1,73 +0,0 @@
|
||||
var _ = require('<%= path2lib %>utils');
|
||||
var errors = require('<%= path2lib %>errors');<%
|
||||
|
||||
|
||||
if (_.keys(enumOptions).length) {
|
||||
%>
|
||||
<% _.each(enumOptions, function(options, name) {
|
||||
%>
|
||||
var <%= name %>Options = <%= stringify(options) %>;<%
|
||||
});
|
||||
}
|
||||
%>
|
||||
|
||||
/**
|
||||
* Perform an elasticsearch [<%= name %>](<%= docUrl %>) request
|
||||
*
|
||||
* @for Client
|
||||
* @method <%= name %>
|
||||
* @param {Object} params - An object with parameters used to carry out this action<% _.each(params, function(param, paramName) { %>
|
||||
* @param {<%= paramType(param.type) %>} <%= paramWithDefault('params.' + paramName, param.default) %><% if (param.description) { %> - <%= param.description %><% } %><%
|
||||
})
|
||||
%>
|
||||
*/
|
||||
function do<%= _.studlyCase(name) %>(params, cb) {
|
||||
if (typeof params === 'function') {
|
||||
cb = params;
|
||||
params = {};
|
||||
} else {
|
||||
params = params || {};
|
||||
cb = typeof cb === 'function' ? cb : _.noop;
|
||||
}
|
||||
|
||||
var request = {
|
||||
<%= writeRequestObjectBody(4, name, body, methods) %>
|
||||
};
|
||||
var parts = {};
|
||||
var query = {};
|
||||
var responseOpts = {};
|
||||
<%
|
||||
|
||||
if (methods.length > 1) { %>
|
||||
// figure out the method
|
||||
if (params.method = _.toUpperString(params.method)) {
|
||||
if (<%= _.map(methods, function (method) { return 'params.method === ' + stringify(method) }).join(' || ') %>) {
|
||||
request.method = params.method;
|
||||
} else {
|
||||
throw new TypeError('Invalid method: should be one of <%= methods.join(', ') %>');
|
||||
}
|
||||
} else {<%
|
||||
if (_.contains(methods, 'GET')) {
|
||||
var nonGet = _.find(methods, function (m) {return m !== 'GET'; });%>
|
||||
request.method = params.body ? <%= stringify(nonGet) %> : 'GET';<%
|
||||
} else {%>
|
||||
request.method = <%= stringify(methods[0]) %>;<%
|
||||
}%>
|
||||
}<%
|
||||
}
|
||||
%>
|
||||
|
||||
// find the paths's params
|
||||
<%= writeParams(2, urlParts, 'parts.') %>
|
||||
|
||||
// build the path
|
||||
<%= writeUrls(2, urls, urlParts, params) %>
|
||||
|
||||
// build the query string
|
||||
<%= writeParams(2, params, 'query.') %>
|
||||
request.path = request.path + _.makeQueryString(query);
|
||||
|
||||
<%= returnStatement(2, name) %>
|
||||
}
|
||||
|
||||
module.exports = do<%= _.studlyCase(name) %>;
|
||||
@ -12,16 +12,16 @@ _.each(actions, function (action) {
|
||||
var className = _.studlyCase(namespace) + 'NS';
|
||||
%>
|
||||
|
||||
api.<%= namespace %> = function <%= className %>(client) {
|
||||
if (this instanceof <%= className %>) {
|
||||
this.client = client;
|
||||
} else {
|
||||
return new <%= className %>(client);
|
||||
}
|
||||
api.<%= namespace %> = function <%= className %>(transport) {
|
||||
this.transport = transport;
|
||||
};<%
|
||||
}
|
||||
%>
|
||||
}%>
|
||||
|
||||
<%= partials.client_action(action) %><%
|
||||
|
||||
}); %>
|
||||
});
|
||||
|
||||
_.each(proxies, function (action) {%>
|
||||
<%= partials.client_action_proxy(action) %><%
|
||||
});
|
||||
%>
|
||||
|
||||
19
scripts/generate/js_api/templates/client_action_proxy.tmpl
Normal file
19
scripts/generate/js_api/templates/client_action_proxy.tmpl
Normal file
@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Perform a [<%= name %>](<%= docUrl %>) request
|
||||
*
|
||||
* @param {Object} params - An object with parameters used to carry out this action<%
|
||||
_.each(allParams, function(param, paramName) { %>
|
||||
* @param {<%= paramType(param.type) %>} <%= paramWithDefault('params.' + paramName, param.default) %><%
|
||||
if (param.description) {
|
||||
%> - <%= param.description %><%
|
||||
}
|
||||
%><% }) %>
|
||||
*/
|
||||
api<%= (location[0] === '[' ? '' : '.') + location %> = ca.proxy(<%= 'api' + (proxy[0] === '[' ? '' : '.') + proxy %><%
|
||||
if (typeof transformBody === 'string') { %>, {
|
||||
transform: function (params) {
|
||||
<%= indent(transformBody, 4) %>
|
||||
}
|
||||
}<%
|
||||
}
|
||||
%>);
|
||||
@ -42,6 +42,13 @@ var templateGlobals = {
|
||||
|
||||
_: _,
|
||||
|
||||
indent: function (block, spaces) {
|
||||
var indent = _.repeat(' ', spaces);
|
||||
return block.split('\n').map(function (line) {
|
||||
return indent + line;
|
||||
}).join('\n');
|
||||
},
|
||||
|
||||
paramType: function (type) {
|
||||
switch (type && type.toLowerCase ? type.toLowerCase() : 'any') {
|
||||
case 'time':
|
||||
|
||||
@ -34,7 +34,8 @@ var es = require('../../../src/elasticsearch'),
|
||||
endingMoment = moment().endOf('day').add('days', days),
|
||||
clientConfig = {
|
||||
log: {
|
||||
level: 'error'
|
||||
level: 'trace',
|
||||
type: 'stdio'
|
||||
}
|
||||
};
|
||||
|
||||
@ -95,7 +96,7 @@ fillIndecies(function () {
|
||||
actions.push(event);
|
||||
|
||||
if (actions.length === 3000 || i === count - 1) {
|
||||
client.config.log.info('writing', actions.length / 2, 'documents');
|
||||
console.info('writing', actions.length / 2, 'documents');
|
||||
client.bulk({
|
||||
body: actions
|
||||
}, done);
|
||||
@ -176,10 +177,11 @@ function fillIndecies(cb) {
|
||||
|
||||
async.parallel(indexPushActions, function (err, responses) {
|
||||
if (err) {
|
||||
client.config.log.error(err.message = 'Unable to create indicies: ' + err.message);
|
||||
console.error(err.message = 'Unable to create indicies: ' + err.message);
|
||||
console.error(err.stack);
|
||||
} else {
|
||||
_.each(_.groupBy(responses), function (list, did) {
|
||||
client.config.log.info(list.length, 'indicies', did);
|
||||
console.info(list.length, 'indicies', did);
|
||||
});
|
||||
cb();
|
||||
}
|
||||
|
||||
@ -32,7 +32,7 @@ function download() {
|
||||
|
||||
|
||||
restSpecUpdated(function (err, updated) {
|
||||
if (process.env.FORCE_GEN || err || updated) {
|
||||
if (process.env.FORCE_GEN || process.env.npm_config_force || err || updated) {
|
||||
download();
|
||||
}
|
||||
});
|
||||
|
||||
@ -1,5 +1,11 @@
|
||||
var https = require('https');
|
||||
var lastCommitUrl = 'https://api.github.com/repos/elasticsearch/elasticsearch-rest-api-spec/commits/HEAD';
|
||||
var request = {
|
||||
hostname: 'api.github.com',
|
||||
path: '/repos/elasticsearch/elasticsearch-rest-api-spec/commits/HEAD',
|
||||
headers: {
|
||||
'User-Agent': 'spenceralger'
|
||||
}
|
||||
};
|
||||
var fs = require('fs');
|
||||
|
||||
var lastRestSpecUpdateFile = __dirname + '/last_rest_spec_update.sha';
|
||||
@ -10,7 +16,7 @@ if (fs.existsSync(lastRestSpecUpdateFile)) {
|
||||
lastRestSpecUpdate = fs.readFileSync(lastRestSpecUpdateFile, 'utf8');
|
||||
}
|
||||
|
||||
var req = https.get(lastCommitUrl, function (incoming) {
|
||||
var req = https.get(request, function (incoming) {
|
||||
if (incoming.statusCode !== 200) {
|
||||
req.abort();
|
||||
console.error('request for last commit failed', incoming.statusCode, incoming.headers);
|
||||
|
||||
146
scripts/run_browser_integration_suite/index.js
Normal file
146
scripts/run_browser_integration_suite/index.js
Normal file
@ -0,0 +1,146 @@
|
||||
var server = require('./server');
|
||||
var child_process = require('child_process');
|
||||
var _ = require('lodash');
|
||||
var open = require('open');
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var async = require('async');
|
||||
|
||||
var yamlTestSourceFile = path.join(__dirname, '../../test/integration/yaml_suite/index.js');
|
||||
var yamlTestBundleFile = path.join(__dirname, '../../test/browser_integration/yaml_tests.js');
|
||||
var clientEntryFile = path.join(__dirname, '../../src/elasticsearch.js');
|
||||
|
||||
var browsers = _.transform({
|
||||
safari: {
|
||||
darwin: 'Safari'
|
||||
},
|
||||
chrome: {
|
||||
darwin: 'Google Chrome',
|
||||
win32: 'Google Chrome',
|
||||
executable: 'google-chrome'
|
||||
},
|
||||
chromium: {
|
||||
|
||||
executable: 'chromium-browser',
|
||||
},
|
||||
firefox: {
|
||||
darwin: 'Firefox',
|
||||
win32: 'Firefox',
|
||||
executable: 'firefox'
|
||||
},
|
||||
opera: {
|
||||
darwin: 'Opera',
|
||||
win32: 'Opera',
|
||||
executable: 'opera'
|
||||
}
|
||||
}, function (browsers, config, name) {
|
||||
if (config[process.platform]) {
|
||||
browsers[name] = config[process.platform];
|
||||
return;
|
||||
}
|
||||
|
||||
if (process.platform !== 'darwin' && process.platform !== 'win32' && config.executable) {
|
||||
browsers[name] = config.executable;
|
||||
return;
|
||||
}
|
||||
}, {});
|
||||
|
||||
var argv = require('optimist')
|
||||
.default('browser', 'chrome')
|
||||
.default('force_gen', false)
|
||||
.boolean('force_gen')
|
||||
.alias('f', 'force_gen')
|
||||
.default('host', 'localhost')
|
||||
.default('port', 9200)
|
||||
.argv;
|
||||
|
||||
var browserAppName;
|
||||
|
||||
async.series([
|
||||
function (done) {
|
||||
if (browsers.hasOwnProperty(argv.browser)) {
|
||||
browserAppName = browsers[argv.browser];
|
||||
done();
|
||||
} else {
|
||||
done('--browser must be set to one of ' + _.keys(browsers).join(', ') + ' on this platform');
|
||||
}
|
||||
},
|
||||
function (done) {
|
||||
fs.exists('dist', function (yes) {
|
||||
if (!argv.force_gen && yes) {
|
||||
done();
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('generating client with "grunt build"');
|
||||
child_process.spawn('grunt', ['build'], {
|
||||
stdio: 'inherit'
|
||||
}).on('close', function (status) {
|
||||
done(status && 'grunt closed with a status code of ' + status + '. aborting.');
|
||||
});
|
||||
});
|
||||
},
|
||||
function (done) {
|
||||
fs.exists(yamlTestBundleFile, function (yes) {
|
||||
if (!argv.force_gen && yes) {
|
||||
done();
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('generating browser\'s yaml_tests.js bundle');
|
||||
var b = require('browserify')();
|
||||
|
||||
b.add(yamlTestSourceFile);
|
||||
var bundle = b.bundle({
|
||||
external: [
|
||||
'optimist'
|
||||
],
|
||||
ignore: [
|
||||
'test/integration/yaml_suite/reporter',
|
||||
clientEntryFile
|
||||
]
|
||||
});
|
||||
var file = fs.createWriteStream(yamlTestBundleFile, {
|
||||
flags: 'w',
|
||||
encoding: 'utf8',
|
||||
mode: 0666
|
||||
});
|
||||
|
||||
bundle.pipe(file);
|
||||
|
||||
file.once('error', function (err) {
|
||||
done(err);
|
||||
});
|
||||
|
||||
bundle.once('error', function (err) {
|
||||
done(err);
|
||||
});
|
||||
|
||||
bundle.once('end', function () {
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
], function (err) {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
} else {
|
||||
server.listen(0, function () {
|
||||
var port = server.address().port;
|
||||
console.log('server listening on port', port);
|
||||
|
||||
open('http://localhost:' + port + '?es_hostname=' + encodeURIComponent(argv.host) +
|
||||
'&es_port=' + encodeURIComponent(argv.port) +
|
||||
'&browser=' + encodeURIComponent(argv.browser), browserAppName);
|
||||
});
|
||||
|
||||
server.on('tests done', function (success) {
|
||||
console.log('test completed', success ? 'successfully' : 'but failed');
|
||||
process.exit(success ? 0 : 1);
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
@ -2,22 +2,43 @@ var http = require('http');
|
||||
var url = require('url');
|
||||
var path = require('path');
|
||||
var fs = require('fs');
|
||||
var crypto = require('crypto');
|
||||
var _ = require('lodash');
|
||||
var util = require('util');
|
||||
var chalk = require('chalk');
|
||||
var moment = require('moment');
|
||||
var makeJUnitXml = require('../make_j_unit_xml');
|
||||
chalk.enabled = true;
|
||||
|
||||
var browserify = require('browserify');
|
||||
var port = process.argv[2] || 8888;
|
||||
|
||||
var middleware = [];
|
||||
|
||||
Error.stackTraceLimit = Infinity;
|
||||
|
||||
var chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||
var chars = 'abcdefghijklmnopqrstuvwxyz';
|
||||
|
||||
var server = http.createServer(function (req, resp) {
|
||||
var parsedUrl = url.parse(req.url, true);
|
||||
req.uri = parsedUrl.pathname;
|
||||
req.query = parsedUrl.query;
|
||||
req.filename = path.join(__dirname, '../../test/browser_integration/', req.uri);
|
||||
|
||||
var end = resp.end;
|
||||
resp.end = function () {
|
||||
console.log(chalk[this.statusCode < 300 ? 'green' : 'red'](this.statusCode), req.uri);
|
||||
end.apply(resp, arguments);
|
||||
};
|
||||
|
||||
var middleIndex = -1;
|
||||
|
||||
function next() {
|
||||
middleIndex++;
|
||||
if (middleIndex < middleware.length) {
|
||||
middleware[middleIndex](req, resp, next);
|
||||
} else {
|
||||
resp.writeHead(500);
|
||||
resp.end('500 Bad Gateway\n');
|
||||
}
|
||||
}
|
||||
next();
|
||||
});
|
||||
|
||||
function rand(length) {
|
||||
var str = '';
|
||||
while (str.length < length) {
|
||||
@ -26,31 +47,10 @@ function rand(length) {
|
||||
return str;
|
||||
}
|
||||
|
||||
function sendBundle(req, resp, files, opts, extend) {
|
||||
resp.setHeader('Content-Type', 'application/javascript');
|
||||
resp.writeHead(200);
|
||||
|
||||
var b = browserify(files);
|
||||
|
||||
if (typeof extend === 'function') {
|
||||
extend(b);
|
||||
}
|
||||
|
||||
var out = b.bundle(opts);
|
||||
|
||||
out.on('data', function (chunk) {
|
||||
resp.write(chunk);
|
||||
});
|
||||
|
||||
out.on('end', function () {
|
||||
resp.end();
|
||||
});
|
||||
}
|
||||
|
||||
function collectTestResults(req, resp) {
|
||||
var body = '';
|
||||
var browser = req.query.browser;
|
||||
var logFilename = path.join(__dirname, '../test-output-' + browser + '.xml');
|
||||
var logFilename = path.join(__dirname, '../../test-output-' + browser + '.xml');
|
||||
|
||||
req.on('data', function (chunk) {
|
||||
body += chunk;
|
||||
@ -79,40 +79,15 @@ function collectTestResults(req, resp) {
|
||||
if (err) {
|
||||
console.log('unable to save test-output to', err.message);
|
||||
console.trace();
|
||||
process.exit(1);
|
||||
server.emit('tests done', false);
|
||||
} else {
|
||||
console.log('test output written to', logFilename);
|
||||
process.exit(testDetails.stats.failures ? 1 : 0);
|
||||
server.emit('tests done', !testDetails.stats.failures);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
var server = http.createServer(function (req, resp) {
|
||||
var parsedUrl = url.parse(req.url, true);
|
||||
req.uri = parsedUrl.pathname;
|
||||
req.query = parsedUrl.query;
|
||||
req.filename = path.join(__dirname, req.uri);
|
||||
|
||||
var end = resp.end;
|
||||
resp.end = function () {
|
||||
console.log(chalk[this.statusCode < 300 ? 'green' : 'red'](this.statusCode), req.uri);
|
||||
end.apply(resp, arguments);
|
||||
};
|
||||
|
||||
var middleIndex = -1;
|
||||
|
||||
function next() {
|
||||
middleIndex++;
|
||||
if (middleIndex < middleware.length) {
|
||||
middleware[middleIndex](req, resp, next);
|
||||
} else {
|
||||
resp.writeHead(500);
|
||||
resp.end('500 Bad Gateway\n');
|
||||
}
|
||||
}
|
||||
next();
|
||||
});
|
||||
|
||||
middleware.push(function (req, resp, next) {
|
||||
// resolve filenames
|
||||
@ -145,14 +120,14 @@ middleware.push(function (req, resp, next) {
|
||||
resp.end();
|
||||
} else {
|
||||
if (stats.isDirectory()) {
|
||||
req.filename = path.join(req.filename, './index.html');
|
||||
req.filename = path.join(req.filename, '../../test/browser_integration/index.html');
|
||||
}
|
||||
next();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
middleware.push(function (req, resp, next) {
|
||||
middleware.push(function (req, resp) {
|
||||
// static files
|
||||
var reader = fs.createReadStream(req.filename);
|
||||
var data = '';
|
||||
@ -201,7 +176,7 @@ middleware.push(function (req, resp, next) {
|
||||
es_hostname: 'localhost',
|
||||
es_port: 9200,
|
||||
browser: 'unknown',
|
||||
ts: rand(5)
|
||||
ts: 'no'//rand(5)
|
||||
})));
|
||||
} else {
|
||||
resp.end(data);
|
||||
@ -211,6 +186,5 @@ middleware.push(function (req, resp, next) {
|
||||
}
|
||||
});
|
||||
|
||||
server.listen(parseInt(port, 10), function () {
|
||||
console.log('server listening on port', server.address().port);
|
||||
});
|
||||
|
||||
module.exports = server;
|
||||
81
scripts/run_tests.js
Normal file
81
scripts/run_tests.js
Normal file
@ -0,0 +1,81 @@
|
||||
var async = require('async');
|
||||
var cp = require('child_process');
|
||||
var chalk = require('chalk');
|
||||
var argv = require('optimist')
|
||||
.default({
|
||||
'check-upstream': false,
|
||||
'in-node': true,
|
||||
'in-browser': true,
|
||||
'not-in-node': false,
|
||||
'not-in-browser': false,
|
||||
'unit': true,
|
||||
'integration': true
|
||||
})
|
||||
.alias({
|
||||
u: 'unit',
|
||||
i: 'integration',
|
||||
b: 'in-browser',
|
||||
n: 'in-node',
|
||||
})
|
||||
.parse(JSON.parse(process.env.npm_config_argv).original);
|
||||
|
||||
if (argv['not-in-browser']) {
|
||||
argv.b = argv['in-browser'] = false;
|
||||
}
|
||||
if (argv['not-in-node']) {
|
||||
argv.n = argv['in-node'] = false;
|
||||
}
|
||||
|
||||
var commands = [];
|
||||
|
||||
if (argv['check-upstream']) {
|
||||
commands.push(['node', 'scripts/generate/yaml_tests/index.js']);
|
||||
}
|
||||
|
||||
if (argv.unit) {
|
||||
if (argv['in-node']) {
|
||||
commands.push(['mocha', 'test/unit/test_*.js', '--require=should']);
|
||||
}
|
||||
if (argv['in-browser']) {
|
||||
commands.push(['testling', '.']);
|
||||
}
|
||||
}
|
||||
|
||||
if (argv.integration) {
|
||||
if (argv['in-node']) {
|
||||
commands.push(['mocha', 'test/integration/yaml_suite/index.js', '-b', '--require=should']);
|
||||
}
|
||||
if (argv['in-browser']) {
|
||||
commands.push(['node', 'scripts/run_browser_integration_suite/index.js']);
|
||||
}
|
||||
}
|
||||
|
||||
if (commands.length) {
|
||||
async.forEachSeries(commands, function (args, done) {
|
||||
var command = args.shift();
|
||||
console.log(chalk.gray('\n\n' + '# ' + command + ' ' + args.join(' ')));
|
||||
var proc = cp.spawn(command, args, {
|
||||
stdio: 'inherit'
|
||||
});
|
||||
|
||||
proc.on('error', function (err) {
|
||||
proc.removeAllListeners();
|
||||
done(err);
|
||||
});
|
||||
|
||||
proc.on('exit', function (status) {
|
||||
proc.removeAllListeners();
|
||||
done(status ? new Error(command + ' exited with status ' + status) : void 0);
|
||||
});
|
||||
}, function (err) {
|
||||
if (err) {
|
||||
console.error(err.message);
|
||||
process.exit(1);
|
||||
} else {
|
||||
process.exit(0);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.log('Arguments resulted in no tests to run.');
|
||||
console.log('Try combining test types with environments');
|
||||
}
|
||||
@ -1,28 +0,0 @@
|
||||
var http = require('http');
|
||||
|
||||
var server = http.createServer(function (req, resp) {
|
||||
var closed, count = 0;
|
||||
|
||||
resp.on('close', function () {
|
||||
closed = true;
|
||||
console.log('response was closed');
|
||||
});
|
||||
|
||||
process.removeAllListeners();
|
||||
|
||||
var interval = setInterval(function () {
|
||||
if (count > 99 || resp.closed || closed) {
|
||||
clearInterval(interval);
|
||||
console.log('done writing', resp.socket.bytesWritten, 'bytes');
|
||||
resp.end();
|
||||
} else {
|
||||
process.stdout.write('->');
|
||||
resp.write('line of data, more to come... slowly!');
|
||||
count++;
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
|
||||
server.listen(7500, function () {
|
||||
console.log('server listening at', server.address());
|
||||
});
|
||||
@ -5,40 +5,27 @@
|
||||
* 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;
|
||||
|
||||
/* global angular */
|
||||
angular.module('elasticsearch.client', [])
|
||||
.factory('esFactory', ['$http', '$q', function ($http, $q) {
|
||||
|
||||
AngularConnector.prototype.$http = $http;
|
||||
AngularConnector.prototype.$http = $http;
|
||||
|
||||
// store the original request function
|
||||
Transport.prototype._request = Transport.prototype.request;
|
||||
var factory = function (config) {
|
||||
config = config || {};
|
||||
config.connectionClass = AngularConnector;
|
||||
config.createDefer = function () {
|
||||
return $q.defer();
|
||||
};
|
||||
return new Client(config);
|
||||
};
|
||||
|
||||
// overwrite the request function to return a promise
|
||||
// and support the callback
|
||||
Transport.prototype.request = function (params, cb) {
|
||||
var deferred = $q.defer();
|
||||
this._request(params, function (err, body, status) {
|
||||
if (typeof cb === 'function') {
|
||||
cb(err, body, status);
|
||||
}
|
||||
factory.errors = require('./lib/errors');
|
||||
factory.ConnectionPool = require('./lib/connection_pool');
|
||||
factory.Transport = require('./lib/transport');
|
||||
|
||||
if (err) {
|
||||
deferred.reject(err);
|
||||
} else {
|
||||
deferred.resolve({ body: body, status: status });
|
||||
}
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
return function (config) {
|
||||
config = config || {};
|
||||
config.connectionClass = AngularConnector;
|
||||
return new Client(config);
|
||||
};
|
||||
}]);
|
||||
}]);
|
||||
|
||||
4
src/elasticsearch.jquery.js
Normal file
4
src/elasticsearch.jquery.js
Normal file
@ -0,0 +1,4 @@
|
||||
process.jquery_build = true;
|
||||
|
||||
/* global jQuery */
|
||||
jQuery.es = require('./elasticsearch');
|
||||
@ -1,5 +1,8 @@
|
||||
var es = {
|
||||
Client: require('./lib/client')
|
||||
Client: require('./lib/client'),
|
||||
errors: require('./lib/errors'),
|
||||
ConnectionPool: require('./lib/connection_pool'),
|
||||
Transport: require('./lib/transport')
|
||||
};
|
||||
|
||||
module.exports = es;
|
||||
module.exports = es;
|
||||
|
||||
960
src/lib/api.js
960
src/lib/api.js
File diff suppressed because it is too large
Load Diff
@ -22,33 +22,20 @@
|
||||
*
|
||||
* @class Client
|
||||
* @constructor
|
||||
* @param {Object} [config={}] - Configuration for the transport
|
||||
* @param {Object} [config.transport] - Transport settings passed to {{#crossLink "Transport"}}Transport Constructor{{/crossLink}}
|
||||
* @param {String|Array<String>} [config.log] - Log output settings {{#crossLink "Log"}}Log Constructor{{/crossLink}}
|
||||
* @param {Object} [config.trace=false] - Create a log output to stdio that only tracks trace logs
|
||||
*/
|
||||
|
||||
module.exports = Client;
|
||||
|
||||
var _ = require('./utils');
|
||||
var ClientConfig = require('./client_config');
|
||||
var api = require('./api.js');
|
||||
var ca = require('./client_action');
|
||||
var Transport = require('./transport');
|
||||
|
||||
function Client(config) {
|
||||
this.client = this;
|
||||
this.transport = new Transport(config);
|
||||
|
||||
// setup the config.. this config will be passed EVERYWHERE so for good measure it is locked down
|
||||
Object.defineProperty(this, 'config', {
|
||||
configurable: false,
|
||||
enumerable: false,
|
||||
writable: false,
|
||||
value: !config || _.isPlainObject(config) ? new ClientConfig(config) : config,
|
||||
});
|
||||
this.config.client = this;
|
||||
|
||||
// instansiate the api's namespaces
|
||||
// instantiate the api's namespaces
|
||||
for (var i = 0; i < this._namespaces.length; i++) {
|
||||
this[this._namespaces[i]] = new this[this._namespaces[i]](this);
|
||||
this[this._namespaces[i]] = new this[this._namespaces[i]](this.transport);
|
||||
}
|
||||
}
|
||||
|
||||
@ -60,19 +47,14 @@ Client.prototype = api;
|
||||
* @param {Object} params - Currently just a placeholder, no params used at this time
|
||||
* @param {Function} cb - callback
|
||||
*/
|
||||
Client.prototype.ping = function (params, cb) {
|
||||
if (typeof params === 'function') {
|
||||
cb = params;
|
||||
params = {};
|
||||
}
|
||||
|
||||
this.config.transport.request({
|
||||
method: 'HEAD',
|
||||
path: '/',
|
||||
timeout: 100,
|
||||
}, cb);
|
||||
};
|
||||
Client.prototype.ping = ca({
|
||||
method: 'HEAD',
|
||||
url: {
|
||||
fmt: '/'
|
||||
},
|
||||
timeout: 100
|
||||
});
|
||||
|
||||
Client.prototype.close = function () {
|
||||
this.config.close();
|
||||
this.transport.close();
|
||||
};
|
||||
|
||||
@ -2,21 +2,50 @@
|
||||
* Constructs a function that can be called to make a request to ES
|
||||
* @type {[type]}
|
||||
*/
|
||||
module.exports = function ClientAction(spec, client) {
|
||||
return function (params, cb) {
|
||||
return exec((client || this.client).config.transport, spec, params, cb);
|
||||
};
|
||||
};
|
||||
module.exports = ClientAction;
|
||||
|
||||
var _ = require('./utils');
|
||||
var when = require('when');
|
||||
|
||||
function ClientAction(spec) {
|
||||
if (!_.isPlainObject(spec.params)) {
|
||||
spec.params = {};
|
||||
}
|
||||
|
||||
if (!spec.method) {
|
||||
spec.method = 'GET';
|
||||
}
|
||||
|
||||
return function (params, cb) {
|
||||
if (typeof params === 'function') {
|
||||
cb = params;
|
||||
params = {};
|
||||
} else {
|
||||
params = params || {};
|
||||
cb = typeof cb === 'function' ? cb : null;
|
||||
}
|
||||
|
||||
try {
|
||||
return exec(this.transport, spec, params, cb);
|
||||
} catch (e) {
|
||||
if (typeof cb === 'function') {
|
||||
_.nextTick(cb, e);
|
||||
} else {
|
||||
return when.reject(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
var castType = {
|
||||
enum: function (param, val, name) {
|
||||
if (_.contains(param.options, val)) {
|
||||
return val;
|
||||
} else {
|
||||
throw new TypeError('Invalid ' + name + ': expected one of ' + param.options.join(','));
|
||||
/* jshint eqeqeq: false */
|
||||
for (var i = 0; i < param.options.length; i++) {
|
||||
if (param.options[i] == val) {
|
||||
return param.options[i];
|
||||
}
|
||||
}
|
||||
throw new TypeError('Invalid ' + name + ': expected one of ' + param.options.join(','));
|
||||
},
|
||||
duration: function (param, val, name) {
|
||||
if (_.isNumeric(val) || _.isInterval(val)) {
|
||||
@ -30,17 +59,16 @@ var castType = {
|
||||
},
|
||||
list: function (param, val, name) {
|
||||
switch (typeof val) {
|
||||
case 'number':
|
||||
case 'string':
|
||||
return val;
|
||||
case 'object':
|
||||
if (_.isArray(val)) {
|
||||
return val.join(',');
|
||||
} else {
|
||||
throw new TypeError('Invalid ' + name + ': expected be a comma seperated list, array, or boolean.');
|
||||
}
|
||||
break;
|
||||
/* falls through */
|
||||
default:
|
||||
return !!val;
|
||||
throw new TypeError('Invalid ' + name + ': expected be a comma seperated list, array, number or string.');
|
||||
}
|
||||
},
|
||||
boolean: function (param, val) {
|
||||
@ -55,18 +83,25 @@ var castType = {
|
||||
}
|
||||
},
|
||||
string: function (param, val, name) {
|
||||
if (typeof val !== 'object' && val) {
|
||||
switch (typeof val) {
|
||||
case 'number':
|
||||
case 'string':
|
||||
return '' + val;
|
||||
} else {
|
||||
default:
|
||||
throw new TypeError('Invalid ' + name + ': expected a string.');
|
||||
}
|
||||
},
|
||||
time: function (param, val, name) {
|
||||
if (typeof val === 'string' || _.isNumeric(val)) {
|
||||
if (typeof val === 'string') {
|
||||
return val;
|
||||
} else if (val instanceof Date) {
|
||||
return val.getTime();
|
||||
} else {
|
||||
}
|
||||
else if (_.isNumeric(val)) {
|
||||
return '' + val;
|
||||
}
|
||||
else if (val instanceof Date) {
|
||||
return '' + val.getTime();
|
||||
}
|
||||
else {
|
||||
throw new TypeError('Invalid ' + name + ': expected some sort of time.');
|
||||
}
|
||||
}
|
||||
@ -88,8 +123,12 @@ function resolveUrl(url, params) {
|
||||
// missing a required param
|
||||
return false;
|
||||
} else {
|
||||
// copy param vals into vars
|
||||
vars[key] = params[key];
|
||||
// cast of copy required param
|
||||
if (castType[url.req[key].type]) {
|
||||
vars[key] = castType[url.req[key].type](url.req[key], params[key], key);
|
||||
} else {
|
||||
vars[key] = params[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -127,60 +166,27 @@ function resolveUrl(url, params) {
|
||||
}, {}));
|
||||
}
|
||||
|
||||
function exec(transport, spec, params, cb) {
|
||||
if (typeof params === 'function') {
|
||||
cb = params;
|
||||
params = {};
|
||||
} else {
|
||||
params = params || {};
|
||||
cb = typeof cb === 'function' ? cb : _.noop;
|
||||
}
|
||||
// export so that we can test this
|
||||
ClientAction.resolveUrl = resolveUrl;
|
||||
|
||||
var request = {};
|
||||
function exec(transport, spec, params, cb) {
|
||||
var request = {
|
||||
method: spec.method,
|
||||
timeout: spec.timeout || 10000
|
||||
};
|
||||
var query = {};
|
||||
var i;
|
||||
|
||||
// verify that we have the body if needed
|
||||
if (spec.needsBody && !params.body) {
|
||||
return _.nextTick(cb, new TypeError('A request body is required.'));
|
||||
throw new TypeError('A request body is required.');
|
||||
}
|
||||
|
||||
if (params.timeout === void 0) {
|
||||
request.timeout = 10000;
|
||||
} else {
|
||||
request.timeout = params.timeout;
|
||||
}
|
||||
|
||||
// copy over some properties from the spec
|
||||
params.body && (request.body = params.body);
|
||||
params.ignore && (request.ignore = _.isArray(params.ignore) ? params.ignore : [params.ignore]);
|
||||
// control params
|
||||
spec.bulkBody && (request.bulkBody = true);
|
||||
spec.castExists && (request.castExists = true);
|
||||
|
||||
if (spec.methods.length === 1) {
|
||||
request.method = spec.methods[0];
|
||||
} else {
|
||||
// if set, uppercase the user's choice, other wise returns ""
|
||||
request.method = _.toUpperString(params.method);
|
||||
|
||||
if (request.method) {
|
||||
// use the one specified as long as it's a valid option
|
||||
if (!_.contains(spec.methods, request.method)) {
|
||||
return _.nextTick(cb, new TypeError('Invalid method: should be one of ' + spec.methods.join(', ')));
|
||||
}
|
||||
} else {
|
||||
// pick a method
|
||||
if (request.body) {
|
||||
// first method that isn't "GET"
|
||||
request.method = spec.methodWithBody || (
|
||||
spec.methodWithBody = _.find(spec.methods, function (m) { return m !== 'GET'; })
|
||||
);
|
||||
} else {
|
||||
// just use the first option
|
||||
request.method = spec.methods[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// pick the url
|
||||
if (spec.url) {
|
||||
// only one url option
|
||||
request.path = resolveUrl(spec.url, params);
|
||||
@ -194,12 +200,9 @@ function exec(transport, spec, params, cb) {
|
||||
|
||||
if (!request.path) {
|
||||
// there must have been some mimimun requirements that were not met
|
||||
return _.nextTick(
|
||||
cb,
|
||||
new TypeError(
|
||||
'Unable to build a path with those params. Supply at least ' +
|
||||
_.keys(spec.urls[spec.urls.length - 1].req).join(', ')
|
||||
)
|
||||
throw new TypeError(
|
||||
'Unable to build a path with those params. Supply at least ' +
|
||||
_.keys(spec.urls[spec.urls.length - 1].req).join(', ')
|
||||
);
|
||||
}
|
||||
|
||||
@ -207,24 +210,56 @@ function exec(transport, spec, params, cb) {
|
||||
if (!spec.paramKeys) {
|
||||
// build a key list on demand
|
||||
spec.paramKeys = _.keys(spec.params);
|
||||
}
|
||||
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[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);
|
||||
spec.requireParamKeys = _.transform(spec.params, function (req, param, key) {
|
||||
if (param.required) {
|
||||
req.push(key);
|
||||
}
|
||||
} catch (e) {
|
||||
return _.nextTick(cb, e);
|
||||
}, []);
|
||||
}
|
||||
|
||||
var key, paramSpec;
|
||||
|
||||
for (key in params) {
|
||||
if (params.hasOwnProperty(key) && params[key] != null) {
|
||||
switch (key) {
|
||||
case 'body':
|
||||
request.body = params.body;
|
||||
break;
|
||||
case 'ignore':
|
||||
request.ignore = _.isArray(params.ignore) ? params.ignore : [params.ignore];
|
||||
break;
|
||||
case 'timeout':
|
||||
request.timeout = params.timeout;
|
||||
break;
|
||||
case 'method':
|
||||
request.method = _.toUpperString(params.method);
|
||||
break;
|
||||
default:
|
||||
paramSpec = spec.params[key];
|
||||
if (paramSpec) {
|
||||
// param keys don't always match the param name, in those cases it's stored in the param def as "name"
|
||||
paramSpec.name = paramSpec.name || key;
|
||||
if (params[key] != null) {
|
||||
if (castType[paramSpec.type]) {
|
||||
query[paramSpec.name] = castType[paramSpec.type](paramSpec, params[key], key);
|
||||
} else {
|
||||
query[paramSpec.name] = params[key];
|
||||
}
|
||||
|
||||
if (paramSpec['default'] && query[paramSpec.name] === paramSpec['default']) {
|
||||
delete query[paramSpec.name];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
query[key] = params[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < spec.requireParamKeys.length; i ++) {
|
||||
if (!query.hasOwnProperty(spec.requireParamKeys[i])) {
|
||||
throw new TypeError('Missing required parameter ' + spec.requireParamKeys[i]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -232,3 +267,23 @@ function exec(transport, spec, params, cb) {
|
||||
|
||||
return transport.request(request, cb);
|
||||
}
|
||||
|
||||
|
||||
|
||||
ClientAction.proxy = function (fn, spec) {
|
||||
return function (params, cb) {
|
||||
if (typeof params === 'function') {
|
||||
cb = params;
|
||||
params = {};
|
||||
} else {
|
||||
params = params || {};
|
||||
cb = typeof cb === 'function' ? cb : null;
|
||||
}
|
||||
|
||||
if (spec.transform) {
|
||||
spec.transform(params);
|
||||
}
|
||||
|
||||
return fn.call(this, params, cb);
|
||||
};
|
||||
};
|
||||
|
||||
@ -1,146 +0,0 @@
|
||||
/**
|
||||
* Manages the configuration of the client.
|
||||
*
|
||||
* @class ClientConfig
|
||||
* @type {Function}
|
||||
*/
|
||||
module.exports = ClientConfig;
|
||||
|
||||
var _ = require('./utils');
|
||||
var Host = require('./host');
|
||||
var selectors = require('./selectors');
|
||||
|
||||
var connectors = {};
|
||||
if (process.browser) {
|
||||
connectors.Xhr = require('./connectors/xhr');
|
||||
connectors.Angular = require('./connectors/angular');
|
||||
connectors.jQuery = require('./connectors/jquery');
|
||||
} else {
|
||||
connectors.Http = require('./connectors/http');
|
||||
}
|
||||
|
||||
// remove connectors that have been excluded in the build
|
||||
_.each(connectors, function (conn, name) {
|
||||
if (typeof conn !== 'function') {
|
||||
delete connectors[name];
|
||||
}
|
||||
});
|
||||
|
||||
var serializers = {
|
||||
Json: require('./serializers/json')
|
||||
};
|
||||
|
||||
var extractHostPartsRE = /\[([^:]+):(\d+)\]/;
|
||||
|
||||
var defaultClasses = {
|
||||
log: require('./log'),
|
||||
serializer: serializers.Json,
|
||||
connectionPool: require('./connection_pool'),
|
||||
transport: require('./transport'),
|
||||
};
|
||||
|
||||
var defaultConfig = {
|
||||
loggers: [
|
||||
{
|
||||
level: 'warning'
|
||||
}
|
||||
],
|
||||
hosts: [
|
||||
{
|
||||
host: 'localhost',
|
||||
port: 9200,
|
||||
protocol: 'http'
|
||||
}
|
||||
],
|
||||
connectionClass: process.browser ? connectors.Xhr : connectors.Http,
|
||||
selector: selectors.roundRobin,
|
||||
sniffOnStart: false,
|
||||
sniffAfterRequests: null,
|
||||
sniffOnConnectionFail: false,
|
||||
maxRetries: 3,
|
||||
timeout: 10000,
|
||||
deadTimeout: 60000,
|
||||
maxSockets: 10,
|
||||
// transforms the response from /_cluster/nodes
|
||||
nodesToHostCallback: function (nodes) {
|
||||
var hosts = [];
|
||||
_.each(nodes, function (node, id) {
|
||||
var hostnameMatches = extractHostPartsRE.exec(node.http_address);
|
||||
hosts.push({
|
||||
host: hostnameMatches[1],
|
||||
port: hostnameMatches[2],
|
||||
_meta: {
|
||||
id: id,
|
||||
name: node.name,
|
||||
hostname: node.hostname,
|
||||
version: node.version
|
||||
}
|
||||
});
|
||||
});
|
||||
return hosts;
|
||||
}
|
||||
};
|
||||
|
||||
// remove connector classes that were not included in the build
|
||||
connectors = _.transform(connectors, function (note, connector, name) {
|
||||
if (connector) {
|
||||
note[name] = connector;
|
||||
}
|
||||
}, {});
|
||||
|
||||
function ClientConfig(config) {
|
||||
_.extend(this, defaultConfig, config);
|
||||
|
||||
if (this.log) {
|
||||
// treat log as an alias for loggers in the config.
|
||||
this.loggers = this.log;
|
||||
delete this.log;
|
||||
}
|
||||
|
||||
// validate connectionClass
|
||||
if (typeof this.connectionClass === 'string') {
|
||||
this.connectionClass = connectors[_.studlyCase(this.connectionClass)];
|
||||
}
|
||||
if (typeof this.connectionClass !== 'function') {
|
||||
throw new TypeError('Invalid connectionClass "' + this.connectionClass + '". ' +
|
||||
'Expected a constructor or one of ' + _.keys(connectors).join(', '));
|
||||
}
|
||||
|
||||
// validate selector
|
||||
if (typeof this.selector === 'string') {
|
||||
this.selector = selectors[_.camelCase(this.selector)];
|
||||
}
|
||||
if (typeof this.selector !== 'function') {
|
||||
throw new TypeError('Invalid Selector "' + this.selector + '". ' +
|
||||
'Expected a function or one of ' + _.keys(selectors).join(', '));
|
||||
}
|
||||
|
||||
_.each(defaultClasses, function (DefaultClass, prop) {
|
||||
this[prop] = typeof this[prop] === 'function' ? new this[prop](this) : new DefaultClass(this);
|
||||
}, this);
|
||||
|
||||
// populate the connection pool
|
||||
this.connectionPool.setNodes(this.prepareHosts(this.hosts));
|
||||
|
||||
// nodes are completely managed by the connection pool, remove traces of the config
|
||||
// value to prevent confusion
|
||||
delete this.hosts;
|
||||
}
|
||||
|
||||
ClientConfig.prototype.prepareHosts = function (hosts) {
|
||||
if (!_.isArray(hosts)) {
|
||||
hosts = [hosts];
|
||||
}
|
||||
|
||||
return _.map(hosts, function (host) {
|
||||
return new Host(host);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Shutdown the connectionPool, log outputs, and clear timers
|
||||
*/
|
||||
ClientConfig.prototype.close = function () {
|
||||
this.log.close();
|
||||
this.connectionPool.close();
|
||||
};
|
||||
@ -9,13 +9,19 @@ var EventEmitter = require('events').EventEmitter;
|
||||
* @constructor
|
||||
*/
|
||||
function ConnectionAbstract(host, config) {
|
||||
config = _.defaults(config || {}, {
|
||||
deadTimeout: 30000
|
||||
});
|
||||
EventEmitter.call(this);
|
||||
this.config = config;
|
||||
this.host = host;
|
||||
this.deadTimeout = config.deadTimeout;
|
||||
this.requestCount = 0;
|
||||
|
||||
if (!this.host) {
|
||||
throw new Error('Missing host config');
|
||||
if (!host) {
|
||||
throw new TypeError('Missing host');
|
||||
} else if (host.makeUrl) {
|
||||
this.host = host;
|
||||
} else {
|
||||
throw new TypeError('Invalid host');
|
||||
}
|
||||
|
||||
_.makeBoundMethods(this);
|
||||
@ -36,12 +42,11 @@ ConnectionAbstract.prototype.request = function () {
|
||||
throw new Error('Connection#request must be overwritten by the Connector');
|
||||
};
|
||||
|
||||
ConnectionAbstract.prototype.ping = function (params, cb) {
|
||||
if (typeof params === 'function') {
|
||||
cb = params;
|
||||
} else if (typeof cb !== 'function') {
|
||||
ConnectionAbstract.prototype.ping = function (cb) {
|
||||
if (typeof cb !== 'function') {
|
||||
throw new TypeError('Callback must be a function');
|
||||
}
|
||||
|
||||
return this.request({
|
||||
path: '/',
|
||||
method: 'HEAD',
|
||||
@ -54,16 +59,20 @@ ConnectionAbstract.prototype.setStatus = function (status) {
|
||||
|
||||
this.status = status;
|
||||
|
||||
if (status === 'dead' || status === 'closed') {
|
||||
if (this.__deadTimeout) {
|
||||
clearTimeout(this.__deadTimeout);
|
||||
}
|
||||
if (status === 'dead') {
|
||||
this.__deadTimeout = setTimeout(this.bound.resuscitate, this.config.deadTimeout);
|
||||
}
|
||||
if (this._deadTimeoutId) {
|
||||
clearTimeout(this._deadTimeoutId);
|
||||
this._deadTimeoutId = null;
|
||||
}
|
||||
|
||||
this.emit('status changed', status, origStatus, this);
|
||||
if (status === 'dead') {
|
||||
this._deadTimeoutId = setTimeout(this.bound.resuscitate, this.deadTimeout);
|
||||
}
|
||||
|
||||
this.emit('status set', status, origStatus, this);
|
||||
|
||||
if (status === 'closed') {
|
||||
this.removeAllListeners();
|
||||
}
|
||||
};
|
||||
|
||||
ConnectionAbstract.prototype.resuscitate = _.scheduled(function () {
|
||||
@ -74,7 +83,7 @@ ConnectionAbstract.prototype.resuscitate = _.scheduled(function () {
|
||||
if (!err) {
|
||||
self.setStatus('alive');
|
||||
} else {
|
||||
self.emit('dead');
|
||||
self.setStatus('dead');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -3,33 +3,66 @@
|
||||
* before providing them to the application
|
||||
*
|
||||
* @class ConnectionPool
|
||||
* @param {Client} client - The client this pool belongs to
|
||||
* @constructor
|
||||
* @param {Object} config - The config object passed to the transport.
|
||||
*/
|
||||
|
||||
module.exports = ConnectionPool;
|
||||
|
||||
var _ = require('./utils');
|
||||
var Host = require('./host');
|
||||
var Log = require('./log');
|
||||
|
||||
function ConnectionPool(config) {
|
||||
_.makeBoundMethods(this);
|
||||
this.config = config;
|
||||
|
||||
this.log = config.log;
|
||||
if (!this.log) {
|
||||
this.log = new Log();
|
||||
}
|
||||
|
||||
// get the selector config var
|
||||
this.selector = _.funcEnum(config, 'selector', ConnectionPool.selectors, ConnectionPool.defaultSelectors);
|
||||
// get the connection class
|
||||
this.Connection = _.funcEnum(config, 'connectionClass', ConnectionPool.connectionClasses,
|
||||
ConnectionPool.defaultConnectionClass);
|
||||
|
||||
// a map of connections to their "id" property, used when sniffing
|
||||
this.index = {};
|
||||
|
||||
this.connections = {
|
||||
alive: [],
|
||||
dead: []
|
||||
};
|
||||
}
|
||||
|
||||
// selector options
|
||||
ConnectionPool.selectors = require('./selectors');
|
||||
ConnectionPool.defaultSelectors = 'round_robin';
|
||||
|
||||
// get the connection options
|
||||
ConnectionPool.connectionClasses = require('./connectors');
|
||||
ConnectionPool.defaultConnectionClass = ConnectionPool.connectionClasses._default;
|
||||
delete ConnectionPool.connectionClasses._default;
|
||||
|
||||
/**
|
||||
* Selects a connection from the list using the this.selector
|
||||
* Features:
|
||||
* - detects if the selector is async or not
|
||||
* - sync selectors should still return asynchronously
|
||||
* - catches errors in sync selectors
|
||||
* - automatically selects the first dead connection when there no living connections
|
||||
*
|
||||
* @param {Function} cb [description]
|
||||
* @return {[type]} [description]
|
||||
*/
|
||||
ConnectionPool.prototype.select = function (cb) {
|
||||
if (this.connections.alive.length) {
|
||||
if (this.config.selector.length > 1) {
|
||||
this.config.selector(this.connections.alive, cb);
|
||||
if (this.selector.length > 1) {
|
||||
this.selector(this.connections.alive, cb);
|
||||
} else {
|
||||
try {
|
||||
_.nextTick(cb, null, this.config.selector(this.connections.alive));
|
||||
_.nextTick(cb, null, this.selector(this.connections.alive));
|
||||
} catch (e) {
|
||||
this.config.log.error(e);
|
||||
cb(e);
|
||||
}
|
||||
}
|
||||
@ -38,7 +71,7 @@ ConnectionPool.prototype.select = function (cb) {
|
||||
}
|
||||
};
|
||||
|
||||
ConnectionPool.prototype.onStatusChanged = _.handler(function (status, oldStatus, connection) {
|
||||
ConnectionPool.prototype.onStatusSet = _.handler(function (status, oldStatus, connection) {
|
||||
var from, to, index;
|
||||
|
||||
if (oldStatus === status) {
|
||||
@ -48,8 +81,6 @@ ConnectionPool.prototype.onStatusChanged = _.handler(function (status, oldStatus
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
this.config.log.info('connection id:', connection.id, 'is', status);
|
||||
}
|
||||
|
||||
switch (status) {
|
||||
@ -86,46 +117,56 @@ ConnectionPool.prototype.onStatusChanged = _.handler(function (status, oldStatus
|
||||
});
|
||||
|
||||
ConnectionPool.prototype.addConnection = function (connection) {
|
||||
if (!connection.id) {
|
||||
connection.id = connection.host.toString();
|
||||
}
|
||||
|
||||
if (!this.index[connection.id]) {
|
||||
this.log.info('Adding connection to', connection.id);
|
||||
this.index[connection.id] = connection;
|
||||
connection.on('status changed', this.bound.onStatusChanged);
|
||||
connection.on('status set', this.bound.onStatusSet);
|
||||
connection.setStatus('alive');
|
||||
}
|
||||
};
|
||||
|
||||
ConnectionPool.prototype.removeConnection = function (connection) {
|
||||
if (!connection.id) {
|
||||
connection.id = connection.host.toString();
|
||||
}
|
||||
|
||||
if (this.index[connection.id]) {
|
||||
delete this.index[connection.id];
|
||||
connection.setStatus('closed');
|
||||
connection.removeListener('status changed', this.bound.onStatusChanged);
|
||||
connection.removeListener('status set', this.bound.onStatusSet);
|
||||
}
|
||||
};
|
||||
|
||||
ConnectionPool.prototype.setNodes = function (nodeConfigs) {
|
||||
ConnectionPool.prototype.setHosts = function (hosts) {
|
||||
var connection;
|
||||
var i;
|
||||
var id;
|
||||
var node;
|
||||
var host;
|
||||
var toRemove = _.clone(this.index);
|
||||
|
||||
for (i = 0; i < nodeConfigs.length; i++) {
|
||||
node = nodeConfigs[i];
|
||||
if (node instanceof Host) {
|
||||
id = node.toString();
|
||||
if (this.index[id]) {
|
||||
delete toRemove[id];
|
||||
} else {
|
||||
connection = new this.config.connectionClass(node, this.config);
|
||||
connection.id = id;
|
||||
this.addConnection(connection);
|
||||
}
|
||||
for (i = 0; i < hosts.length; i++) {
|
||||
host = hosts[i];
|
||||
id = host.toString();
|
||||
if (this.index[id]) {
|
||||
delete toRemove[id];
|
||||
} else {
|
||||
connection = new this.Connection(host);
|
||||
connection.id = id;
|
||||
this.addConnection(connection);
|
||||
}
|
||||
}
|
||||
|
||||
_.each(toRemove, this.removeConnection, this);
|
||||
var removeIds = _.keys(toRemove);
|
||||
for (i = 0; i < removeIds.length; i++) {
|
||||
this.removeConnection(this.index[removeIds[i]]);
|
||||
}
|
||||
};
|
||||
|
||||
ConnectionPool.prototype.close = function () {
|
||||
this.setNodes([]);
|
||||
this.setHosts([]);
|
||||
};
|
||||
ConnectionPool.prototype.empty = ConnectionPool.prototype.close;
|
||||
|
||||
24
src/lib/connectors/browser_index.js
Normal file
24
src/lib/connectors/browser_index.js
Normal file
@ -0,0 +1,24 @@
|
||||
var opts = {
|
||||
xhr: require('./xhr'),
|
||||
jquery: require('./jquery'),
|
||||
angular: require('./angular')
|
||||
};
|
||||
var _ = require('../utils');
|
||||
|
||||
// remove modules that have been ignored by browserify
|
||||
_.each(opts, function (conn, name) {
|
||||
if (typeof conn !== 'function') {
|
||||
delete opts[name];
|
||||
}
|
||||
});
|
||||
|
||||
// custom __default specification
|
||||
if (opts.xhr) {
|
||||
opts._default = 'xhr';
|
||||
} else if (opts.angular) {
|
||||
opts._default = 'angular';
|
||||
} else {
|
||||
opts._default = 'jquery';
|
||||
}
|
||||
|
||||
module.exports = opts;
|
||||
@ -12,56 +12,64 @@ var handles = {
|
||||
http: require('http'),
|
||||
https: require('https')
|
||||
};
|
||||
var Log = require('../log');
|
||||
var _ = require('../utils');
|
||||
var errors = require('../errors');
|
||||
var qs = require('querystring');
|
||||
var KeepAliveAgent = require('agentkeepalive/lib/agent');
|
||||
var KeepAliveAgent = require('agentkeepalive');
|
||||
var ConnectionAbstract = require('../connection');
|
||||
|
||||
|
||||
/**
|
||||
* Connector used to talk to an elasticsearch node via HTTP
|
||||
*
|
||||
* @param {Host} host - The host object representing the elasticsearch node we will be talking to
|
||||
* @param {Object} [config] - Configuration options (extends the configuration options for ConnectionAbstract)
|
||||
* @param {Number} [config.maxSockets=10] - the maximum number of sockets that will be opened to this node
|
||||
* @param {Number} [config.maxFreeSockets=10] - this maximum number of sockets that can sit idle to this node
|
||||
* @param {Number} [config.maxKeepAliveTime=300000] - an idle timeout for the connections to this node. If your
|
||||
* maxSockets is much higher than your average concurrent usage, this timeout will cause sockets to close which
|
||||
* can be interpreted as "bad" behavior for clients.
|
||||
*/
|
||||
function HttpConnector(host, config) {
|
||||
config = _.defaults(config || {}, {
|
||||
maxSockets: 10,
|
||||
maxFreeSockets: 10,
|
||||
maxKeepAliveTime: 3e5 // 5 minutes
|
||||
});
|
||||
ConnectionAbstract.call(this, host, config);
|
||||
|
||||
this.hand = handles[this.host.protocol];
|
||||
this.agent = new KeepAliveAgent({
|
||||
maxSockets: 1,
|
||||
maxKeepAliveRequests: 0, // max requests per keepalive socket, default is 0, no limit.
|
||||
maxKeepAliveTime: 30000 // keepalive for 30 seconds
|
||||
keepAlive: true,
|
||||
maxSockets: config.maxSockets,
|
||||
maxFreeSockets: config.maxFreeSockets || this.hand.Agent.defaultMaxSockets,
|
||||
keepAliveMsecs: config.keepAliveMsecs
|
||||
});
|
||||
|
||||
this.on('closed', this.bound.onClosed);
|
||||
this.on('alive', this.bound.onAlive);
|
||||
this.log = config.log;
|
||||
if (!_.isObject(this.log)) {
|
||||
this.log = new Log();
|
||||
}
|
||||
}
|
||||
_.inherits(HttpConnector, ConnectionAbstract);
|
||||
|
||||
HttpConnector.prototype.onClosed = _.handler(function () {
|
||||
this.agent.destroy();
|
||||
this.removeAllListeners();
|
||||
});
|
||||
|
||||
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;
|
||||
});
|
||||
|
||||
HttpConnector.prototype.makeReqParams = function (params) {
|
||||
params = params || {};
|
||||
var host = this.host;
|
||||
|
||||
var reqParams = {
|
||||
method: params.method,
|
||||
protocol: this.host.protocol + ':',
|
||||
auth: this.host.auth,
|
||||
hostname: this.host.host,
|
||||
port: this.host.port,
|
||||
pathname: this.host.path + params.path,
|
||||
headers: this.host.headers,
|
||||
method: params.method || 'GET',
|
||||
protocol: host.protocol + ':',
|
||||
auth: host.auth,
|
||||
hostname: host.host,
|
||||
port: host.port,
|
||||
path: (host.path || '') + (params.path || ''),
|
||||
headers: host.headers,
|
||||
agent: this.agent
|
||||
};
|
||||
|
||||
var query = this.host.query ? this.host.query : null;
|
||||
|
||||
if (typeof query === 'string') {
|
||||
query = qs.parse(query);
|
||||
}
|
||||
|
||||
if (params.query) {
|
||||
query = _.defaults({},
|
||||
typeof params.query === 'string' ? qs.parse(params.query) : params.query,
|
||||
@ -70,10 +78,7 @@ HttpConnector.prototype.makeReqParams = function (params) {
|
||||
}
|
||||
|
||||
if (query) {
|
||||
reqParams.query = query;
|
||||
reqParams.path = reqParams.pathname + '?' + qs.stringify(query);
|
||||
} else {
|
||||
reqParams.path = reqParams.pathname;
|
||||
reqParams.path = reqParams.path + '?' + qs.stringify(query);
|
||||
}
|
||||
|
||||
return reqParams;
|
||||
@ -86,7 +91,7 @@ HttpConnector.prototype.request = function (params, cb) {
|
||||
var response;
|
||||
var status = 0;
|
||||
var timeout = params.timeout || this.config.timeout;
|
||||
var log = this.config.log;
|
||||
var log = this.log;
|
||||
|
||||
var reqParams = this.makeReqParams(params);
|
||||
|
||||
|
||||
4
src/lib/connectors/index.js
Normal file
4
src/lib/connectors/index.js
Normal file
@ -0,0 +1,4 @@
|
||||
module.exports = {
|
||||
http: require('./http'),
|
||||
_default: 'http'
|
||||
};
|
||||
@ -1,5 +1,5 @@
|
||||
var _ = require('./utils'),
|
||||
errors = module.exports;
|
||||
var _ = require('./utils');
|
||||
var errors = module.exports;
|
||||
|
||||
function ErrorAbstract(msg, constructor) {
|
||||
this.message = msg;
|
||||
@ -11,6 +11,7 @@ function ErrorAbstract(msg, constructor) {
|
||||
Error.captureStackTrace(this, constructor);
|
||||
}
|
||||
}
|
||||
errors._Abstract = ErrorAbstract;
|
||||
_.inherits(ErrorAbstract, Error);
|
||||
|
||||
/**
|
||||
|
||||
115
src/lib/host.js
115
src/lib/host.js
@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Class to wrap URLS, formatting them and maintaining their seperate details
|
||||
* Class to wrap URLS, formatting them and maintaining their separate details
|
||||
* @type {[type]}
|
||||
*/
|
||||
module.exports = Host;
|
||||
@ -16,42 +16,76 @@ var defaultPort = {
|
||||
https: 443
|
||||
};
|
||||
|
||||
var urlParseFields = [
|
||||
'protocol', 'hostname', 'pathname', 'port', 'auth', 'query'
|
||||
];
|
||||
|
||||
var simplify = ['host', 'path'];
|
||||
|
||||
function Host(config) {
|
||||
if (this instanceof Host) {
|
||||
if (typeof config === 'string') {
|
||||
return Host.fromString(config);
|
||||
} else {
|
||||
_.extend(this, config || {});
|
||||
config = config || {};
|
||||
|
||||
// defaults
|
||||
this.protocol = 'http';
|
||||
this.host = 'localhost';
|
||||
this.port = 9200;
|
||||
this.auth = null;
|
||||
this.query = null;
|
||||
|
||||
if (typeof config === 'string') {
|
||||
if (!startsWithProtocolRE.test(config)) {
|
||||
config = 'http://' + config;
|
||||
}
|
||||
config = _.pick(url.parse(config, false, true), urlParseFields);
|
||||
}
|
||||
|
||||
if (_.isObject(config)) {
|
||||
// move hostname/portname to host/port semi-intelligently.
|
||||
_.each(simplify, function (to) {
|
||||
var from = to + 'name';
|
||||
if (config[from] && config[to]) {
|
||||
if (config[to].indexOf(config[from]) === 0) {
|
||||
config[to] = config[from];
|
||||
}
|
||||
} else if (config[from]) {
|
||||
config[to] = config[from];
|
||||
}
|
||||
delete config[from];
|
||||
});
|
||||
} else {
|
||||
return new Host(config);
|
||||
config = {};
|
||||
}
|
||||
|
||||
_.assign(this, config);
|
||||
|
||||
// make sure the query string is parsed
|
||||
if (this.query === null) {
|
||||
// majority case
|
||||
this.query = {};
|
||||
} else if (!_.isPlainObject(this.query)) {
|
||||
this.query = qs.parse(this.query);
|
||||
}
|
||||
|
||||
// make sure that the port is a number
|
||||
if (typeof this.port !== 'number') {
|
||||
this.port = parseInt(this.port, 10);
|
||||
if (isNaN(this.port)) {
|
||||
this.port = 9200;
|
||||
}
|
||||
}
|
||||
|
||||
// make sure the path starts with a leading slash
|
||||
// and that empty paths convert to '/'
|
||||
if (!this.path || this.path.charAt(0) !== '/') {
|
||||
this.path = '/' + (this.path || '');
|
||||
}
|
||||
|
||||
// strip trailing ':' on the protocol (when config comes from url.parse)
|
||||
if (this.protocol.substr(-1) === ':') {
|
||||
this.protocol = this.protocol.substring(0, this.protocol.length - 1);
|
||||
}
|
||||
}
|
||||
|
||||
Host.fromString = function (urlString) {
|
||||
if (!startsWithProtocolRE.test(urlString)) {
|
||||
urlString = 'http://' + urlString;
|
||||
}
|
||||
var u = url.parse(urlString, true, true);
|
||||
return new Host({
|
||||
protocol: u.protocol ? u.protocol.substring(0, u.protocol.length - 1) : 'http',
|
||||
host: u.hostname || 'localhost',
|
||||
port: u.port || 9200,
|
||||
auth: u.auth || '',
|
||||
path: u.pathname,
|
||||
query: u.query,
|
||||
});
|
||||
};
|
||||
|
||||
Host.prototype = {
|
||||
protocol: 'http',
|
||||
host: 'localhost',
|
||||
port: 9200,
|
||||
auth: '',
|
||||
path: '',
|
||||
query: false
|
||||
};
|
||||
|
||||
Host.prototype.makeUrl = function (params) {
|
||||
params = params || {};
|
||||
// build the port
|
||||
@ -62,17 +96,10 @@ Host.prototype.makeUrl = function (params) {
|
||||
}
|
||||
|
||||
// build the path
|
||||
var path = '';
|
||||
// add the path prefix if set
|
||||
if (this.path) {
|
||||
path += this.path;
|
||||
}
|
||||
// then the path from the params
|
||||
if (params.path) {
|
||||
path += params.path;
|
||||
}
|
||||
// if we still have a path, and it doesn't start with '/' add it.
|
||||
if (path && path.charAt(0) !== '/') {
|
||||
var path = '' + (this.path || '') + (params.path || '');
|
||||
|
||||
// if path doesn't start with '/' add it.
|
||||
if (path.charAt(0) !== '/') {
|
||||
path = '/' + path;
|
||||
}
|
||||
|
||||
@ -90,3 +117,7 @@ Host.prototype.makeUrl = function (params) {
|
||||
|
||||
return this.protocol + '://' + this.host + port + path + (query ? '?' + query : '');
|
||||
};
|
||||
|
||||
Host.prototype.toString = function () {
|
||||
return this.makeUrl();
|
||||
};
|
||||
|
||||
109
src/lib/log.js
109
src/lib/log.js
@ -1,20 +1,6 @@
|
||||
var _ = require('./utils');
|
||||
var url = require('url');
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
if (process.browser) {
|
||||
var loggers = {
|
||||
Console: require('./loggers/console')
|
||||
};
|
||||
} else {
|
||||
var loggers = {
|
||||
File: require('./loggers/file'),
|
||||
Stream: require('./loggers/file'),
|
||||
Stdio: require('./loggers/stdio'),
|
||||
Tracer: require('./loggers/tracer')
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Log bridge, which is an [EventEmitter](http://nodejs.org/api/events.html#events_class_events_eventemitter)
|
||||
@ -31,40 +17,42 @@ if (process.browser) {
|
||||
* @param {string} output.type - The name of the logger to use for this output
|
||||
*/
|
||||
function Log(config) {
|
||||
this.config = config || {};
|
||||
config = config || {};
|
||||
|
||||
var i;
|
||||
var output = config.loggers ? config.loggers : 'warning';
|
||||
var outputs;
|
||||
|
||||
if (_.isString(output) || _.isFinite(output)) {
|
||||
output = [
|
||||
{
|
||||
level: output
|
||||
}
|
||||
];
|
||||
} else if (_.isPlainObject(output)) {
|
||||
output = [output];
|
||||
} else if (_.isArray(output)) {
|
||||
for (i = 0; i < output.length; i++) {
|
||||
if (_.isString(output[i])) {
|
||||
output[i] = {
|
||||
level: output[i]
|
||||
};
|
||||
}
|
||||
if (config.log) {
|
||||
if (_.isArrayOfStrings(config.log)) {
|
||||
outputs = [{
|
||||
levels: config.log
|
||||
}];
|
||||
} else {
|
||||
outputs = _.createArray(config.log, function (val) {
|
||||
if (_.isPlainObject(val)) {
|
||||
return val;
|
||||
}
|
||||
if (typeof val === 'string') {
|
||||
return {
|
||||
level: val
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!outputs) {
|
||||
throw new TypeError('Invalid logging output config. Expected either a log level, array of log levels, ' +
|
||||
'a logger config object, or an array of logger config objects.');
|
||||
}
|
||||
|
||||
for (i = 0; i < outputs.length; i++) {
|
||||
this.addOutput(outputs[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!_.isArrayOfPlainObjects(output)) {
|
||||
throw new TypeError('Invalid Logging output config');
|
||||
}
|
||||
|
||||
for (i = 0; i < output.length; i++) {
|
||||
this.addOutput(output[i]);
|
||||
}
|
||||
|
||||
}
|
||||
_.inherits(Log, EventEmitter);
|
||||
|
||||
Log.loggers = require('./loggers');
|
||||
|
||||
Log.prototype.close = function () {
|
||||
this.emit('closing');
|
||||
@ -149,11 +137,24 @@ Log.levels = [
|
||||
* @return {Array} -
|
||||
*/
|
||||
Log.parseLevels = function (input) {
|
||||
if (_.isString(input)) {
|
||||
return Log.levels.slice(0, _.indexOf(Log.levels, input) + 1);
|
||||
}
|
||||
else if (_.isArray(input)) {
|
||||
return _.intersection(input, Log.levels);
|
||||
switch (typeof input) {
|
||||
case 'string':
|
||||
var i = _.indexOf(Log.levels, input);
|
||||
if (i >= 0) {
|
||||
return Log.levels.slice(0, i + 1);
|
||||
}
|
||||
/* fall through */
|
||||
case 'object':
|
||||
if (_.isArray(input)) {
|
||||
var valid = _.intersection(input, Log.levels);
|
||||
if (valid.length === input.length) {
|
||||
return valid;
|
||||
}
|
||||
}
|
||||
/* fall through */
|
||||
default:
|
||||
throw new TypeError('invalid logging level ' + input + '. Expected zero or more of these options: ' +
|
||||
Log.levels.join(', '));
|
||||
}
|
||||
};
|
||||
|
||||
@ -190,22 +191,14 @@ Log.join = function (arrayish) {
|
||||
* @return {Logger}
|
||||
*/
|
||||
Log.prototype.addOutput = function (config) {
|
||||
var levels = Log.parseLevels(config.levels || config.level || 'warning');
|
||||
config = config || {};
|
||||
|
||||
_.defaults(config || {}, {
|
||||
type: process.browser ? 'Console' : 'Stdio',
|
||||
});
|
||||
|
||||
// force the levels config
|
||||
// force "levels" key
|
||||
config.levels = Log.parseLevels(config.levels || config.level || 'warning');
|
||||
delete config.level;
|
||||
config.levels = levels;
|
||||
|
||||
var Logger = loggers[_.studlyCase(config.type)];
|
||||
if (Logger) {
|
||||
return new Logger(config, this);
|
||||
} else {
|
||||
throw new Error('Invalid logger type "' + config.type + '". Expected one of ' + _.keys(loggers).join(', '));
|
||||
}
|
||||
var Logger = _.funcEnum(config, 'type', Log.loggers, process.browser ? 'console' : 'stdio');
|
||||
return new Logger(this, config);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@ -2,18 +2,17 @@ var _ = require('./utils');
|
||||
|
||||
/**
|
||||
* Abstract class providing common functionality to loggers
|
||||
* @param {[type]} log [description]
|
||||
* @param {[type]} config [description]
|
||||
* @param {[type]} bridge [description]
|
||||
*/
|
||||
function LoggerAbstract(config, bridge) {
|
||||
|
||||
this.bridge = bridge;
|
||||
function LoggerAbstract(log, config) {
|
||||
this.log = log;
|
||||
this.listeningLevels = [];
|
||||
|
||||
_.makeBoundMethods(this);
|
||||
|
||||
// when the bridge closes, remove our event listeners
|
||||
this.bridge.on('closing', this.bound.cleanUpListeners);
|
||||
// when the log closes, remove our event listeners
|
||||
this.log.on('closing', this.bound.cleanUpListeners);
|
||||
|
||||
this.setupListeners(config.levels);
|
||||
}
|
||||
@ -68,7 +67,7 @@ LoggerAbstract.prototype.setupListeners = function (levels) {
|
||||
_.each(this.listeningLevels, function (level) {
|
||||
var fnName = 'on' + _.ucfirst(level);
|
||||
if (this.bound[fnName]) {
|
||||
this.bridge.on(level, this.bound[fnName]);
|
||||
this.log.on(level, this.bound[fnName]);
|
||||
} else {
|
||||
throw new Error(fnName + ' is not a function');
|
||||
}
|
||||
@ -84,12 +83,12 @@ LoggerAbstract.prototype.setupListeners = function (levels) {
|
||||
*/
|
||||
LoggerAbstract.prototype.cleanUpListeners = _.handler(function () {
|
||||
_.each(this.listeningLevels, function (level) {
|
||||
this.bridge.removeListener(level, this.bound['on' + _.ucfirst(level)]);
|
||||
this.log.removeListener(level, this.bound['on' + _.ucfirst(level)]);
|
||||
}, this);
|
||||
});
|
||||
|
||||
/**
|
||||
* Handler for the bridges "error" event
|
||||
* Handler for the logs "error" event
|
||||
*
|
||||
* @method onError
|
||||
* @private
|
||||
@ -101,7 +100,7 @@ LoggerAbstract.prototype.onError = _.handler(function (e) {
|
||||
});
|
||||
|
||||
/**
|
||||
* Handler for the bridges "warning" event
|
||||
* Handler for the logs "warning" event
|
||||
*
|
||||
* @method onWarning
|
||||
* @private
|
||||
@ -113,7 +112,7 @@ LoggerAbstract.prototype.onWarning = _.handler(function (msg) {
|
||||
});
|
||||
|
||||
/**
|
||||
* Handler for the bridges "info" event
|
||||
* Handler for the logs "info" event
|
||||
*
|
||||
* @method onInfo
|
||||
* @private
|
||||
@ -125,7 +124,7 @@ LoggerAbstract.prototype.onInfo = _.handler(function (msg) {
|
||||
});
|
||||
|
||||
/**
|
||||
* Handler for the bridges "debug" event
|
||||
* Handler for the logs "debug" event
|
||||
*
|
||||
* @method onDebug
|
||||
* @private
|
||||
@ -137,7 +136,7 @@ LoggerAbstract.prototype.onDebug = _.handler(function (msg) {
|
||||
});
|
||||
|
||||
/**
|
||||
* Handler for the bridges "trace" event
|
||||
* Handler for the logs "trace" event
|
||||
*
|
||||
* @method onTrace
|
||||
* @private
|
||||
|
||||
3
src/lib/loggers/browser_index.js
Normal file
3
src/lib/loggers/browser_index.js
Normal file
@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
console: require('./console')
|
||||
};
|
||||
@ -15,9 +15,9 @@ module.exports = Console;
|
||||
var LoggerAbstract = require('../logger');
|
||||
var _ = require('../utils');
|
||||
|
||||
function Console(config, bridge) {
|
||||
function Console(log, config) {
|
||||
// call my super
|
||||
LoggerAbstract.call(this, config, bridge);
|
||||
LoggerAbstract.call(this, log, config);
|
||||
|
||||
// config/state
|
||||
this.color = _.has(config, 'color') ? !!config.color : true;
|
||||
|
||||
@ -15,16 +15,20 @@ var StreamLogger = require('./stream');
|
||||
var _ = require('../utils');
|
||||
var fs = require('fs');
|
||||
|
||||
function File(config, bridge) {
|
||||
// setup the stream before calling the super
|
||||
function File(log, config) {
|
||||
config = config || {};
|
||||
|
||||
// we should probably through an error if they don't support a path
|
||||
this.path = config.path || 'elasticsearch.log';
|
||||
|
||||
// yahoo!
|
||||
config.stream = fs.createWriteStream(this.path, {
|
||||
flags: 'a',
|
||||
encoding: 'utf8'
|
||||
});
|
||||
|
||||
// call my super
|
||||
StreamLogger.call(this, config, bridge);
|
||||
StreamLogger.call(this, log, config);
|
||||
}
|
||||
_.inherits(File, StreamLogger);
|
||||
|
||||
|
||||
6
src/lib/loggers/index.js
Normal file
6
src/lib/loggers/index.js
Normal file
@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
file: require('./file'),
|
||||
stream: require('./file'),
|
||||
stdio: require('./stdio'),
|
||||
tracer: require('./tracer')
|
||||
};
|
||||
@ -40,12 +40,12 @@ var defaultColors = {
|
||||
}
|
||||
};
|
||||
|
||||
function Stdio(config, bridge) {
|
||||
function Stdio(log, config) {
|
||||
// call my super
|
||||
LoggerAbstract.call(this, config, bridge);
|
||||
LoggerAbstract.call(this, log, config);
|
||||
|
||||
// config/state
|
||||
this.color = Boolean(_.has(config, 'color') ? config.color : chalk.supportsColor);
|
||||
this.color = !!(_.has(config, 'color') ? config.color : chalk.supportsColor);
|
||||
|
||||
this.colors = _.defaults(config.colors || {}, defaultColors);
|
||||
}
|
||||
|
||||
@ -15,11 +15,11 @@ module.exports = Stream;
|
||||
var LoggerAbstract = require('../logger');
|
||||
var _ = require('../utils');
|
||||
|
||||
function Stream(config, bridge) {
|
||||
function Stream(log, config) {
|
||||
// call my super
|
||||
LoggerAbstract.call(this, config, bridge);
|
||||
LoggerAbstract.call(this, log, config);
|
||||
|
||||
if (config.stream.write && config.stream.end) {
|
||||
if (config.stream && config.stream.write && config.stream.end) {
|
||||
this.stream = config.stream;
|
||||
} else {
|
||||
throw new TypeError('Invalid stream, use an instance of stream.Writeable');
|
||||
|
||||
@ -15,9 +15,9 @@ module.exports = Tracer;
|
||||
var FileLogger = require('./file');
|
||||
var _ = require('../utils');
|
||||
|
||||
function Tracer(config, bridge) {
|
||||
function Tracer(log, config) {
|
||||
// call my super
|
||||
FileLogger.call(this, config, bridge);
|
||||
FileLogger.call(this, log, config);
|
||||
}
|
||||
_.inherits(Tracer, FileLogger);
|
||||
|
||||
|
||||
@ -1,5 +1,11 @@
|
||||
module.exports = RandomSelect;
|
||||
|
||||
function RandomSelect(connections) {
|
||||
/**
|
||||
* Selects a connection randomly
|
||||
*
|
||||
* @module selectors
|
||||
* @type {Function}
|
||||
* @param {Array} connection - The list of connections to choose from
|
||||
* @return {Connection} - The selected connection
|
||||
*/
|
||||
module.exports = function RandomSelector(connections) {
|
||||
return connections[Math.floor(Math.random() * connections.length)];
|
||||
}
|
||||
};
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
/**
|
||||
* Selects a connection the simplest way possible, Round Robin
|
||||
*
|
||||
* @class selector.roundRobin
|
||||
* @constructor
|
||||
* @module selectors
|
||||
* @type {Function}
|
||||
* @param {Array} connections - The list of connections that this selector needs to choose from
|
||||
* @return {Connection} - The selected connection
|
||||
*/
|
||||
module.exports = RoundRobinSelect;
|
||||
|
||||
function RoundRobinSelect(connections) {
|
||||
connections.unshift(connections.pop());
|
||||
return connections[0];
|
||||
}
|
||||
module.exports = function (connections) {
|
||||
var connection = connections[0];
|
||||
connections.push(connections.shift());
|
||||
return connection;
|
||||
};
|
||||
|
||||
@ -6,13 +6,11 @@ module.exports = Json;
|
||||
|
||||
var _ = require('../utils');
|
||||
|
||||
function Json(client) {
|
||||
this.client = client;
|
||||
}
|
||||
function Json() {}
|
||||
|
||||
Json.prototype.serialize = function (val, replacer, spaces) {
|
||||
if (val == null) {
|
||||
return null;
|
||||
return;
|
||||
}
|
||||
else if (typeof val === 'string') {
|
||||
return val;
|
||||
@ -26,8 +24,7 @@ Json.prototype.unserialize = function (str) {
|
||||
try {
|
||||
return JSON.parse(str);
|
||||
} catch (e) {
|
||||
this.client.log.error(new Error('unable to parse', str));
|
||||
return null;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
return str;
|
||||
|
||||
@ -6,12 +6,66 @@ module.exports = Transport;
|
||||
|
||||
var _ = require('./utils');
|
||||
var errors = require('./errors');
|
||||
var Host = require('./host');
|
||||
var Log = require('./log');
|
||||
var when = require('when');
|
||||
|
||||
function Transport(config) {
|
||||
this.config = config;
|
||||
config = config || {};
|
||||
|
||||
var LogClass;
|
||||
// setup the log
|
||||
switch (typeof config.log) {
|
||||
case 'function':
|
||||
LogClass = config.log;
|
||||
break;
|
||||
case 'undefined':
|
||||
config.log = 'warning';
|
||||
/* fall through */
|
||||
default:
|
||||
LogClass = Log;
|
||||
}
|
||||
|
||||
config.log = this.log = new LogClass(config);
|
||||
|
||||
// overwrite the createDefer method if a new implementation is provided
|
||||
if (typeof config.createDefer === 'function') {
|
||||
this.createDefer = config.createDefer;
|
||||
}
|
||||
|
||||
// setup the connection pool
|
||||
var ConnectionPool = _.funcEnum(config, 'connectionPool', Transport.connectionPools, 'main');
|
||||
this.connectionPool = new ConnectionPool(config);
|
||||
|
||||
if (config.hosts) {
|
||||
var hosts = _.createArray(config.hosts, function (val) {
|
||||
if (_.isPlainObject(val) || _.isString(val)) {
|
||||
return val;
|
||||
}
|
||||
});
|
||||
if (!hosts) {
|
||||
throw new Error('Invalid hosts config. Expected a URL, an array of urls, a host config object, or an array of ' +
|
||||
'host config objects.');
|
||||
}
|
||||
|
||||
this.connectionPool.setHosts(_.map(hosts, function (conf) {
|
||||
return new Host(conf);
|
||||
}));
|
||||
}
|
||||
|
||||
// setup the serializer
|
||||
var Serializer = _.funcEnum(config, 'serializer', Transport.serializers, 'json');
|
||||
this.serializer = new Serializer(config);
|
||||
}
|
||||
|
||||
Transport.connectionPools = {
|
||||
main: require('./connection_pool')
|
||||
};
|
||||
|
||||
Transport.serializers = {
|
||||
json: require('./serializers/json')
|
||||
};
|
||||
|
||||
/**
|
||||
* Perform a request with the client's transport
|
||||
*
|
||||
@ -27,15 +81,13 @@ function Transport(config) {
|
||||
*/
|
||||
Transport.prototype.request = function (params, cb) {
|
||||
|
||||
var log = this.config.log;
|
||||
var serializer = this.config.serializer;
|
||||
var connectionPool = this.config.connectionPool;
|
||||
var remainingRetries = this.config.maxRetries;
|
||||
var self = this;
|
||||
var remainingRetries = this.maxRetries;
|
||||
var connection; // set in sendReqWithConnection
|
||||
var connectionReq; // an object with an abort method, set in sendReqWithConnection
|
||||
var request; // the object returned to the user, might be a deferred
|
||||
|
||||
log.debug('starting request', params);
|
||||
self.log.debug('starting request', params);
|
||||
|
||||
if (params.body && params.method === 'GET') {
|
||||
_.nextTick(respond, new TypeError('Body can not be sent with method "GET"'));
|
||||
@ -44,7 +96,7 @@ Transport.prototype.request = function (params, cb) {
|
||||
|
||||
// serialize the body
|
||||
if (params.body) {
|
||||
params.body = serializer[params.bulkBody ? 'bulkBody' : 'serialize'](params.body);
|
||||
params.body = self.serializer[params.bulkBody ? 'bulkBody' : 'serialize'](params.body);
|
||||
}
|
||||
|
||||
params.req = {
|
||||
@ -55,7 +107,7 @@ Transport.prototype.request = function (params, cb) {
|
||||
body: params.body,
|
||||
};
|
||||
|
||||
connectionPool.select(sendReqWithConnection);
|
||||
self.connectionPool.select(sendReqWithConnection);
|
||||
|
||||
function abortRequest() {
|
||||
remainingRetries = 0;
|
||||
@ -69,7 +121,7 @@ Transport.prototype.request = function (params, cb) {
|
||||
connection = _connection;
|
||||
connectionReq = connection.request(params.req, checkRespForFailure);
|
||||
} else {
|
||||
log.warning('No living connections');
|
||||
self.log.warning('No living connections');
|
||||
respond(new errors.NoConnections());
|
||||
}
|
||||
}
|
||||
@ -77,10 +129,10 @@ Transport.prototype.request = function (params, cb) {
|
||||
function checkRespForFailure(err, body, status) {
|
||||
if (err && remainingRetries) {
|
||||
remainingRetries--;
|
||||
log.error(err.message, '-- retrying');
|
||||
connectionPool.select(sendReqWithConnection);
|
||||
self.log.error(err.message, '-- retrying');
|
||||
self.connectionPool.select(sendReqWithConnection);
|
||||
} else {
|
||||
log.info('Request complete');
|
||||
self.log.info('Request complete');
|
||||
respond(err, body, status);
|
||||
}
|
||||
}
|
||||
@ -89,21 +141,15 @@ Transport.prototype.request = function (params, cb) {
|
||||
var parsedBody;
|
||||
|
||||
if (!err && body) {
|
||||
parsedBody = serializer.unserialize(body);
|
||||
parsedBody = self.serializer.unserialize(body);
|
||||
if (parsedBody == null) {
|
||||
err = new errors.Serialization();
|
||||
}
|
||||
}
|
||||
|
||||
if (!err) {
|
||||
// get ignore and ensure that it's an array
|
||||
var ignore = params.ignore;
|
||||
if (ignore && !_.isArray(ignore)) {
|
||||
ignore = [ignore];
|
||||
}
|
||||
|
||||
if ((status < 200 || status >= 300)
|
||||
&& (!ignore || !_.contains(ignore, status))
|
||||
&& (!params.ignore || !_.contains(params.ignore, status))
|
||||
) {
|
||||
if (errors[status]) {
|
||||
err = new errors[status](parsedBody && parsedBody.error);
|
||||
@ -140,7 +186,7 @@ Transport.prototype.request = function (params, cb) {
|
||||
abort: abortRequest
|
||||
};
|
||||
} else {
|
||||
var defer = when.defer();
|
||||
var defer = this.createDefer();
|
||||
defer.promise.abort = abortRequest;
|
||||
request = defer.promise;
|
||||
}
|
||||
@ -148,6 +194,10 @@ Transport.prototype.request = function (params, cb) {
|
||||
return request;
|
||||
};
|
||||
|
||||
Transport.prototype.createDefer = function () {
|
||||
return when.defer();
|
||||
};
|
||||
|
||||
/**
|
||||
* Ask an ES node for a list of all the nodes, add/remove nodes from the connection
|
||||
* pool as appropriate
|
||||
@ -155,7 +205,7 @@ Transport.prototype.request = function (params, cb) {
|
||||
* @param {Function} cb - Function to call back once complete
|
||||
*/
|
||||
Transport.prototype.sniff = function (cb) {
|
||||
var config = this.config;
|
||||
var self = this;
|
||||
|
||||
// make cb a function if it isn't
|
||||
cb = typeof cb === 'function' ? cb : _.noop;
|
||||
@ -165,9 +215,16 @@ Transport.prototype.sniff = function (cb) {
|
||||
method: 'GET'
|
||||
}, function (err, resp) {
|
||||
if (!err && resp && resp.nodes) {
|
||||
var nodes = config.nodesToHostCallback(resp.nodes);
|
||||
config.connectionPool.setNodes(nodes);
|
||||
var hosts = _.map(self.nodesToHostCallback(resp.nodes), function (hostConfig) {
|
||||
return new Host(hostConfig);
|
||||
});
|
||||
this.connectionPool.setHosts(hosts);
|
||||
}
|
||||
cb(err, resp);
|
||||
});
|
||||
};
|
||||
|
||||
Transport.prototype.close = function () {
|
||||
this.log.close();
|
||||
this.connectionPool.close();
|
||||
};
|
||||
|
||||
111
src/lib/utils.js
111
src/lib/utils.js
@ -13,15 +13,6 @@ var nodeUtils = require('util');
|
||||
var utils = _.extend({}, _, nodeUtils);
|
||||
_ = utils;
|
||||
|
||||
utils.inspect = function (thing, opts) {
|
||||
return nodeUtils.inspect(thing, _.defaults(opts || {}, {
|
||||
showHidden: true,
|
||||
depth: null,
|
||||
color: true
|
||||
}));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Link to [path.join](http://nodejs.org/api/path.html#path_path_join_path1_path2)
|
||||
*
|
||||
@ -46,7 +37,7 @@ utils.reKey = function (obj, transform, recursive) {
|
||||
var out = {};
|
||||
|
||||
_.each(obj, function (prop, name) {
|
||||
if (recursive && typeof prop === 'object') {
|
||||
if (recursive && _.isPlainObject(prop)) {
|
||||
out[transform(name)] = utils.reKey(prop, transform, recursive);
|
||||
} else {
|
||||
out[transform(name)] = prop;
|
||||
@ -104,7 +95,6 @@ utils.deepMerge = function (to, from) {
|
||||
/**
|
||||
* Capitalize the first letter of a word
|
||||
*
|
||||
* @todo Tests
|
||||
* @method ucfirst
|
||||
* @param {string} word - The word to transform
|
||||
* @return {string}
|
||||
@ -166,7 +156,6 @@ function adjustWordCase(firstWordCap, otherWordsCap, sep) {
|
||||
/**
|
||||
* Transform a string into StudlyCase
|
||||
*
|
||||
* @todo Tests
|
||||
* @method studlyCase
|
||||
* @param {String} string
|
||||
* @return {String}
|
||||
@ -176,7 +165,6 @@ utils.studlyCase = adjustWordCase(true, true, '');
|
||||
/**
|
||||
* Transform a string into camelCase
|
||||
*
|
||||
* @todo Tests
|
||||
* @method camelCase
|
||||
* @param {String} string
|
||||
* @return {String}
|
||||
@ -186,7 +174,6 @@ utils.camelCase = adjustWordCase(false, true, '');
|
||||
/**
|
||||
* Transform a string into snakeCase
|
||||
*
|
||||
* @todo Tests
|
||||
* @method snakeCase
|
||||
* @param {String} string
|
||||
* @return {String}
|
||||
@ -196,7 +183,6 @@ utils.snakeCase = adjustWordCase(false, false, '_');
|
||||
/**
|
||||
* Lower-case a string, and return an empty string if any is not a string
|
||||
*
|
||||
* @todo Tests
|
||||
* @param any {*} - Something or nothing
|
||||
* @returns {string}
|
||||
*/
|
||||
@ -214,7 +200,6 @@ utils.toLowerString = function (any) {
|
||||
/**
|
||||
* Upper-case the string, return an empty string if any is not a string
|
||||
*
|
||||
* @todo Tests
|
||||
* @param any {*} - Something or nothing
|
||||
* @returns {string}
|
||||
*/
|
||||
@ -232,13 +217,12 @@ utils.toUpperString = function (any) {
|
||||
/**
|
||||
* Test if a value is "numeric" meaning that it can be transformed into something besides NaN
|
||||
*
|
||||
* @todo Tests
|
||||
* @method isNumeric
|
||||
* @param {*} val
|
||||
* @return {Boolean}
|
||||
*/
|
||||
utils.isNumeric = function (val) {
|
||||
return !isNaN(val === null ? NaN : val * 1);
|
||||
return typeof val !== 'object' && val - parseFloat(val) >= 0;
|
||||
};
|
||||
|
||||
// regexp to test for intervals
|
||||
@ -247,7 +231,6 @@ var intervalRE = /^(\d+(?:\.\d+)?)([Mwdhmsy])$/;
|
||||
/**
|
||||
* Test if a string represents an interval (eg. 1m, 2Y)
|
||||
*
|
||||
* @todo Test
|
||||
* @method isInterval
|
||||
* @param {String} val
|
||||
* @return {Boolean}
|
||||
@ -259,7 +242,6 @@ utils.isInterval = function (val) {
|
||||
/**
|
||||
* Repeat a string n times
|
||||
*
|
||||
* @todo Test
|
||||
* @todo TestPerformance
|
||||
* @method repeat
|
||||
* @param {String} what - The string to repeat
|
||||
@ -271,27 +253,18 @@ utils.repeat = function (what, times) {
|
||||
};
|
||||
|
||||
/**
|
||||
* Override node's util.inherits function to also supply a callSuper function on the child class that can be called
|
||||
* with the instance and the arguments passed to the child's constructor. This should only be called from within the
|
||||
* constructor of the child class and should be removed from the code once the constructor is "done".
|
||||
* Override node's util.inherits function, providing a browser safe version thanks to lodash
|
||||
*
|
||||
* @param constructor {Function} - the constructor that should subClass superConstructor
|
||||
* @param superConstructor {Function} - The parent constructor
|
||||
*/
|
||||
utils.inherits = function (constructor, superConstructor) {
|
||||
nodeUtils.inherits(constructor, superConstructor);
|
||||
constructor.callSuper = function (inst, args) {
|
||||
if (args) {
|
||||
if (_.isArguments(args)) {
|
||||
utils.applyArgs(superConstructor, inst, args);
|
||||
} else {
|
||||
utils.applyArgs(superConstructor, inst, arguments, 1);
|
||||
}
|
||||
} else {
|
||||
superConstructor.call(inst);
|
||||
}
|
||||
};
|
||||
};
|
||||
if (process.browser) {
|
||||
utils.inherits = require('inherits');
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* Remove leading/trailing spaces from a string
|
||||
@ -412,15 +385,59 @@ _.makeBoundMethods = function (obj, methods) {
|
||||
|
||||
_.noop = function () {};
|
||||
|
||||
// _.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');
|
||||
// };
|
||||
/**
|
||||
* Implements the standard "string or constructor" check that I was copy/pasting everywhere
|
||||
* @param {String|Function} val - the value that the user passed in
|
||||
* @param {Object} opts - a map of the options
|
||||
* @return {Function|undefined} - If a valid option was specified, then the constructor is returned
|
||||
*/
|
||||
_.funcEnum = function (config, name, opts, def) {
|
||||
var val = config[name];
|
||||
switch (typeof val) {
|
||||
case 'undefined':
|
||||
return opts[def];
|
||||
case 'function':
|
||||
return val;
|
||||
case 'string':
|
||||
if (opts[val]) {
|
||||
return opts[val];
|
||||
}
|
||||
/* falls through */
|
||||
default:
|
||||
throw new TypeError('Invalid ' + name + ' "' + val + '", expected a function or one of ' +
|
||||
_.keys(opts).join(', '));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Accepts any object and attempts to convert it into an array. If the object passed in is not
|
||||
* an array it will be wrapped in one. Then the transform/map function will be called for each element
|
||||
* and create a new array that is returned. If the map function fails to return something, the loop is
|
||||
* halted and false is returned instead of an array.
|
||||
*
|
||||
* @param {*} input - The value to convert
|
||||
* @param {Function} transform - A function called for each element of the resulting array
|
||||
* @return {Array|false} - an array on success, or false on failure.
|
||||
*/
|
||||
_.createArray = function (input, transform) {
|
||||
transform = typeof transform === 'function' ? transform : _.identity;
|
||||
var output = [];
|
||||
var item;
|
||||
var i;
|
||||
|
||||
if (!_.isArray(input)) {
|
||||
input = [input];
|
||||
}
|
||||
|
||||
for (i = 0; i < input.length; i++) {
|
||||
item = transform(input[i]);
|
||||
if (item === void 0) {
|
||||
return false;
|
||||
} else {
|
||||
output.push(item);
|
||||
}
|
||||
}
|
||||
return output;
|
||||
};
|
||||
|
||||
module.exports = utils;
|
||||
|
||||
@ -34,8 +34,12 @@
|
||||
var stats = this.stats;
|
||||
var rootSuite = {
|
||||
$el: $('<ul id="mocha-report"></ul>'),
|
||||
name: 'root',
|
||||
start: Date.now(),
|
||||
results: [],
|
||||
suites: []
|
||||
suites: [],
|
||||
stdout: '',
|
||||
stderr: '',
|
||||
};
|
||||
|
||||
|
||||
@ -135,7 +139,7 @@
|
||||
runner.on('end', function () {
|
||||
var testResults = {
|
||||
stats: stats,
|
||||
suites: $.map(rootSuite.suites, function removeElements(suite) {
|
||||
suites: $.map([rootSuite], function removeElements(suite) {
|
||||
var s = {
|
||||
name: suite.name,
|
||||
start: suite.start,
|
||||
@ -12,6 +12,7 @@
|
||||
<script type="text/javascript" src="expect.js?_c=<%= ts %>"></script>
|
||||
<script type="text/javascript" src="mocha.js?_c=<%= ts %>"></script>
|
||||
<script type="text/javascript" src="jquery.js?_c=<%= ts %>"></script>
|
||||
<script type="text/javascript" src="client.js?_c=<%= ts %>"></script>
|
||||
|
||||
<!-- ESJS Reporter - sets itself as the reporter -->
|
||||
<link rel="stylesheet" href="esjs_reporter.css?_c=<%= ts %>"/>
|
||||
@ -24,18 +25,21 @@
|
||||
var BROWSER_NAME = <%= JSON.stringify(browser) %>;
|
||||
</script>
|
||||
|
||||
<!-- test -->
|
||||
<script>mocha.setup('bdd')</script>
|
||||
<script type="text/javascript" src="client.js?_c=<%= ts %>"></script>
|
||||
<!-- mocha setup -->
|
||||
<script>
|
||||
mocha.setup('bdd')
|
||||
mocha.checkLeaks();
|
||||
mocha.globals(['console', 'elasticsearch']);
|
||||
mocha.slow(1000);
|
||||
mocha.timeout(11000);
|
||||
mocha.bail();
|
||||
</script>
|
||||
|
||||
<!-- include tests -->
|
||||
<script type="text/javascript" src="yaml_tests.js?_c=<%= ts %>"></script>
|
||||
|
||||
<!-- begin -->
|
||||
<script>
|
||||
mocha.checkLeaks();
|
||||
mocha.slow(1000);
|
||||
mocha.timeout(11000);
|
||||
mocha.globals(['console', 'elasticsearch']);
|
||||
mocha.bail();
|
||||
/PhantomJS/i.test(navigator.userAgent) || mocha.run();
|
||||
</script>
|
||||
</body>
|
||||
42354
test/integration/browser_yaml_suite/yaml_tests.js
Normal file
42354
test/integration/browser_yaml_suite/yaml_tests.js
Normal file
File diff suppressed because one or more lines are too long
@ -37,8 +37,12 @@ module.exports = {
|
||||
} else if (externalExists === void 0) {
|
||||
doCreateClient(function () {
|
||||
client.ping(function (err) {
|
||||
externalExists = !err;
|
||||
create(cb);
|
||||
if (err instanceof es.errors.ConnectionFault) {
|
||||
externalExists = !err;
|
||||
create(done);
|
||||
} else {
|
||||
done(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
|
||||
@ -10,7 +10,7 @@ var Base = require('mocha/lib/reporters/base');
|
||||
var _ = require('lodash');
|
||||
var chalk = require('chalk');
|
||||
var clientManager = require('./client_manager');
|
||||
var makeJUnitXml = require('../../make_j_unit_xml');
|
||||
var makeJUnitXml = require('../../../scripts/make_j_unit_xml');
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
|
||||
|
||||
@ -116,7 +116,7 @@ function YamlDoc(doc, file) {
|
||||
// check that it's a function
|
||||
expect(method).to.be.a('function');
|
||||
|
||||
if (typeof action.args === 'object') {
|
||||
if (_.isPlainObject(action.args)) {
|
||||
action.name += ' ' + _.keys(action.args).join(', ');
|
||||
} else if (action.args) {
|
||||
action.name += ' ' + action.args;
|
||||
|
||||
@ -1,80 +0,0 @@
|
||||
/**
|
||||
* A mock elasticsearch server used to test network libraries quickly and easily.
|
||||
*
|
||||
* It is actually a server created using node's http.createServer method and
|
||||
* listens on a random port by default. The server emits an "online" event once
|
||||
* it is listening and ready for requests.
|
||||
*
|
||||
* @type {}
|
||||
* @class EsServer
|
||||
* @param {Object} config - An object containing config options
|
||||
*/
|
||||
module.exports = EsServer;
|
||||
|
||||
var _ = require('../../src/lib/utils');
|
||||
var url = require('url');
|
||||
var http = require('http');
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
|
||||
var pingResp = JSON.stringify({
|
||||
'ok': true,
|
||||
'status': 200,
|
||||
'name': 'Commando',
|
||||
'version': {
|
||||
'number': '0.90.5',
|
||||
'build_hash': 'c8714e8e0620b62638f660f6144831792b9dedee',
|
||||
'build_timestamp': '2013-09-17T12:50:20Z',
|
||||
'build_snapshot': false,
|
||||
'lucene_version': '4.4'
|
||||
},
|
||||
'tagline': 'You Know, for Search'
|
||||
});
|
||||
|
||||
function EsServer() {
|
||||
var self = this;
|
||||
var server = http.createServer();
|
||||
|
||||
server.on('request', function (request, response) {
|
||||
request.parsedUrl = url.parse(request.url, true);
|
||||
|
||||
var routes = self.routes[request.method.toLowerCase()];
|
||||
var route = routes && routes[request.parsedUrl.pathname];
|
||||
|
||||
if (route) {
|
||||
route.call(self, request, response);
|
||||
} else {
|
||||
response.writeHead(400);
|
||||
response.end('No handler found for uri [/] and method [' + request.method + ']');
|
||||
}
|
||||
});
|
||||
|
||||
self.shutdown = function (cb) {
|
||||
server.close(function () {
|
||||
self.emit('offline');
|
||||
cb();
|
||||
});
|
||||
};
|
||||
|
||||
self.routes = {
|
||||
get: {
|
||||
'/' : function (request, response) {
|
||||
response.writeHead(200, {
|
||||
'Content-Type': 'application/json',
|
||||
'Content-Length': pingResp.length
|
||||
});
|
||||
response.end(pingResp, 'utf8');
|
||||
}
|
||||
},
|
||||
post: {},
|
||||
put: {},
|
||||
delete: {},
|
||||
head: {}
|
||||
};
|
||||
|
||||
process.nextTick(function () {
|
||||
server.listen(0, function () {
|
||||
self.emit('online', server.address().port);
|
||||
});
|
||||
});
|
||||
}
|
||||
_.inherits(EsServer, EventEmitter);
|
||||
@ -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);
|
||||
});
|
||||
});
|
||||
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -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();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
@ -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'
|
||||
]);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
@ -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();
|
||||
}
|
||||
|
||||
};
|
||||
@ -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
35
test/unit/test_client.js
Normal 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);
|
||||
});
|
||||
});
|
||||
816
test/unit/test_client_action.js
Normal file
816
test/unit/test_client_action.js
Normal 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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
154
test/unit/test_connection_abstract.js
Normal file
154
test/unit/test_connection_abstract.js
Normal 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);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
237
test/unit/test_connection_pool.js
Normal file
237
test/unit/test_connection_pool.js
Normal 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
25
test/unit/test_errors.js
Normal 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
134
test/unit/test_host.js
Normal 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 () {
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
58
test/unit/test_http_connection.js
Normal file
58
test/unit/test_http_connection.js
Normal 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
259
test/unit/test_log.js
Normal 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);
|
||||
});
|
||||
});
|
||||
});
|
||||
19
test/unit/test_random_selector.js
Normal file
19
test/unit/test_random_selector.js
Normal 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);
|
||||
|
||||
});
|
||||
});
|
||||
16
test/unit/test_round_robin_selector.js
Normal file
16
test/unit/test_round_robin_selector.js
Normal 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);
|
||||
});
|
||||
});
|
||||
71
test/unit/test_stdio_logger.js
Normal file
71
test/unit/test_stdio_logger.js
Normal 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
294
test/unit/test_utils.js
Normal 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);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -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);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user