Add promise interface to database execution methods

This commit is contained in:
Timothy Warren 2016-01-26 19:29:12 -05:00
parent 4b24771716
commit da8b473bc7
33 changed files with 3096 additions and 2639 deletions

View File

@ -18,6 +18,7 @@ A node query builder for various SQL databases, based on CodeIgniter's query bui
### Installation ### Installation
npm install ci-node-query npm install ci-node-query
[![NPM](https://nodei.co/npm/ci-node-query.png?downloads=true&downloadRank=true&stars=true)](https://nodei.co/npm/ci-node-query/)
### Basic use ### Basic use

View File

@ -6,7 +6,7 @@
margin:0; margin:0;
} }
.force-inline p { .force-inline, .force-inline p {
display: inline; display: inline;
color: #222; color: #222;
} }

File diff suppressed because it is too large Load Diff

View File

@ -54,6 +54,13 @@ const ESLINT_SETTINGS = {
} }
}; };
const MOCHA_OPTIONS = {
ui: 'tdd',
bail: true,
reporter: 'list',
timeout: 10000,
};
gulp.task('lint', () => { gulp.task('lint', () => {
pipe(gulp.src(SRC_FILES), [ pipe(gulp.src(SRC_FILES), [
eslint(ESLINT_SETTINGS), eslint(ESLINT_SETTINGS),
@ -82,22 +89,14 @@ gulp.task('sloc', () => gulp.src(SRC_FILES).pipe(sloc()));
gulp.task('test-sloc', () => gulp.src(TEST_FILES).pipe(sloc())); gulp.task('test-sloc', () => gulp.src(TEST_FILES).pipe(sloc()));
gulp.task('docs', () => { gulp.task('docs', () => {
gulp.src('./lib/QueryBuilder.js') gulp.src(['lib/*.js'])
.pipe(documentation({format: 'html'})) .pipe(documentation({format: 'html'}))
.pipe(gulp.dest('docs')); .pipe(gulp.dest('docs'));
/*gulp.src('./lib/QueryBuilder.js')
.pipe(documentation({format: 'md'}))
.pipe(gulp.dest('api-docs'));*/
}); });
gulp.task('mocha', ['lint-tests', 'sloc'], () => { gulp.task('mocha', ['lint-tests', 'sloc'], () => {
return gulp.src(TEST_FILES) return gulp.src(TEST_FILES)
.pipe(mocha({ .pipe(mocha(MOCHA_OPTIONS))
ui: 'tdd',
bail: true,
reporter: 'list',
timeout: 5000
}))
.once('error', () => { .once('error', () => {
process.exit(1); process.exit(1);
}) })
@ -112,10 +111,7 @@ gulp.task('test', ['test-sloc', 'lint-tests'], function(cb) {
istanbul.hookRequire() istanbul.hookRequire()
]).on('finish', () => { ]).on('finish', () => {
pipe(gulp.src(TEST_FILES), [ pipe(gulp.src(TEST_FILES), [
mocha({ mocha(MOCHA_OPTIONS),
ui: 'tdd',
bail: true
}),
istanbul.writeReports({ istanbul.writeReports({
dir: './coverage', dir: './coverage',
reporters: ['lcov', 'lcovonly', 'html', 'text'] reporters: ['lcov', 'lcovonly', 'html', 'text']

View File

@ -1,12 +1,18 @@
'use strict'; 'use strict';
/** @module Adapter */ /**
module.exports = class Adapter { * Class that wraps database connection libraries
*
* @private
* @param {Object} instance - The connection object
*/
class Adapter {
/** /**
* Invoke an adapter * Invoke an adapter
* *
* @param {Object} instance - The connection objec * @constructor
* @return {void} * @param {Object} instance - The connection object
*/ */
constructor(instance) { constructor(instance) {
this.instance = instance; this.instance = instance;
@ -17,8 +23,8 @@ module.exports = class Adapter {
* *
* @param {String} sql - The sql with placeholders * @param {String} sql - The sql with placeholders
* @param {Array} params - The values to insert into the query * @param {Array} params - The values to insert into the query
* @param {Function} callback - Callback to run when a response is recieved * @param {Function} [callback] - Callback to run when a response is recieved
* @return {void} * @return {void|Promise} - returns a promise if no callback is passed
*/ */
execute(/*sql, params, callback*/) { execute(/*sql, params, callback*/) {
throw new Error('Correct adapter not defined for query execution'); throw new Error('Correct adapter not defined for query execution');
@ -31,4 +37,6 @@ module.exports = class Adapter {
close() { close() {
this.instance.end(); this.instance.end();
} }
}; }
module.exports = Adapter;

View File

@ -1,13 +1,13 @@
'use strict'; 'use strict';
let helpers = require('./helpers'); const helpers = require('./helpers');
/** /**
* Base Database Driver * Base Database Driver
* *
* @module driver * @private
*/ */
let d = { let Driver = {
identifierStartChar: '"', identifierStartChar: '"',
identifierEndChar: '"', identifierEndChar: '"',
tablePrefix: null, tablePrefix: null,
@ -15,14 +15,14 @@ let d = {
/** /**
* Low level function for naive quoting of strings * Low level function for naive quoting of strings
*
* @param {String} str - The sql fragment to quote * @param {String} str - The sql fragment to quote
* @return {String} - The quoted sql fragment * @return {String} - The quoted sql fragment
* @private * @private
*/ */
_quote(str) { _quote(str) {
return (helpers.isString(str) && ! (str.startsWith(d.identifierStartChar) || str.endsWith(d.identifierEndChar))) return (helpers.isString(str) && ! (str.startsWith(Driver.identifierStartChar) || str.endsWith(Driver.identifierEndChar)))
? `${d.identifierStartChar}${str}${d.identifierEndChar}` ? `${Driver.identifierStartChar}${str}${Driver.identifierEndChar}`
: str; : str;
}, },
@ -31,7 +31,7 @@ let d = {
* @param {String} sql - SQL statement to modify * @param {String} sql - SQL statement to modify
* @param {Number} limit - Maximum number of rows to fetch * @param {Number} limit - Maximum number of rows to fetch
* @param {Number|null} offset - Number of rows to skip * @param {Number} [offset] - Number of rows to skip
* @return {String} - Modified SQL statement * @return {String} - Modified SQL statement
*/ */
limit(sql, limit, offset) { limit(sql, limit, offset) {
@ -53,37 +53,37 @@ let d = {
*/ */
quoteTable(table) { quoteTable(table) {
// Quote after prefix // Quote after prefix
return d.quoteIdentifiers(table); return Driver.quoteIdentifiers(table);
}, },
/** /**
* Use the driver's escape character to quote identifiers * Use the driver's escape character to quote identifiers
* *
* @param {String|String[]} - String or array of strings to quote identifiers * @param {String|Array} str - String or array of strings to quote identifiers
* @return {String|String[]} - Quoted identifier(s) * @return {String|Array} - Quoted identifier(s)
*/ */
quoteIdentifiers(str) { quoteIdentifiers(str) {
let hiers, raw; let hiers, raw;
let pattern = new RegExp( let pattern = new RegExp(
`${d.identifierStartChar}(` `${Driver.identifierStartChar}(`
+ '([a-zA-Z0-9_]+)' + '(\((.*?)\))' + '([a-zA-Z0-9_]+)' + '(\((.*?)\))'
+ `)${d.identifierEndChar}`, 'ig'); + `)${Driver.identifierEndChar}`, 'ig');
// Recurse for arrays of identifiiers // Recurse for arrays of identifiiers
if (Array.isArray(str)) if (Array.isArray(str))
{ {
return str.map(d.quoteIdentifiers); return str.map(Driver.quoteIdentifiers);
} }
// Handle commas // Handle commas
if (str.includes(',')) if (str.includes(','))
{ {
let parts = str.split(',').map(helpers.stringTrim); let parts = str.split(',').map(helpers.stringTrim);
str = parts.map(d.quoteIdentifiers).join(','); str = parts.map(Driver.quoteIdentifiers).join(',');
} }
// Split identifiers by period // Split identifiers by period
hiers = str.split('.').map(d._quote); hiers = str.split('.').map(Driver._quote);
raw = hiers.join('.'); raw = hiers.join('.');
// Fix functions // Fix functions
@ -96,30 +96,30 @@ let d = {
// Quote the identifiers inside of the parens // Quote the identifiers inside of the parens
let inParens = funcs[3].substring(1, funcs[3].length - 1); let inParens = funcs[3].substring(1, funcs[3].length - 1);
raw = raw.replace(inParens, d.quoteIdentifiers(inParens)); raw = raw.replace(inParens, Driver.quoteIdentifiers(inParens));
} }
return raw; return raw;
}, },
/** /**
* SQL to truncate the passed table * Generate SQL to truncate the passed table
* *
* @param {String} table - Table to truncate * @param {String} table - Table to truncate
* @return {String} - Truncation SQL * @return {String} - Truncation SQL
*/ */
truncate(table) { truncate(table) {
let sql = (d.hasTruncate) let sql = (Driver.hasTruncate)
? 'TRUNCATE ' ? 'TRUNCATE '
: 'DELETE FROM '; : 'DELETE FROM ';
sql += d.quoteTable(table); sql += Driver.quoteTable(table);
return sql; return sql;
}, },
/** /**
* SQL to insert a group of rows * Generate SQL to insert a group of rows
* *
* @param {String} table - The table to insert to * @param {String} table - The table to insert to
* @param {Array} [data] - The array of object containing data to insert * @param {Array} [data] - The array of object containing data to insert
@ -143,9 +143,9 @@ let d = {
// Get the field names from the keys of the first // Get the field names from the keys of the first
// object inserted // object inserted
table = d.quoteTable(table); table = Driver.quoteTable(table);
sql += `INSERT INTO ${table} (${d.quoteIdentifiers(fields).join(',')}) VALUES `; sql += `INSERT INTO ${table} (${Driver.quoteIdentifiers(fields).join(',')}) VALUES `;
// Create placeholder groups // Create placeholder groups
params = Array(fields.length).fill('?'); params = Array(fields.length).fill('?');
@ -161,4 +161,4 @@ let d = {
}, },
}; };
module.exports = d; module.exports = Driver;

View File

@ -5,14 +5,15 @@ let fs = require('fs'),
QueryBuilder = require('./QueryBuilder'); QueryBuilder = require('./QueryBuilder');
/** /**
* @module NodeQuery * Class for connection management
*/ */
class NodeQuery { class NodeQuery {
/** /**
* Constructor * Constructor
* *
* @return {void} * @private
* @constructor
*/ */
constructor() { constructor() {
this.instance = null; this.instance = null;
@ -23,7 +24,7 @@ class NodeQuery {
* *
* @param {String} driverType - The name of the database type, eg. mysql or pg * @param {String} driverType - The name of the database type, eg. mysql or pg
* @param {Object} connObject - A connection object from the database library you are connecting with * @param {Object} connObject - A connection object from the database library you are connecting with
* @param {String} [connLib] - The name of the db connection library you are using, eg. mysql or mysql2. Optional if the same as drivername * @param {String} [connLib] - The name of the db connection library you are using, eg. mysql or mysql2. Optional if the same as driverType
* @return {QueryBuilder} - The Query Builder object * @return {QueryBuilder} - The Query Builder object
*/ */
init(driverType, connObject, connLib) { init(driverType, connObject, connLib) {

View File

@ -1,23 +1,26 @@
'use strict'; 'use strict';
/** @module QueryBuilder */
let getArgs = require('getargs'), let getArgs = require('getargs'),
helpers = require('./helpers'), helpers = require('./helpers'),
State = require('./State'), State = require('./State'),
QueryParser = require('./QueryParser'); QueryParser = require('./QueryParser');
module.exports = class QueryBuilder { /**
/* * Main object that builds SQL queries.
* SQL generation object *
* * @param {Driver} Driver - The syntax driver for the database
* @param {driver} - The syntax driver for the database * @param {Adapter} Adapter - The database module adapter for running queries
* @param {adapter} - The database module adapter for running queries */
* @return {QueryBuilder} - The Query Builder object, for chaining class QueryBuilder {
/**
* @private
* @constructor * @constructor
* @param {Driver} Driver - The syntax driver for the database
* @param {Adapter} Adapter - The database module adapter for running queries
*/ */
constructor(driver, adapter) { constructor(Driver, Adapter) {
this.driver = driver; this.driver = Driver;
this.adapter = adapter; this.adapter = Adapter;
this.parser = new QueryParser(this.driver); this.parser = new QueryParser(this.driver);
this.state = new State(); this.state = new State();
} }
@ -265,8 +268,11 @@ module.exports = class QueryBuilder {
this._resetState(); this._resetState();
// Pass the sql and values to the adapter to run on the database // Pass the sql and values to the adapter to run on the database
this.adapter.execute(sql, vals, callback); if (callback) {
return this.adapter.execute(sql, vals, callback);
} else {
return this.adapter.execute(sql, vals);
}
} }
_getCompile(type, table, reset) { _getCompile(type, table, reset) {
@ -743,11 +749,11 @@ module.exports = class QueryBuilder {
* @param {String} [table] - The table to select from * @param {String} [table] - The table to select from
* @param {Number} [limit] - A limit for the query * @param {Number} [limit] - A limit for the query
* @param {Number} [offset] - An offset for the query * @param {Number} [offset] - An offset for the query
* @param {Function} callback - A callback for receiving the result * @param {Function} [callback] - A callback for receiving the result
* @return {void} * @return {void|Promise} - If no callback is passed, a promise is returned
*/ */
get(/* [table], [limit], [offset], callback */) { get(/* [table], [limit], [offset], [callback] */) {
let args = getArgs('[table]:string, [limit]:number, [offset]:number, callback:function', arguments); let args = getArgs('[table]:string, [limit]:number, [offset]:number, [callback]:function', arguments);
if (args.table) { if (args.table) {
this.from(args.table); this.from(args.table);
@ -758,7 +764,7 @@ module.exports = class QueryBuilder {
} }
// Run the query // Run the query
this._run('get', args.table, args.callback); return this._run('get', args.table, args.callback);
} }
/** /**
@ -766,18 +772,18 @@ module.exports = class QueryBuilder {
* *
* @param {String} table - The table to insert into * @param {String} table - The table to insert into
* @param {Object} [data] - Data to insert, if not already added with the 'set' method * @param {Object} [data] - Data to insert, if not already added with the 'set' method
* @param {Function} callback - Callback for handling response from the database * @param {Function} [callback] - Callback for handling response from the database
* @return {void} * @return {void|Promise} - If no callback is passed, a promise is returned
*/ */
insert(/* table, data, callback */) { insert(/* table, data, callback */) {
let args = getArgs('table:string, [data]:object, callback:function', arguments); let args = getArgs('table:string, [data]:object, [callback]:function', arguments);
if (args.data) { if (args.data) {
this.set(args.data); this.set(args.data);
} }
// Run the query // Run the query
this._run('insert', this.driver.quoteTable(args.table), args.callback); return this._run('insert', this.driver.quoteTable(args.table), args.callback);
} }
/** /**
@ -785,16 +791,16 @@ module.exports = class QueryBuilder {
* *
* @param {String} table - The table to insert into * @param {String} table - The table to insert into
* @param {Array} data - The array of objects containing data rows to insert * @param {Array} data - The array of objects containing data rows to insert
* @param {Function} callback - Callback for handling database response * @param {Function} [callback] - Callback for handling database response
* @example query.insertBatch('foo',[{id:1,val:'bar'},{id:2,val:'baz'}], callbackFunction); * @example query.insertBatch('foo',[{id:1,val:'bar'},{id:2,val:'baz'}], callbackFunction);
* @return {void} * @return {void|Promise} - If no callback is passed, a promise is returned
*/ */
insertBatch(/* table, data, callback */) { insertBatch(/* table, data, callback */) {
let args = getArgs('table:string, data:array, callback:function', arguments); let args = getArgs('table:string, data:array, [callback]:function', arguments);
let batch = this.driver.insertBatch(args.table, args.data); let batch = this.driver.insertBatch(args.table, args.data);
// Run the query // Run the query
this._run('', '', args.callback, batch.sql, batch.values); return this._run('', '', args.callback, batch.sql, batch.values);
} }
/** /**
@ -802,18 +808,18 @@ module.exports = class QueryBuilder {
* *
* @param {String} table - The table to insert into * @param {String} table - The table to insert into
* @param {Object} [data] - Data to insert, if not already added with the 'set' method * @param {Object} [data] - Data to insert, if not already added with the 'set' method
* @param {Function} callback - Callback for handling response from the database * @param {Function} [callback] - Callback for handling response from the database
* @return {void} * @return {void|Promise} - If no callback is passed, a promise is returned
*/ */
update(/*table, data, callback*/) { update(/*table, data, callback*/) {
let args = getArgs('table:string, [data]:object, callback:function', arguments); let args = getArgs('table:string, [data]:object, [callback]:function', arguments);
if (args.data) { if (args.data) {
this.set(args.data); this.set(args.data);
} }
// Run the query // Run the query
this._run('update', this.driver.quoteTable(args.table), args.callback); return this._run('update', this.driver.quoteTable(args.table), args.callback);
} }
/** /**
@ -821,18 +827,18 @@ module.exports = class QueryBuilder {
* *
* @param {String} table - The table to insert into * @param {String} table - The table to insert into
* @param {Object} [where] - Where clause for delete statement * @param {Object} [where] - Where clause for delete statement
* @param {Function} callback - Callback for handling response from the database * @param {Function} [callback] - Callback for handling response from the database
* @return {void} * @return {void|Promise} - If no callback is passed, a promise is returned
*/ */
delete(/*table, [where], callback*/) { delete(/*table, [where], callback*/) {
let args = getArgs('table:string, [where]:object, callback:function', arguments); let args = getArgs('table:string, [where]:object, [callback]:function', arguments);
if (args.where) { if (args.where) {
this.where(args.where); this.where(args.where);
} }
// Run the query // Run the query
this._run('delete', this.driver.quoteTable(args.table), args.callback); return this._run('delete', this.driver.quoteTable(args.table), args.callback);
} }
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
@ -887,4 +893,6 @@ module.exports = class QueryBuilder {
getCompiledDelete(table, reset) { getCompiledDelete(table, reset) {
return this._getCompile('delete', this.driver.quoteTable(table), reset); return this._getCompile('delete', this.driver.quoteTable(table), reset);
} }
}; }
module.exports = QueryBuilder;

View File

@ -4,7 +4,13 @@ let helpers = require('./helpers');
// -------------------------------------------------------------------------- // --------------------------------------------------------------------------
module.exports = class QueryParser { /**
* Internal object for parsing query fragments
*
* @private
* @param {Driver} driver - The driver object for the database in use
*/
class QueryParser {
/** /**
* @constructor * @constructor
* *
@ -237,4 +243,6 @@ module.exports = class QueryParser {
return state; return state;
} }
}; }
module.exports = QueryParser;

View File

@ -1,7 +1,10 @@
'use strict'; 'use strict';
/** @module State */ /**
module.exports = class State { * Class for objects containing the query builder state
* @private
*/
class State {
constructor() { constructor() {
// Arrays/maps // Arrays/maps
this.queryMap = []; this.queryMap = [];
@ -25,6 +28,8 @@ module.exports = class State {
this.limit = null; this.limit = null;
this.offset = null; this.offset = null;
} }
}; }
module.exports = State;
// End of module State // End of module State

View File

@ -1,7 +1,8 @@
'use strict'; 'use strict';
let Adapter = require('../Adapter'), let Adapter = require('../Adapter'),
getArgs = require('getargs'); getArgs = require('getargs'),
Promise = require('bluebird');
module.exports = class dblite extends Adapter { module.exports = class dblite extends Adapter {
/** /**
@ -9,16 +10,23 @@ module.exports = class dblite extends Adapter {
* *
* @param {String} sql - The sql with placeholders * @param {String} sql - The sql with placeholders
* @param {Array} params - The values to insert into the query * @param {Array} params - The values to insert into the query
* @param {Function} callback - Callback to run when a response is recieved * @param {Function} [callback] - Callback to run when a response is recieved
* @return {void} * @return {void|Promise} - Returns a promise if no callback is provided
*/ */
execute(/*sql, params, callback*/) { execute(/*sql, params, callback*/) {
let args = getArgs('sql:string, [params]:array, callback:function', arguments); let args = getArgs('sql:string, [params]:array, [callback]:function', arguments);
this.instance.query(args.sql, args.params, args.callback); let instance = Promise.promisifyAll(this.instance);
if (! args.callback) {
return instance.queryAsync(args.sql, args.params);
}
return this.instance.query(args.sql, args.params, args.callback);
} }
/** /**
* Close the current database connection * Close the current database connection
* @return {void} * @return {void}
*/ */
close() { close() {

View File

@ -1,7 +1,8 @@
'use strict'; 'use strict';
let Adapter = require('../Adapter'), let Adapter = require('../Adapter'),
getArgs = require('getargs'); getArgs = require('getargs'),
Promise = require('bluebird');
module.exports = class mysql extends Adapter { module.exports = class mysql extends Adapter {
/** /**
@ -9,11 +10,17 @@ module.exports = class mysql extends Adapter {
* *
* @param {String} sql - The sql with placeholders * @param {String} sql - The sql with placeholders
* @param {Array} params - The values to insert into the query * @param {Array} params - The values to insert into the query
* @param {Function} callback - Callback to run when a response is recieved * @param {Function} [callback] - Callback to run when a response is recieved
* @return {void} * @return {void|Promise} - Returns a promise if no callback is provided
*/ */
execute(sql, params, callback) { execute(sql, params, callback) {
let args = getArgs('sql:string, [params], callback:function', arguments); let args = getArgs('sql:string, [params], [callback]:function', arguments);
let instance = Promise.promisifyAll(this.instance);
if (! args.callback) {
return instance.queryAsync(args.sql, args.params);
}
return this.instance.query(args.sql, args.params, args.callback); return this.instance.query(args.sql, args.params, args.callback);
} }
}; };

View File

@ -1,7 +1,8 @@
'use strict'; 'use strict';
let Adapter = require('../Adapter'), let Adapter = require('../Adapter'),
getArgs = require('getargs'); getArgs = require('getargs'),
Promise = require('bluebird');
module.exports = class mysql2 extends Adapter { module.exports = class mysql2 extends Adapter {
/** /**
@ -9,11 +10,17 @@ module.exports = class mysql2 extends Adapter {
* *
* @param {String} sql - The sql with placeholders * @param {String} sql - The sql with placeholders
* @param {Array} params - The values to insert into the query * @param {Array} params - The values to insert into the query
* @param {Function} callback - Callback to run when a response is recieved * @param {Function} [callback] - Callback to run when a response is recieved
* @return {void} * @return {void|Promise} - Returns a promise if no callback is provided
*/ */
execute(/*sql, params, callback*/) { execute(/*sql, params, callback*/) {
let args = getArgs('sql:string, [params], callback:function', arguments); let args = getArgs('sql:string, [params], [callback]:function', arguments);
let instance = Promise.promisifyAll(this.instance);
if (! args.callback) {
return instance.executeAsync(args.sql, args.params);
}
return this.instance.execute(args.sql, args.params, args.callback); return this.instance.execute(args.sql, args.params, args.callback);
} }
}; };

View File

@ -1,7 +1,8 @@
'use strict'; 'use strict';
let Adapter = require('../Adapter'), let Adapter = require('../Adapter'),
getArgs = require('getargs'); getArgs = require('getargs'),
Promise = require('bluebird');
module.exports = class nodefirebird extends Adapter { module.exports = class nodefirebird extends Adapter {
/** /**
@ -9,11 +10,25 @@ module.exports = class nodefirebird extends Adapter {
* *
* @param {String} sql - The sql with placeholders * @param {String} sql - The sql with placeholders
* @param {Array} params - The values to insert into the query * @param {Array} params - The values to insert into the query
* @param {Function} callback - Callback to run when a response is recieved * @param {Function} [callback] - Callback to run when a response is recieved
* @return {void} * @return {void|Promise} - Returns a promise if no callback is provided
*/ */
execute(/*sql, params, callback*/) { execute(/*sql, params, callback*/) {
let args = getArgs('sql:string, [params], callback:function', arguments); let args = getArgs('sql:string, [params], [callback]:function', arguments);
return this.instance.execute(args.sql, args.params, args.callback); let instance = Promise.promisifyAll(this.instance);
if (! args.callback) {
return instance.queryAsync(args.sql, args.params);
}
return this.instance.query(args.sql, args.params, args.callback);
}
/**
* Close the current database connection
* @return {void}
*/
close() {
this.instance.detach();
} }
}; };

View File

@ -1,7 +1,8 @@
'use strict'; 'use strict';
let Adapter = require('../Adapter'), let Adapter = require('../Adapter'),
getArgs = require('getargs'); getArgs = require('getargs'),
Promise = require('bluebird');
module.exports = class pg extends Adapter { module.exports = class pg extends Adapter {
/** /**
@ -9,11 +10,12 @@ module.exports = class pg extends Adapter {
* *
* @param {String} sql - The sql with placeholders * @param {String} sql - The sql with placeholders
* @param {Array} params - The values to insert into the query * @param {Array} params - The values to insert into the query
* @param {Function} callback - Callback to run when a response is recieved * @param {Function} [callback] - Callback to run when a response is recieved
* @return {void} * @return {void|Promise} - Returns a promise if no callback is provided
*/ */
execute(/*sql, params, callback*/) { execute(/*sql, params, callback*/) {
let args = getArgs('sql:string, [params]:array, callback:function', arguments); let args = getArgs('sql:string, [params]:array, [callback]:function', arguments);
let instance = Promise.promisifyAll(this.instance);
// Replace question marks with numbered placeholders, because this adapter is different... // Replace question marks with numbered placeholders, because this adapter is different...
let count = 0; let count = 0;
@ -22,6 +24,10 @@ module.exports = class pg extends Adapter {
return `$${count}`; return `$${count}`;
}); });
this.instance.query(args.sql, args.params, args.callback); if (! args.callback) {
return instance.queryAsync(args.sql, args.params);
}
return this.instance.query(args.sql, args.params, args.callback);
} }
}; };

View File

@ -1,5 +1,10 @@
'use strict'; 'use strict';
/**
* Various internal helper functions
*
* @private
*/
let helpers = { let helpers = {
/** /**
* Wrap String.prototype.trim in a way that is easily mappable * Wrap String.prototype.trim in a way that is easily mappable
@ -124,9 +129,9 @@ types.forEach(t => {
* *
* Types available are Null, Undefined, Object, Array, String, Number, Boolean, Function, RegExp, NaN and Infinite * Types available are Null, Undefined, Object, Array, String, Number, Boolean, Function, RegExp, NaN and Infinite
* *
* @name is[type] * @private
* @param {mixed} o * @param {mixed} o - The object to check its type
* @return {Boolean} * @return {Boolean} - If the type matches
*/ */
helpers[`is${t}`] = function(o) { helpers[`is${t}`] = function(o) {
if (t.toLowerCase() === 'infinite') { if (t.toLowerCase() === 'infinite') {

View File

@ -1,11 +1,14 @@
{ {
"name": "ci-node-query", "name": "ci-node-query",
"version": "3.0.1", "version": "3.1.0",
"description": "A query builder for node based on the one in CodeIgniter", "description": "A query builder for node based on the one in CodeIgniter",
"author": "Timothy J Warren <tim@timshomepage.net>", "author": "Timothy J Warren <tim@timshomepage.net>",
"engines": { "engines": {
"node": ">=4.0.0" "node": ">=4.0.0"
}, },
"files": [
"lib/"
],
"contributors": [ "contributors": [
{ {
"name": "Timothy J Warren", "name": "Timothy J Warren",
@ -33,32 +36,30 @@
}, },
"main": "lib/NodeQuery.js", "main": "lib/NodeQuery.js",
"dependencies": { "dependencies": {
"getargs": "", "bluebird": "^3.1.4",
"mysql": "^2.9.0", "dblite": "~0.7.6",
"mysql2": "^0.15.8", "getargs": "~0.0.8",
"node-firebird": "^0.7.0", "mysql": "~2.10.2",
"pg": "^4.4.3", "mysql2": "~0.15.8",
"require-reload": "*" "node-firebird": "~0.7.2",
}, "pg": "~4.4.3",
"optionalDependencies": { "require-reload": "~0.2.2"
"dblite": "*",
"node-firebird": "*",
"pg": "*"
}, },
"devDependencies": { "devDependencies": {
"chai": "", "chai": "~3.4.1",
"chai-as-promised": "^5.2.0",
"documentation": "", "documentation": "",
"eslint": "", "eslint": "~1.10.3",
"glob": "^6.0.1", "glob": "~6.0.4",
"gulp": "", "gulp": "~3.9.0",
"gulp-documentation": "^2.1.0", "gulp-documentation": "~2.1.0",
"gulp-eslint": "", "gulp-eslint": "~1.1.1",
"gulp-istanbul": "^0.10.3", "gulp-istanbul": "^0.10.3",
"gulp-jscs": "^3.0.2", "gulp-jscs": "^3.0.2",
"gulp-mocha": "^2.2.0", "gulp-mocha": "^2.2.0",
"gulp-pipe": "^1.0.4", "gulp-pipe": "^1.0.4",
"gulp-sloc": "", "gulp-sloc": "~1.0.4",
"istanbul": "", "istanbul": "~0.4.2",
"mocha": "" "mocha": ""
}, },
"license": "MIT", "license": "MIT",

View File

@ -1,479 +0,0 @@
'use strict';
module.exports.tests = {
'Get tests': {
'Get with function': {
select: ['id, COUNT(id) as count'],
from: ['create_test'],
groupBy: ['id'],
get: [],
},
'Basic select all get': {
get: ['create_test'],
},
'Basic select all with from': {
from: ['create_test'],
get: [],
},
'Get with limit': {
get: ['create_test', 2],
},
'Get with limit and offset': {
get: ['create_test', 2, 1],
},
'Get with having': {
select: ['id'],
from: ['create_test'],
groupBy: ['id'],
having: [
'multiple',
[{'id >': 1}],
['id !=', 3],
['id', 900],
],
get: [],
},
'Get with orHaving': {
select: ['id'],
from: ['create_test'],
groupBy: ['id'],
having: [{'id >': 1}],
orHaving: ['id !=', 3],
get: [],
},
},
'Select tests': {
'Select where get': {
select: [['id', 'key as k', 'val']],
where: [
'multiple',
['id >', 1],
['id <', 900],
],
get: ['create_test', 2, 1],
},
'Select where get 2': {
select: ['id, key as k, val'],
where: ['id !=', 1],
get: ['create_test', 2, 1],
},
'Multi Order By': {
from: ['create_test'],
orderBy: ['id, key'],
get: [],
},
'Select get': {
select: ['id, key as k, val'],
get: ['create_test', 2, 1],
},
'Select from get': {
select: ['id, key as k, val'],
from: ['create_test ct'],
where: ['id >', 1],
get: [],
},
'Select from limit get': {
select: ['id, key as k, val'],
from: ['create_test ct'],
where: ['id >', 1],
limit: [3],
get: [],
},
'Select where IS NOT NULL': {
select: ['id', 'key as k', 'val'],
from: ['create_test ct'],
whereIsNotNull: ['id'],
get: [],
},
'Select where IS NULL': {
select: ['id', 'key as k', 'val'],
from: ['create_test ct'],
whereIsNull: ['id'],
get: [],
},
'Select where OR IS NOT NULL': {
select: ['id', 'key as k', 'val'],
from: ['create_test ct'],
whereIsNull: ['id'],
orWhereIsNotNull: ['id'],
get: [],
},
'Select where OR IS NULL': {
select: ['id', 'key as k', 'val'],
from: ['create_test ct'],
where: ['id', 3],
orWhereIsNull: ['id'],
get: [],
},
'Select with string where value': {
select: ['id', 'key as k', 'val'],
from: ['create_test ct'],
where: ['id > 3'],
get: [],
},
'Select with function and argument in WHERE clause': {
select: ['id'],
from: ['create_test ct'],
where: ['id', 'CEILING(SQRT(88))'],
get: [],
},
},
'Where in tests': {
'Where in': {
from: ['create_test'],
whereIn: ['id', [0, 6, 56, 563, 341]],
get: [],
},
'Or Where in': {
from: ['create_test'],
where: ['key', 'false'],
orWhereIn: ['id', [0, 6, 56, 563, 341]],
get: [],
},
'Where Not in': {
from: ['create_test'],
where: ['key', 'false'],
whereNotIn: ['id', [0, 6, 56, 563, 341]],
get: [],
},
'Or Where Not in': {
from: ['create_test'],
where: ['key', 'false'],
orWhereNotIn: ['id', [0, 6, 56, 563, 341]],
get: [],
},
},
'Query modifier tests': {
'Order By': {
select: ['id, key as k, val'],
from: ['create_test'],
where: [
'multiple',
['id >', 0],
['id <', 9000],
],
orderBy: [
'multiple',
['id', 'DESC'],
['k', 'ASC'],
],
limit: [5, 2],
get: [],
},
'Group By': {
select: ['id, key as k, val'],
from: ['create_test'],
where: [
'multiple',
['id >', 0],
['id <', 9000],
],
groupBy: [
'multiple',
['k'],
[['id', 'val']],
],
orderBy: [
'multiple',
['id', 'DESC'],
['k', 'ASC'],
],
limit: [5, 2],
get: [],
},
'Or Where': {
select: ['id, key as k, val'],
from: ['create_test'],
where: [' id ', 1],
orWhere: ['key > ', 0],
limit: [2, 1],
get: [],
},
Like: {
from: ['create_test'],
like: ['key', 'og'],
get: [],
},
'Or Like': {
from: ['create_test'],
like: ['key', 'og'],
orLike: ['key', 'val'],
get: [],
},
'Not Like': {
from: ['create_test'],
like: ['key', 'og', 'before'],
notLike: ['key', 'val'],
get: [],
},
'Or Not Like': {
from: ['create_test'],
like: ['key', 'og', 'before'],
orNotLike: ['key', 'val'],
get: [],
},
'Like Before': {
from: ['create_test'],
like: ['key', 'og', 'before'],
get: [],
},
'Like After': {
from: ['create_test'],
like: ['key', 'og', 'after'],
get: [],
},
'Basic Join': {
from: ['create_test ct'],
join: ['create_join cj', 'cj.id=ct.id'],
get: [],
},
'Left Join': {
from: ['create_test ct'],
join: ['create_join cj', 'cj.id=ct.id', 'left'],
get: [],
},
'Inner Join': {
from: ['create_test ct'],
join: ['create_join cj', 'cj.id=ct.id', 'inner'],
get: [],
},
'Join with multiple where values': {
from: ['create_test ct'],
join: ['create_join cj', 'cj.id=ct.id', 'inner'],
where: [
{
'ct.id < ': 3,
'ct.key ': 'foo',
},
],
get: [],
},
},
};
let expect = require('chai').expect,
helpers = require('../../lib/helpers'),
State = require('../../lib/State');
module.exports.runner = (tests, qb, callback) => {
Object.keys(tests).forEach(suiteName => {
suite(suiteName, () => {
let currentSuite = tests[suiteName];
Object.keys(currentSuite).forEach(testDesc => {
test(testDesc, done => {
let methodObj = currentSuite[testDesc];
let methodNames = Object.keys(methodObj);
let lastMethodIndex = methodNames[methodNames.length - 1];
methodObj[lastMethodIndex].push((err, rows) => {
callback(err, done);
});
methodNames.forEach(name => {
let args = methodObj[name],
method = qb[name];
if (args[0] === 'multiple') {
args.shift();
args.forEach(argSet => {
method.apply(qb, argSet);
});
} else {
method.apply(qb, args);
}
});
});
});
});
});
suite('DB update tests', () => {
setup(done => {
let sql = qb.driver.truncate('create_test');
qb.adapter.execute(sql, (err, res) => {
done();
});
});
test('Test Insert', done => {
qb.set('id', 98)
.set('key', '84')
.set('val', new Buffer('120'))
.insert('create_test', (err, rows) => {
return callback(err, done);
});
});
test('Test Insert Object', done => {
qb.insert('create_test', {
id: 587,
key: 1,
val: new Buffer('2'),
}, (err, rows) => {
return callback(err, done);
});
});
test('Test Update', done => {
qb.where('id', 7)
.update('create_test', {
id: 7,
key: 'gogle',
val: new Buffer('non-word'),
}, (err, rows) => {
return callback(err, done);
});
});
test('Test set Array Update', done => {
let object = {
id: 22,
key: 'gogle',
val: new Buffer('non-word'),
};
qb.set(object)
.where('id', 22)
.update('create_test', (err, rows) => {
return callback(err, done);
});
});
test('Test where set update', done => {
qb.where('id', 36)
.set('id', 36)
.set('key', 'gogle')
.set('val', new Buffer('non-word'))
.update('create_test', (err, rows) => {
return callback(err, done);
});
});
test('Test delete', done => {
qb.delete('create_test', {id: 5}, (err, rows) => {
return callback(err, done);
});
});
test('Delete with where', done => {
qb.where('id', 5)
.delete('create_test', (err, rows) => {
return callback(err, done);
});
});
test('Delete multiple where values', done => {
qb.delete('create_test', {
id: 5,
key: 'gogle',
}, (err, rows) => {
return callback(err, done);
});
});
});
suite('Grouping tests', () => {
test('Using grouping method', done => {
qb.select('id, key as k, val')
.from('create_test')
.groupStart()
.where('id >', 1)
.where('id <', 900)
.groupEnd()
.limit(2, 1)
.get((err, rows) => {
return callback(err, done);
});
});
test('Using where first grouping', done => {
qb.select('id, key as k, val')
.from('create_test')
.where('id !=', 5)
.groupStart()
.where('id >', 1)
.where('id <', 900)
.groupEnd()
.limit(2, 1)
.get((err, rows) => {
return callback(err, done);
});
});
test('Using or grouping method', done => {
qb.select('id, key as k, val')
.from('create_test')
.groupStart()
.where('id >', 1)
.where('id <', 900)
.groupEnd()
.orGroupStart()
.where('id', 0)
.groupEnd()
.limit(2, 1)
.get((err, rows) => {
return callback(err, done);
});
});
test('Using or not grouping method', done => {
qb.select('id, key as k, val')
.from('create_test')
.groupStart()
.where('id >', 1)
.where('id <', 900)
.groupEnd()
.orNotGroupStart()
.where('id', 0)
.groupEnd()
.limit(2, 1)
.get((err, rows) => {
return callback(err, done);
});
});
});
suite('Get compiled tests', () => {
test('select', () => {
let sql = qb.select('id')
.from('create_test')
.getCompiledSelect(true);
expect(helpers.isString(sql)).to.be.true;
});
test('select from', () => {
let sql = qb.select('id')
.getCompiledSelect('create_test', true);
expect(helpers.isString(sql)).to.be.true;
});
test('insert', () => {
let sql = qb.set('id', 3)
.getCompiledInsert('create_test');
expect(helpers.isString(sql)).to.be.true;
});
test('update', () => {
let sql = qb.set('id', 3)
.where('id', 5)
.getCompiledUpdate('create_test');
expect(helpers.isString(sql)).to.be.true;
});
test('delete', () => {
let sql = qb.where('id', 5)
.getCompiledDelete('create_test');
expect(helpers.isString(sql)).to.be.true;
});
});
suite('Misc tests', () => {
test('Get State', () => {
qb.select('foo')
.from('bar')
.where('baz', 'foobar');
let state = new State();
expect(JSON.stringify(state)).to.not.be.deep.equal(qb.getState());
});
test('Reset State', () => {
qb.select('foo')
.from('bar')
.where('baz', 'foobar');
qb.resetQuery();
let state = new State();
expect(qb.getState()).to.be.deep.equal(state);
});
});
};

View File

@ -1,11 +1,14 @@
'use strict'; 'use strict';
// Load the test base // Load the test base
let reload = require('require-reload')(require); const reload = require('require-reload')(require);
let getArgs = require('getargs'); reload.emptyCache();
let expect = require('chai').expect; const testBase = reload('../base');
let tests = reload('./adapterTestBase').tests; const expect = testBase.expect,
let testRunner = reload('./adapterTestBase').runner; promiseTestRunner = testBase.promiseTestRunner,
testRunner = testBase.testRunner;
let tests = reload('../base/tests');
// Load the test config file // Load the test config file
let adapterName = 'dblite'; let adapterName = 'dblite';
@ -25,53 +28,73 @@ if (connection) {
let nodeQuery = require('../../lib/NodeQuery'); let nodeQuery = require('../../lib/NodeQuery');
let qb = nodeQuery.init('sqlite', connection, adapterName); let qb = nodeQuery.init('sqlite', connection, adapterName);
// Add a test for this adapter suite('Dblite adapter tests -', () => {
tests['Select tests']['Select with function and argument in WHERE clause'] = { suiteSetup(done => {
select: ['id'],
from: ['create_test'],
where: ['id', 'ABS(-88)'],
get: [],
};
suite('Dblite adapter tests', () => {
suiteSetup(() => {
// Set up the sqlite database // Set up the sqlite database
let sql = 'CREATE TABLE IF NOT EXISTS "create_test" ("id" INTEGER PRIMARY KEY, "key" TEXT, "val" TEXT);' + let sql = 'CREATE TABLE IF NOT EXISTS "create_test" ("id" INTEGER PRIMARY KEY, "key" TEXT, "val" TEXT);' +
'CREATE TABLE IF NOT EXISTS "create_join" ("id" INTEGER PRIMARY KEY, "key" TEXT, "val" TEXT);'; 'CREATE TABLE IF NOT EXISTS "create_join" ("id" INTEGER PRIMARY KEY, "key" TEXT, "val" TEXT);';
connection.query(sql); connection.query(sql, () => {
return done();
});
}); });
testRunner(tests, qb, (err, done) => { test('nodeQuery.getQuery = nodeQuery.init', () => {
expect(nodeQuery.getQuery())
.to.be.deep.equal(qb);
});
/*---------------------------------------------------------------------------
Callback Tests
---------------------------------------------------------------------------*/
testRunner(qb, (err, done) => {
expect(err).is.not.ok; expect(err).is.not.ok;
done(); done();
}); });
suite('Adapter-specific tests', () => { test('Callback - Select with function and argument in WHERE clause', done => {
test('nodeQuery.getQuery = nodeQuery.init', () => { qb.select('id')
expect(nodeQuery.getQuery()) .from('create_test')
.to.be.deep.equal(qb); .where('id', 'ABS(-88)')
}); .get((err, rows) => {
test('Test Insert Batch', done => {
let data = [
{
id: 544,
key: 3,
val: new Buffer('7'),
}, {
id: 89,
key: 34,
val: new Buffer('10 o\'clock'),
}, {
id: 48,
key: 403,
val: new Buffer('97'),
},
];
qb.insertBatch('create_test', data, (err, rows) => {
expect(err).is.not.ok; expect(err).is.not.ok;
return done(); return done();
}); });
});
test('Callback - Test Insert Batch', done => {
let data = [
{
id: 544,
key: 3,
val: new Buffer('7'),
}, {
id: 89,
key: 34,
val: new Buffer('10 o\'clock'),
}, {
id: 48,
key: 403,
val: new Buffer('97'),
},
];
qb.insertBatch('create_test', data, (err, rows) => {
expect(err).is.not.ok;
return done();
}); });
}); });
/*---------------------------------------------------------------------------
Promise Tests
---------------------------------------------------------------------------*/
promiseTestRunner(qb);
test('Promise - Select with function and argument in WHERE clause', () => {
let promise = qb.select('id')
.from('create_test')
.where('id', 'ABS(-88)')
.get();
expect(promise).to.be.fulfilled;
});
suiteTeardown(() => { suiteTeardown(() => {
qb.end(); qb.end();
}); });

View File

@ -0,0 +1,64 @@
'use strict';
module.exports = function mysqlBase(qb, nodeQuery, expect, testRunner, promiseTestRunner) {
test('nodeQuery.getQuery = nodeQuery.init', () => {
expect(nodeQuery.getQuery())
.to.be.deep.equal(qb);
});
/*---------------------------------------------------------------------------
Callback Tests
---------------------------------------------------------------------------*/
testRunner(qb, (err, done) => {
expect(err).is.not.ok;
done();
});
test('Callback - Select with function and argument in WHERE clause', done => {
qb.select('id')
.from('create_test')
.where('id', 'CEILING(SQRT(88))')
.get((err, rows) => {
expect(err).is.not.ok;
return done();
});
});
test('Test Insert Batch', done => {
let data = [
{
id: 544,
key: 3,
val: new Buffer('7'),
}, {
id: 89,
key: 34,
val: new Buffer('10 o\'clock'),
}, {
id: 48,
key: 403,
val: new Buffer('97'),
},
];
qb.insertBatch('create_test', data, (err, rows) => {
expect(err).is.not.ok;
return done();
});
});
/*---------------------------------------------------------------------------
Promise Tests
---------------------------------------------------------------------------*/
promiseTestRunner(qb);
test('Promise - Select with function and argument in WHERE clause', () => {
let promise = qb.select('id')
.from('create_test')
.where('id', 'CEILING(SQRT(88))')
.get();
expect(promise).to.be.fulfilled;
});
suiteTeardown(() => {
qb.end();
});
};

View File

@ -1,14 +1,16 @@
'use strict'; 'use strict';
let configFile = (process.env.CI) ? '../config-travis.json' : '../config.json'; const configFile = (process.env.CI) ? '../config-travis.json' : '../config.json';
// Load the test base // Load the test base
let reload = require('require-reload')(require); const reload = require('require-reload')(require);
reload.emptyCache(); reload.emptyCache();
const testBase = reload('../base');
const expect = testBase.expect,
promiseTestRunner = testBase.promiseTestRunner,
testRunner = testBase.testRunner;
let getArgs = reload('getargs'); let getArgs = reload('getargs');
let expect = reload('chai').expect;
let tests = reload('./adapterTestBase').tests;
let testRunner = reload('./adapterTestBase').runner;
// Load the test config file // Load the test config file
let adapterName = 'mysql2'; let adapterName = 'mysql2';
@ -22,40 +24,6 @@ let connection = mysql2.createConnection(config.conn);
let nodeQuery = reload('../../lib/NodeQuery'); let nodeQuery = reload('../../lib/NodeQuery');
let qb = nodeQuery.init('mysql', connection, adapterName); let qb = nodeQuery.init('mysql', connection, adapterName);
suite('Mysql2 adapter tests', () => { suite('Mysql2 adapter tests -', () => {
testRunner(tests, qb, (err, done) => { require('./mysql-base')(qb, nodeQuery, expect, testRunner, promiseTestRunner);
expect(err).is.not.ok;
done();
});
suite('Adapter-specific tests', () => {
test('nodeQuery.getQuery = nodeQuery.init', () => {
expect(nodeQuery.getQuery())
.to.be.deep.equal(qb);
});
test('Test Insert Batch', done => {
let data = [
{
id: 544,
key: 3,
val: new Buffer('7'),
}, {
id: 89,
key: 34,
val: new Buffer('10 o\'clock'),
}, {
id: 48,
key: 403,
val: new Buffer('97'),
},
];
qb.insertBatch('create_test', data, (err, rows) => {
expect(err).is.not.ok;
return done();
});
});
});
suiteTeardown(() => {
qb.end();
});
}); });

View File

@ -1,14 +1,16 @@
'use strict'; 'use strict';
let configFile = (process.env.CI) ? '../config-travis.json' : '../config.json'; const configFile = (process.env.CI) ? '../config-travis.json' : '../config.json';
// Load the test base // Load the test base
let reload = require('require-reload')(require); const reload = require('require-reload')(require);
reload.emptyCache(); reload.emptyCache();
const testBase = reload('../base');
const expect = testBase.expect,
promiseTestRunner = testBase.promiseTestRunner,
testRunner = testBase.testRunner;
let getArgs = reload('getargs'); let getArgs = reload('getargs');
let expect = reload('chai').expect;
let tests = reload('./adapterTestBase').tests;
let testRunner = reload('./adapterTestBase').runner;
// Load the test config file // Load the test config file
let adapterName = 'mysql'; let adapterName = 'mysql';
@ -22,40 +24,6 @@ let connection = mysql.createConnection(config.conn);
let nodeQuery = reload('../../lib/NodeQuery'); let nodeQuery = reload('../../lib/NodeQuery');
let qb = nodeQuery.init('mysql', connection); let qb = nodeQuery.init('mysql', connection);
suite('Mysql adapter tests', () => { suite('Mysql adapter tests -', () => {
testRunner(tests, qb, (err, done) => { require('./mysql-base')(qb, nodeQuery, expect, testRunner, promiseTestRunner);
expect(err).is.not.ok;
done();
});
suite('Adapter-specific tests', () => {
test('nodeQuery.getQuery = nodeQuery.init', () => {
expect(nodeQuery.getQuery())
.to.be.deep.equal(qb);
});
test('Test Insert Batch', done => {
let data = [
{
id: 544,
key: 3,
val: new Buffer('7'),
}, {
id: 89,
key: 34,
val: new Buffer('10 o\'clock'),
}, {
id: 48,
key: 403,
val: new Buffer('97'),
},
];
qb.insertBatch('create_test', data, (err, rows) => {
expect(err).is.not.ok;
return done();
});
});
});
suiteTeardown(() => {
qb.end();
});
}); });

View File

@ -0,0 +1,51 @@
'use strict';
// Load the test base
const path = require('path');
const reload = require('require-reload')(require);
const testBase = reload('../base');
const expect = reload('chai').expect;
const testRunner = testBase.testRunner;
const promiseTestRunner = testBase.promiseTestRunner;
// Load the test config file
let adapterName = 'node-firebird';
let Firebird = reload(adapterName);
const config = reload('../config.json')[adapterName];
config.conn.database = path.join(__dirname, config.conn.database);
let nodeQuery = reload('../../lib/NodeQuery');
let qb = null;
// Skip on TravisCi
if (process.env.CI || process.env.JENKINS_HOME) {
return;
}
suite('Firebird adapter tests -', () => {
// Set up the query builder object
suiteSetup('Database connection', connected => {
Firebird.attach(config.conn, (err, db) => {
qb = nodeQuery.init('firebird', db, adapterName);
return connected(err);
});
});
testRunner(qb, (err, done) => {
expect(err).is.not.ok;
done();
});
suite('Adapter-specific tests', () => {
test('nodeQuery.getQuery = nodeQuery.init', () => {
expect(nodeQuery.getQuery())
.to.be.deep.equal(qb);
});
test('insertBatch throws error', () => {
expect(() => {
qb.driver.insertBatch('create_test', []);
}).to.throw(Error, "Not Implemented");
});
});
suiteTeardown(() => {
qb.end();
});
});

View File

@ -1,46 +0,0 @@
'use strict';
// Load the test base
let reload = require('require-reload')(require);
let expect = reload('chai').expect;
let tests = reload('./adapterTestBase').tests;
let testRunner = reload('./adapterTestBase').runner;
// Load the test config file
let adapterName = 'node-firebird';
let Firebird = reload(adapterName);
let config = reload('../config.json')[adapterName];
config.conn.database = __dirname + config.conn.database;
let nodeQuery = reload('../../lib/NodeQuery');
// Skip on TravisCi
if (process.env.CI || process.env.JENKINS_HOME)
{
return;
}
suite('Firebird adapter tests', () => {
Firebird.attach(config.conn, (err, db) => {
// Set up the query builder object
let qb = nodeQuery.init('firebird', db, adapterName);
testRunner(tests, qb, (err, done) => {
expect(err).is.not.ok;
done();
});
suite('Adapter-specific tests', () => {
test('nodeQuery.getQuery = nodeQuery.init', () => {
expect(nodeQuery.getQuery())
.to.be.deep.equal(qb);
});
test('insertBatch throws error', () => {
expect(() => {
qb.driver.insertBatch('create_test', []);
}).to.throw(Error, "Not Implemented");
});
});
suiteTeardown(() => {
db.detach();
});
});
});

View File

@ -3,10 +3,12 @@
let configFile = (process.env.CI) ? '../config-travis.json' : '../config.json'; let configFile = (process.env.CI) ? '../config-travis.json' : '../config.json';
// Load the test base // Load the test base
let reload = require('require-reload')(require); const reload = require('require-reload')(require);
let expect = reload('chai').expect; reload.emptyCache();
let tests = reload('./adapterTestBase').tests; const testBase = reload('../base');
let testRunner = reload('./adapterTestBase').runner; const expect = testBase.expect,
promiseTestRunner = testBase.promiseTestRunner,
testRunner = testBase.testRunner;
// Load the test config file // Load the test config file
let adapterName = 'pg'; let adapterName = 'pg';
@ -25,40 +27,59 @@ connection.connect(err => {
let nodeQuery = reload('../../lib/NodeQuery'); let nodeQuery = reload('../../lib/NodeQuery');
let qb = nodeQuery.init('pg', connection); let qb = nodeQuery.init('pg', connection);
suite('Pg adapter tests', () => { suite('Pg adapter tests -', () => {
testRunner(tests, qb, (err, done) => { test('nodeQuery.getQuery = nodeQuery.init', () => {
expect(nodeQuery.getQuery())
.to.be.deep.equal(qb);
});
/*---------------------------------------------------------------------------
Callback Tests
---------------------------------------------------------------------------*/
testRunner(qb, (err, done) => {
expect(err).is.not.ok; expect(err).is.not.ok;
done(); done();
}); });
suite('Adapter-specific tests', () => { test('Callback - Select with function and argument in WHERE clause', done => {
test('nodeQuery.getQuery = nodeQuery.init', () => { qb.select('id')
expect(nodeQuery.getQuery()) .from('create_test')
.to.be.deep.equal(qb); .where('id', 'CEILING(SQRT(88))')
}); .get((err, rows) => {
test('Test Insert Batch', done => {
let data = [
{
id: 544,
key: 3,
val: new Buffer('7'),
}, {
id: 89,
key: 34,
val: new Buffer('10 o\'clock'),
}, {
id: 48,
key: 403,
val: new Buffer('97'),
},
];
qb.insertBatch('create_test', data, (err, rows) => {
expect(err).is.not.ok; expect(err).is.not.ok;
return done(); return done();
}); });
});
}); });
suiteTeardown(() => {
qb.end(); /*---------------------------------------------------------------------------
Promise Tests
---------------------------------------------------------------------------*/
promiseTestRunner(qb);
test('Promise - Select with function and argument in WHERE clause', () => {
let promise = qb.select('id')
.from('create_test')
.where('id', 'CEILING(SQRT(88))')
.get();
expect(promise).to.be.fulfilled;
});
test('Promise - Test Insert Batch', () => {
let data = [
{
id: 544,
key: 3,
val: new Buffer('7'),
}, {
id: 89,
key: 34,
val: new Buffer('10 o\'clock'),
}, {
id: 48,
key: 403,
val: new Buffer('97'),
},
];
let promise = qb.insertBatch('create_test', data);
return expect(promise).to.be.fulfilled;
}); });
}); });

13
test/base.js Normal file
View File

@ -0,0 +1,13 @@
'use strict';
const chai = require('chai');
const chaiAsPromised = require('chai-as-promised');
chai.use(chaiAsPromised);
const expect = chai.expect;
module.exports = {
expect: expect,
tests: require('./base/tests'),
testRunner: require('./base/adapterCallbackTestRunner'),
promiseTestRunner: require('./base/adapterPromiseTestRunner'),
};

View File

@ -0,0 +1,236 @@
'use strict';
// Load the test base
const chai = require('chai');
const chaiAsPromised = require('chai-as-promised');
chai.use(chaiAsPromised);
const expect = chai.expect;
const reload = require('require-reload')(require);
let tests = reload('../base/tests');
let helpers = reload('../../lib/helpers'),
State = reload('../../lib/State');
module.exports = function testRunner(qb, callback) {
Object.keys(tests).forEach(suiteName => {
suite(suiteName, () => {
let currentSuite = tests[suiteName];
Object.keys(currentSuite).forEach(testDesc => {
test(`Callback - ${testDesc}`, done => {
let methodObj = currentSuite[testDesc];
let methodNames = Object.keys(methodObj);
let lastMethodIndex = methodNames[methodNames.length - 1];
methodObj[lastMethodIndex].push((err, rows) => {
callback(err, done);
});
methodNames.forEach(name => {
let args = methodObj[name],
method = qb[name];
if (args[0] === 'multiple') {
args.shift();
args.forEach(argSet => {
method.apply(qb, argSet);
});
} else {
method.apply(qb, args);
}
});
});
});
});
});
suite('DB update tests -', () => {
setup(done => {
let sql = qb.driver.truncate('create_test');
qb.adapter.execute(sql, (err, res) => {
done();
});
});
test('Callback - Test Insert', done => {
qb.set('id', 98)
.set('key', '84')
.set('val', new Buffer('120'))
.insert('create_test', (err, rows) => {
return callback(err, done);
});
});
test('Callback - Test Insert Object', done => {
qb.insert('create_test', {
id: 587,
key: 1,
val: new Buffer('2'),
}, (err, rows) => {
return callback(err, done);
});
});
test('Callback - Test Update', done => {
qb.where('id', 7)
.update('create_test', {
id: 7,
key: 'gogle',
val: new Buffer('non-word'),
}, (err, rows) => {
return callback(err, done);
});
});
test('Callback - Test set Array Update', done => {
let object = {
id: 22,
key: 'gogle',
val: new Buffer('non-word'),
};
qb.set(object)
.where('id', 22)
.update('create_test', (err, rows) => {
return callback(err, done);
});
});
test('Callback - Test where set update', done => {
qb.where('id', 36)
.set('id', 36)
.set('key', 'gogle')
.set('val', new Buffer('non-word'))
.update('create_test', (err, rows) => {
return callback(err, done);
});
});
test('Callback - Test delete', done => {
qb.delete('create_test', {id: 5}, (err, rows) => {
return callback(err, done);
});
});
test('Callback - Delete with where', done => {
qb.where('id', 5)
.delete('create_test', (err, rows) => {
return callback(err, done);
});
});
test('Callback - Delete multiple where values', done => {
qb.delete('create_test', {
id: 5,
key: 'gogle',
}, (err, rows) => {
return callback(err, done);
});
});
});
suite('Grouping tests -', () => {
test('Callback - Using grouping method', done => {
qb.select('id, key as k, val')
.from('create_test')
.groupStart()
.where('id >', 1)
.where('id <', 900)
.groupEnd()
.limit(2, 1)
.get((err, rows) => {
return callback(err, done);
});
});
test('Callback - Using where first grouping', done => {
qb.select('id, key as k, val')
.from('create_test')
.where('id !=', 5)
.groupStart()
.where('id >', 1)
.where('id <', 900)
.groupEnd()
.limit(2, 1)
.get((err, rows) => {
return callback(err, done);
});
});
test('Callback - Using or grouping method', done => {
qb.select('id, key as k, val')
.from('create_test')
.groupStart()
.where('id >', 1)
.where('id <', 900)
.groupEnd()
.orGroupStart()
.where('id', 0)
.groupEnd()
.limit(2, 1)
.get((err, rows) => {
return callback(err, done);
});
});
test('Callback - Using or not grouping method', done => {
qb.select('id, key as k, val')
.from('create_test')
.groupStart()
.where('id >', 1)
.where('id <', 900)
.groupEnd()
.orNotGroupStart()
.where('id', 0)
.groupEnd()
.limit(2, 1)
.get((err, rows) => {
return callback(err, done);
});
});
});
suite('Get compiled tests -', () => {
test('select', () => {
let sql = qb.select('id')
.from('create_test')
.getCompiledSelect(true);
expect(helpers.isString(sql)).to.be.true;
});
test('select from', () => {
let sql = qb.select('id')
.getCompiledSelect('create_test', true);
expect(helpers.isString(sql)).to.be.true;
});
test('insert', () => {
let sql = qb.set('id', 3)
.getCompiledInsert('create_test');
expect(helpers.isString(sql)).to.be.true;
});
test('update', () => {
let sql = qb.set('id', 3)
.where('id', 5)
.getCompiledUpdate('create_test');
expect(helpers.isString(sql)).to.be.true;
});
test('delete', () => {
let sql = qb.where('id', 5)
.getCompiledDelete('create_test');
expect(helpers.isString(sql)).to.be.true;
});
});
suite('Misc tests -', () => {
test('Get State', () => {
qb.select('foo')
.from('bar')
.where('baz', 'foobar');
let state = new State();
expect(JSON.stringify(state)).to.not.be.deep.equal(qb.getState());
});
test('Reset State', () => {
qb.select('foo')
.from('bar')
.where('baz', 'foobar');
qb.resetQuery();
let state = new State();
expect(qb.getState()).to.be.deep.equal(state);
});
});
};

View File

@ -0,0 +1,181 @@
'use strict';
// Load the test base
const chai = require('chai');
const chaiAsPromised = require('chai-as-promised');
chai.use(chaiAsPromised);
const expect = chai.expect;
const reload = require('require-reload')(require);
let tests = reload('../base/tests');
let helpers = reload('../../lib/helpers'),
State = reload('../../lib/State');
module.exports = function promiseTestRunner(qb) {
Object.keys(tests).forEach(suiteName => {
suite(suiteName, () => {
let currentSuite = tests[suiteName];
Object.keys(currentSuite).forEach(testDesc => {
test(`Promise - ${testDesc}`, () => {
let methodObj = currentSuite[testDesc];
let methodNames = Object.keys(methodObj);
let lastMethodIndex = methodNames[methodNames.length - 1];
let results = [];
methodNames.forEach(name => {
let args = methodObj[name],
method = qb[name];
if (args[0] === 'multiple') {
args.shift();
args.forEach(argSet => {
results.push(method.apply(qb, argSet));
});
} else {
results.push(method.apply(qb, args));
}
});
let promise = results.pop();
expect(promise).to.be.fulfilled;
});
});
});
});
suite('DB update tests -', () => {
suiteSetup(done => {
let sql = qb.driver.truncate('create_test');
qb.adapter.execute(sql).then(res => {
return done();
}).catch(err => {
return done(err);
});
});
test('Promise - Test Insert', () => {
let promise = qb.set('id', 98)
.set('key', '84')
.set('val', new Buffer('120'))
.insert('create_test');
expect(promise).to.be.fulfilled;
});
test('Promise - Test Insert Object', () => {
let promise = qb.insert('create_test', {
id: 587,
key: 1,
val: new Buffer('2'),
});
expect(promise).to.be.fulfilled;
});
test('Promise - Test Update', () => {
let promise = qb.where('id', 7)
.update('create_test', {
id: 7,
key: 'gogle',
val: new Buffer('non-word'),
});
expect(promise).to.be.fulfilled;
});
test('Promise - Test set Array Update', () => {
let object = {
id: 22,
key: 'gogle',
val: new Buffer('non-word'),
};
let promise = qb.set(object)
.where('id', 22)
.update('create_test');
expect(promise).to.be.fulfilled;
});
test('Promise - Test where set update', () => {
let promise = qb.where('id', 36)
.set('id', 36)
.set('key', 'gogle')
.set('val', new Buffer('non-word'))
.update('create_test');
expect(promise).to.be.fulfilled;
});
test('Promise - Test delete', () => {
let promise = qb.delete('create_test', {id: 5});
expect(promise).to.be.fulfilled;
});
test('Promise - Delete with where', () => {
let promise = qb.where('id', 5)
.delete('create_test');
expect(promise).to.be.fulfilled;
});
test('Promise - Delete multiple where values', () => {
let promise = qb.delete('create_test', {
id: 5,
key: 'gogle',
});
expect(promise).to.be.fulfilled;
});
});
suite('Grouping tests -', () => {
test('Promise - Using grouping method', () => {
let promise = qb.select('id, key as k, val')
.from('create_test')
.groupStart()
.where('id >', 1)
.where('id <', 900)
.groupEnd()
.limit(2, 1)
.get();
expect(promise).to.be.fulfilled;
});
test('Promise - Using where first grouping', () => {
let promise = qb.select('id, key as k, val')
.from('create_test')
.where('id !=', 5)
.groupStart()
.where('id >', 1)
.where('id <', 900)
.groupEnd()
.limit(2, 1)
.get();
expect(promise).to.be.fulfilled;
});
test('Promise - Using or grouping method', () => {
let promise = qb.select('id, key as k, val')
.from('create_test')
.groupStart()
.where('id >', 1)
.where('id <', 900)
.groupEnd()
.orGroupStart()
.where('id', 0)
.groupEnd()
.limit(2, 1)
.get();
expect(promise).to.be.fulfilled;
});
test('Promise - Using or not grouping method', () => {
let promise = qb.select('id, key as k, val')
.from('create_test')
.groupStart()
.where('id >', 1)
.where('id <', 900)
.groupEnd()
.orNotGroupStart()
.where('id', 0)
.groupEnd()
.limit(2, 1)
.get();
expect(promise).to.be.fulfilled;
});
});
};

246
test/base/tests.js Normal file
View File

@ -0,0 +1,246 @@
'use strict';
module.exports = {
'Get tests -': {
'Get with function': {
select: ['id, COUNT(id) as count'],
from: ['create_test'],
groupBy: ['id'],
get: [],
},
'Basic select all get': {
get: ['create_test'],
},
'Basic select all with from': {
from: ['create_test'],
get: [],
},
'Get with limit': {
get: ['create_test', 2],
},
'Get with limit and offset': {
get: ['create_test', 2, 1],
},
'Get with having': {
select: ['id'],
from: ['create_test'],
groupBy: ['id'],
having: [
'multiple',
[{'id >': 1}],
['id !=', 3],
['id', 900],
],
get: [],
},
'Get with orHaving': {
select: ['id'],
from: ['create_test'],
groupBy: ['id'],
having: [{'id >': 1}],
orHaving: ['id !=', 3],
get: [],
},
},
'Select tests -': {
'Select where get': {
select: [['id', 'key as k', 'val']],
where: [
'multiple',
['id >', 1],
['id <', 900],
],
get: ['create_test', 2, 1],
},
'Select where get 2': {
select: ['id, key as k, val'],
where: ['id !=', 1],
get: ['create_test', 2, 1],
},
'Multi Order By': {
from: ['create_test'],
orderBy: ['id, key'],
get: [],
},
'Select get': {
select: ['id, key as k, val'],
get: ['create_test', 2, 1],
},
'Select from get': {
select: ['id, key as k, val'],
from: ['create_test ct'],
where: ['id >', 1],
get: [],
},
'Select from limit get': {
select: ['id, key as k, val'],
from: ['create_test ct'],
where: ['id >', 1],
limit: [3],
get: [],
},
'Select where IS NOT NULL': {
select: ['id', 'key as k', 'val'],
from: ['create_test ct'],
whereIsNotNull: ['id'],
get: [],
},
'Select where IS NULL': {
select: ['id', 'key as k', 'val'],
from: ['create_test ct'],
whereIsNull: ['id'],
get: [],
},
'Select where OR IS NOT NULL': {
select: ['id', 'key as k', 'val'],
from: ['create_test ct'],
whereIsNull: ['id'],
orWhereIsNotNull: ['id'],
get: [],
},
'Select where OR IS NULL': {
select: ['id', 'key as k', 'val'],
from: ['create_test ct'],
where: ['id', 3],
orWhereIsNull: ['id'],
get: [],
},
'Select with string where value': {
select: ['id', 'key as k', 'val'],
from: ['create_test ct'],
where: ['id > 3'],
get: [],
},
},
'Where in tests -': {
'Where in': {
from: ['create_test'],
whereIn: ['id', [0, 6, 56, 563, 341]],
get: [],
},
'Or Where in': {
from: ['create_test'],
where: ['key', 'false'],
orWhereIn: ['id', [0, 6, 56, 563, 341]],
get: [],
},
'Where Not in': {
from: ['create_test'],
where: ['key', 'false'],
whereNotIn: ['id', [0, 6, 56, 563, 341]],
get: [],
},
'Or Where Not in': {
from: ['create_test'],
where: ['key', 'false'],
orWhereNotIn: ['id', [0, 6, 56, 563, 341]],
get: [],
},
},
'Query modifier tests -': {
'Order By': {
select: ['id, key as k, val'],
from: ['create_test'],
where: [
'multiple',
['id >', 0],
['id <', 9000],
],
orderBy: [
'multiple',
['id', 'DESC'],
['k', 'ASC'],
],
limit: [5, 2],
get: [],
},
'Group By': {
select: ['id, key as k, val'],
from: ['create_test'],
where: [
'multiple',
['id >', 0],
['id <', 9000],
],
groupBy: [
'multiple',
['k'],
[['id', 'val']],
],
orderBy: [
'multiple',
['id', 'DESC'],
['k', 'ASC'],
],
limit: [5, 2],
get: [],
},
'Or Where': {
select: ['id, key as k, val'],
from: ['create_test'],
where: [' id ', 1],
orWhere: ['key > ', 0],
limit: [2, 1],
get: [],
},
Like: {
from: ['create_test'],
like: ['key', 'og'],
get: [],
},
'Or Like': {
from: ['create_test'],
like: ['key', 'og'],
orLike: ['key', 'val'],
get: [],
},
'Not Like': {
from: ['create_test'],
like: ['key', 'og', 'before'],
notLike: ['key', 'val'],
get: [],
},
'Or Not Like': {
from: ['create_test'],
like: ['key', 'og', 'before'],
orNotLike: ['key', 'val'],
get: [],
},
'Like Before': {
from: ['create_test'],
like: ['key', 'og', 'before'],
get: [],
},
'Like After': {
from: ['create_test'],
like: ['key', 'og', 'after'],
get: [],
},
'Basic Join': {
from: ['create_test ct'],
join: ['create_join cj', 'cj.id=ct.id'],
get: [],
},
'Left Join': {
from: ['create_test ct'],
join: ['create_join cj', 'cj.id=ct.id', 'left'],
get: [],
},
'Inner Join': {
from: ['create_test ct'],
join: ['create_join cj', 'cj.id=ct.id', 'inner'],
get: [],
},
'Join with multiple where values': {
from: ['create_test ct'],
join: ['create_join cj', 'cj.id=ct.id', 'inner'],
where: [
{
'ct.id < ': 3,
'ct.key ': 'foo',
},
],
get: [],
},
},
};

View File

@ -6,7 +6,7 @@ let expect = require('chai').expect,
nodeQuery = reload('../lib/NodeQuery'), nodeQuery = reload('../lib/NodeQuery'),
Adapter = reload('../lib/Adapter'); Adapter = reload('../lib/Adapter');
suite('Base tests', () => { suite('Base tests -', () => {
suite('Sanity check', () => { suite('Sanity check', () => {
let files = glob.sync(`${__dirname}/../lib/**/*.js`); let files = glob.sync(`${__dirname}/../lib/**/*.js`);
files.forEach(mod => { files.forEach(mod => {

View File

@ -7,8 +7,8 @@ let chai = require('chai'),
let helpers = require('../lib/helpers'); let helpers = require('../lib/helpers');
suite('Helper Module Tests', () => { suite('Helper Module Tests -', () => {
suite('Type-checking methods', () => { suite('Type-checking methods -', () => {
suite('Object wrappers are listed as their native type', () => { suite('Object wrappers are listed as their native type', () => {
test('Boolean Wrapper returns \'boolean\' not \'object\'', () => { test('Boolean Wrapper returns \'boolean\' not \'object\'', () => {
let item = Boolean(true); let item = Boolean(true);
@ -23,7 +23,7 @@ suite('Helper Module Tests', () => {
expect(helpers.type(item)).to.deep.equal('string'); expect(helpers.type(item)).to.deep.equal('string');
}); });
}); });
suite('is..Method methods exist', () => { suite('is..Method methods exist -', () => {
let types = [ let types = [
'Null', 'Null',
'Undefined', 'Undefined',
@ -44,7 +44,7 @@ suite('Helper Module Tests', () => {
}); });
}); });
}); });
suite('isScalar', () => { suite('isScalar -', () => {
let trueCases = { let trueCases = {
'Strings are scalar': 'foo', 'Strings are scalar': 'foo',
'Booleans are scalar': true, 'Booleans are scalar': true,
@ -66,7 +66,7 @@ suite('Helper Module Tests', () => {
}); });
}); });
}); });
suite('isInfinity', () => { suite('isInfinity -', () => {
test('The type of 1/0 is infinity', () => { test('The type of 1/0 is infinity', () => {
expect(helpers.type(1 / 0)).to.equal('infinity'); expect(helpers.type(1 / 0)).to.equal('infinity');
}); });
@ -74,7 +74,7 @@ suite('Helper Module Tests', () => {
expect(helpers.isInfinite(1 / 0)).to.be.true; expect(helpers.isInfinite(1 / 0)).to.be.true;
}); });
}); });
suite('isNaN', () => { suite('isNaN -', () => {
test('The type of 0 / 0 is NaN', () => { test('The type of 0 / 0 is NaN', () => {
expect(helpers.type(0 / 0)).to.equal('nan'); expect(helpers.type(0 / 0)).to.equal('nan');
}); });
@ -83,8 +83,8 @@ suite('Helper Module Tests', () => {
}); });
}); });
}); });
suite('Other helper methods', () => { suite('Other helper methods -', () => {
suite('stringTrim', () => { suite('stringTrim -', () => {
test('stringTrim method works as expected', () => { test('stringTrim method works as expected', () => {
let orig = [' x y ', 'z ', ' q']; let orig = [' x y ', 'z ', ' q'];
let ret = ['x y', 'z', 'q']; let ret = ['x y', 'z', 'q'];
@ -92,7 +92,7 @@ suite('Helper Module Tests', () => {
expect(orig.map(helpers.stringTrim)).to.be.deep.equal(ret); expect(orig.map(helpers.stringTrim)).to.be.deep.equal(ret);
}); });
}); });
suite('arrayPluck', () => { suite('arrayPluck -', () => {
let orig = [ let orig = [
{ {
foo: 1, foo: 1,
@ -115,7 +115,7 @@ suite('Helper Module Tests', () => {
expect(helpers.arrayPluck([], 'apple')).to.be.deep.equal([]); expect(helpers.arrayPluck([], 'apple')).to.be.deep.equal([]);
}); });
}); });
suite('regexInArray', () => { suite('regexInArray -', () => {
let orig = ['apple', ' string ', 6, 4, 7]; let orig = ['apple', ' string ', 6, 4, 7];
let cases = [ let cases = [

View File

@ -1,7 +1,9 @@
--ui tdd --ui tdd
--bail --bail
--recursive --recursive
--reporter dot --reporter list
--slow 100 --slow 200
--timeout 5000 --timeout 10000
--check-leaks --check-leaks
--growl
test/**/*_test.js