Increase code coverage

This commit is contained in:
Timothy Warren 2015-12-07 12:03:42 -05:00
parent 104e439230
commit 816911b54d
19 changed files with 446 additions and 58 deletions

View File

@ -2,7 +2,6 @@ language: node_js
sudo: false sudo: false
node_js: node_js:
- "iojs"
- "node" - "node"
- "5.1" - "5.1"
- "5.0" - "5.0"
@ -18,7 +17,3 @@ before_script:
- psql test postgres -f ./test/sql/pgsql.sql - psql test postgres -f ./test/sql/pgsql.sql
script: gulp script: gulp
matrix:
allow_failures:
- node_js: iojs

View File

@ -9,7 +9,10 @@ const documentation = require('gulp-documentation'),
sloc = require('gulp-sloc'); sloc = require('gulp-sloc');
const SRC_FILES = ['lib/**/*.js']; const SRC_FILES = ['lib/**/*.js'];
const TEST_FILES = ['test/**/*_test.js']; const TEST_FILES = [
'test/*_test.js',
'test/adapters/*_test.js'
];
const ESLINT_SETTINGS = { const ESLINT_SETTINGS = {
"env": { "env": {
@ -22,7 +25,9 @@ const ESLINT_SETTINGS = {
"no-constant-condition": [1], "no-constant-condition": [1],
"no-extra-semi": [1], "no-extra-semi": [1],
"no-func-assign": [1], "no-func-assign": [1],
"no-obj-calls": [2],
"no-unexpected-multiline" : [2], "no-unexpected-multiline" : [2],
"no-unneeded-ternary": [2],
"radix": [2], "radix": [2],
"no-with": [2], "no-with": [2],
"no-eval": [2], "no-eval": [2],
@ -42,7 +47,9 @@ const ESLINT_SETTINGS = {
"no-var": [2], "no-var": [2],
"valid-jsdoc": [1], "valid-jsdoc": [1],
"strict": [2, "global"], "strict": [2, "global"],
"callback-return": [1] "callback-return": [1],
"object-shorthand": [1, "methods"],
"prefer-template": [1]
} }
}; };
@ -55,7 +62,7 @@ gulp.task('lint', () => {
}); });
gulp.task('lint-tests', ['lint'], () => { gulp.task('lint-tests', ['lint'], () => {
return pipe(gulp.src(TEST_FILES), [ return pipe(gulp.src(['test/**/*.js']), [
eslint(ESLINT_SETTINGS), eslint(ESLINT_SETTINGS),
eslint.format(), eslint.format(),
eslint.failAfterError() eslint.failAfterError()
@ -81,7 +88,13 @@ gulp.task('mocha', ['lint-tests', 'sloc'], () => {
bail: true, bail: true,
//reporter: 'dot', //reporter: 'dot',
//reporter: 'landing', //reporter: 'landing',
})); }))
.once('error', () => {
process.exit(1);
})
.once('end', () => {
process.exit();
});
}); });
gulp.task('test', ['test-sloc', 'lint-tests'], function(cb) { gulp.task('test', ['test-sloc', 'lint-tests'], function(cb) {
@ -98,6 +111,12 @@ gulp.task('test', ['test-sloc', 'lint-tests'], function(cb) {
dir: './coverage', dir: './coverage',
reporters: ['lcov', 'lcovonly', 'html', 'text'] reporters: ['lcov', 'lcovonly', 'html', 'text']
}) })
.once('error', () => {
process.exit(1);
})
.once('end', () => {
process.exit();
})
]); ]);
}); });
}); });

View File

@ -1,6 +1,5 @@
"use strict"; "use strict";
let instance = null;
let fs = require('fs'), let fs = require('fs'),
helpers = require('./helpers'), helpers = require('./helpers'),
QueryBuilder = require('./QueryBuilder'); QueryBuilder = require('./QueryBuilder');
@ -22,7 +21,6 @@ class NodeQuery {
/** /**
* Create a query builder object * Create a query builder object
* *
* @memberOf 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 drivername
@ -32,17 +30,16 @@ class NodeQuery {
connLib = connLib || driverType; connLib = connLib || driverType;
let paths = { let paths = {
driver: `${__dirname}/drivers/` + helpers.upperCaseFirst(driverType), driver: `${__dirname}/drivers/${helpers.upperCaseFirst(driverType)}`,
adapter: `${__dirname}/adapters/${connLib}` adapter: `${__dirname}/adapters/${connLib}`
}; };
Object.keys(paths).forEach(type => { Object.keys(paths).forEach(type => {
if ( ! fs.existsSync(paths[type])) try {
{ fs.statSync(`${paths[type]}.js`);
} catch(e) {
throw new Error( throw new Error(
`Selected ${type} (` + `Selected ${type} (${helpers.upperCaseFirst(driverType)}) does not exist!`
helpers.upperCaseFirst(driverType) +
`) does not exist!`
); );
} }
}); });
@ -62,13 +59,12 @@ class NodeQuery {
* @return {QueryBuilder} - The Query Builder object * @return {QueryBuilder} - The Query Builder object
*/ */
getQuery() { getQuery() {
if ( ! this.instance) { if (this.instance == null) {
throw new Error("No Query Builder instance to return"); throw new Error("No Query Builder instance to return");
} }
return this.instance; return this.instance;
} }
} }
module.exports = new NodeQuery(); module.exports = new NodeQuery();

View File

@ -261,7 +261,7 @@ module.exports = class QueryBuilder {
let args = getArgs('key:string, val:array, inClause:string, conj:string', arguments); let args = getArgs('key:string, val:array, inClause:string, conj:string', arguments);
args.key = this.driver.quoteIdentifiers(args.key); args.key = this.driver.quoteIdentifiers(args.key);
let params = new Array(args.val.length); let params = Array(args.val.length);
params.fill('?'); params.fill('?');
args.val.forEach(value => { args.val.forEach(value => {

View File

@ -114,8 +114,6 @@ module.exports = class QueryParser {
*/ */
compileJoin(condition) { compileJoin(condition) {
let parts = this.parseJoin(condition); let parts = this.parseJoin(condition);
let count = parts.identifiers.length;
let i;
// Quote the identifiers // Quote the identifiers
parts.combined.forEach((part, i) => { parts.combined.forEach((part, i) => {

View File

@ -1,6 +1,6 @@
"use strict"; "use strict";
var helpers = require('../helpers'); let helpers = require('../helpers');
/** /**
* Driver for Firebird databases * Driver for Firebird databases
@ -9,7 +9,7 @@ var helpers = require('../helpers');
*/ */
module.exports = (function() { module.exports = (function() {
delete require.cache[require.resolve('../Driver')]; delete require.cache[require.resolve('../Driver')];
var driver = require('../Driver'); let driver = require('../Driver');
driver.hasTruncate = false; driver.hasTruncate = false;
@ -22,7 +22,7 @@ module.exports = (function() {
* @return {String} * @return {String}
*/ */
driver.limit = function(origSql, limit, offset) { driver.limit = function(origSql, limit, offset) {
var sql = 'FIRST ' + limit; let sql = 'FIRST ' + limit;
if (helpers.isNumber(offset)) if (helpers.isNumber(offset))
{ {

View File

@ -7,7 +7,7 @@
*/ */
module.exports = (function() { module.exports = (function() {
delete require.cache[require.resolve('../Driver')]; delete require.cache[require.resolve('../Driver')];
var driver = require('../Driver'), let driver = require('../Driver'),
helpers = require('../helpers'); helpers = require('../helpers');
driver.identifierStartChar = '`'; driver.identifierStartChar = '`';

View File

@ -7,7 +7,7 @@
*/ */
module.exports = (function() { module.exports = (function() {
delete require.cache[require.resolve('../Driver')]; delete require.cache[require.resolve('../Driver')];
var driver = require('../Driver'); let driver = require('../Driver');
return driver; return driver;
}()); }());

View File

@ -7,7 +7,7 @@
*/ */
module.exports = (function() { module.exports = (function() {
delete require.cache[require.resolve('../Driver')]; delete require.cache[require.resolve('../Driver')];
var driver = require('../Driver'), let driver = require('../Driver'),
helpers = require('../helpers'); helpers = require('../helpers');
// Sqlite doesn't have a truncate command // Sqlite doesn't have a truncate command
@ -25,7 +25,7 @@ module.exports = (function() {
// Get the data values to insert, so they can // Get the data values to insert, so they can
// be parameterized // be parameterized
var sql = "", let sql = "",
vals = [], vals = [],
cols = [], cols = [],
fields = [], fields = [],
@ -36,7 +36,7 @@ module.exports = (function() {
data.forEach(function(obj) { data.forEach(function(obj) {
var row = []; let row = [];
Object.keys(obj).forEach(function(key) { Object.keys(obj).forEach(function(key) {
row.push(obj[key]); row.push(obj[key]);
}); });
@ -55,7 +55,7 @@ module.exports = (function() {
sql += "SELECT " + cols.join(', ') + "\n"; sql += "SELECT " + cols.join(', ') + "\n";
vals.forEach(function(row_values) { vals.forEach(function(row_values) {
var quoted = row_values.map(function(value) { let quoted = row_values.map(function(value) {
return String(value).replace("'", "'\'"); return String(value).replace("'", "'\'");
}); });
sql += "UNION ALL SELECT '" + quoted.join("', '") + "'\n"; sql += "UNION ALL SELECT '" + quoted.join("', '") + "'\n";

View File

@ -49,6 +49,7 @@
"chai": "", "chai": "",
"documentation": "", "documentation": "",
"eslint": "", "eslint": "",
"glob": "^6.0.1",
"gulp": "", "gulp": "",
"gulp-documentation": "^2.1.0", "gulp-documentation": "^2.1.0",
"gulp-eslint": "", "gulp-eslint": "",

View File

@ -0,0 +1,157 @@
"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: []
}
}
};
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);
}
});
});
});
});
});
};

View File

@ -0,0 +1,61 @@
'use strict';
// Load the test base
let reload = require('require-reload')(require);
let getArgs = require('getargs');
let expect = require('chai').expect;
let tests = reload('./adapterTestBase').tests;
let testRunner = reload('./adapterTestBase').runner;
// Load the test config file
let adapterName = 'dblite';
let sqlite = null;
let connection = null;
// Set up the connection
try {
sqlite = require(adapterName).withSQLite('3.7.11');
connection = sqlite(':memory:');
} catch (e) {
// Export an empty testsuite if module not loaded
console.log(e);
console.log("Database adapter dblite not found");
//return {};
}
if (connection) {
// Set up the query builder object
let nodeQuery = require('../../lib/NodeQuery');
let qb = nodeQuery.init('sqlite', connection, adapterName);
// Add a test for this adapter
tests['Select tests']['Select with function and argument in WHERE clause'] = {
'select': ['id'],
'from': ['create_test'],
'where': ['id', 'ABS(-88)'],
'get': []
};
suite('Dblite adapter tests', () => {
suiteSetup(() => {
// Set up the sqlite database
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);';
connection.query(sql);
});
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);
});
});
suiteTeardown(() => {
connection.close();
});
});
}

View File

@ -0,0 +1,39 @@
'use strict';
let configFile = (process.env.CI) ? '../config-travis.json' : '../config.json';
// Load the test base
let reload = require('require-reload')(require);
reload.emptyCache();
let getArgs = reload('getargs');
let expect = reload('chai').expect;
let tests = reload('./adapterTestBase').tests;
let testRunner = reload('./adapterTestBase').runner;
// Load the test config file
let adapterName = 'mysql2';
let config = reload(configFile)[adapterName];
// Set up the connection
let mysql2 = reload(adapterName);
let connection = mysql2.createConnection(config.conn);
// Set up the query builder object
let nodeQuery = reload('../../lib/NodeQuery');
let qb = nodeQuery.init('mysql', connection, adapterName);
suite('Mysql2 adapter tests', () => {
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);
});
});
suiteTeardown(() => {
connection.end();
});
});

View File

@ -0,0 +1,39 @@
'use strict';
let configFile = (process.env.CI) ? '../config-travis.json' : '../config.json';
// Load the test base
let reload = require('require-reload')(require);
reload.emptyCache();
let getArgs = reload('getargs');
let expect = reload('chai').expect;
let tests = reload('./adapterTestBase').tests;
let testRunner = reload('./adapterTestBase').runner;
// Load the test config file
let adapterName = 'mysql';
let config = reload(configFile)[adapterName];
// Set up the connection
let mysql = reload(adapterName);
let connection = mysql.createConnection(config.conn);
// Set up the query builder object
let nodeQuery = reload('../../lib/NodeQuery');
let qb = nodeQuery.init('mysql', connection);
suite('Mysql adapter tests', () => {
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);
});
});
suiteTeardown(() => {
connection.end();
});
});

View File

@ -0,0 +1,41 @@
'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);
});
});
suiteTeardown(() => {
db.detach();
});
});
});

42
test/adapters/pg_test.js Normal file
View File

@ -0,0 +1,42 @@
'use strict';
let configFile = (process.env.CI) ? '../config-travis.json' : '../config.json';
// 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 = 'pg';
let config = reload(configFile)[adapterName];
// Set up the connection
let pg = reload(adapterName);
let connection = new pg.Client(config.conn);
connection.connect(function(err) {
if (err) {
throw new Error(err);
}
});
// Set up the query builder object
let nodeQuery = reload('../../lib/NodeQuery');
let qb = nodeQuery.init('pg', connection);
suite('Pg adapter tests', () => {
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);
});
});
suiteTeardown(() => {
connection.end();
});
});

View File

@ -1,39 +1,35 @@
'use strict'; 'use strict';
let assert = require('chai').assert; let expect = require('chai').expect,
let nodeQuery = require('../lib/NodeQuery'); reload = require('require-reload')(require),
glob = require('glob'),
nodeQuery = reload('../lib/NodeQuery');
suite('Base tests', () => { suite('Base tests', () => {
test('Sanity check', () => { suite('Sanity check', () => {
let modules = { let files = glob.sync(`${__dirname}/../lib/**/*.js`);
helpers: require('../lib/helpers'), files.forEach(mod => {
driver: require('../lib/DriverBase'), let obj = require(mod);
qb: require('../lib/QueryBuilder'), let shortName = mod.replace(/^\/(.*?)\/lib\/(.*?)\.js$/g, "$2");
'node-query': require('../lib/NodeQuery'), test(`${shortName} module is sane`, () => {
'state': require('../lib/State'), expect(obj).to.be.ok;
'drivers/pg': require('../lib/drivers/Pg'), });
'drivers/mysql': require('../lib/drivers/Mysql'),
'drivers/sqlite': require('../lib/drivers/Sqlite'),
'adapters/mysql': require('../lib/adapters/mysql'),
'adapters/mysql2': require('../lib/adapters/mysql2'),
'adapters/pg': require('../lib/adapters/pg'),
'adapters/dblite': require('../lib/adapters/dblite')
};
Object.keys(modules).forEach(mod => {
assert.ok(modules[mod], mod + " module is sane");
}); });
}); });
test('NodeQuery.getQuery with no instance', () => { test('NodeQuery.getQuery with no instance', () => {
assert.throws(() => { // Hack for testing to work around node
nodeQuery.getQuery(); // module caching
}, Error, "No Query Builder instance to return"); let nodeQueryCopy = Object.create(nodeQuery);
nodeQueryCopy.instance = null;
expect(() => {
nodeQueryCopy.getQuery();
}).to.throw(Error, "No Query Builder instance to return");
}); });
test('Invalid driver type', () => { test('Invalid driver type', () => {
assert.throws(() => { expect(() => {
nodeQuery.init('foo', {}, 'bar'); nodeQuery.init('foo', {}, 'bar');
}, Error, "Selected driver (Foo) does not exist!"); }).to.throw(Error, "Selected driver (Foo) does not exist!");
}); });
}); });

4
test/mocha.opts Normal file
View File

@ -0,0 +1,4 @@
--ui tdd
--bail
--recursive
--reporter nyan

View File

@ -5,7 +5,7 @@ let expect = require('chai').expect;
// Use the base driver as a mock for testing // Use the base driver as a mock for testing
let getArgs = require('getargs'); let getArgs = require('getargs');
let helpers = require('../lib/helpers'); let helpers = require('../lib/helpers');
let driver = require('../lib/DriverBase'); let driver = require('../lib/Driver');
let p = require('../lib/QueryParser'); let p = require('../lib/QueryParser');
let parser = new p(driver); let parser = new p(driver);