diff --git a/.travis.yml b/.travis.yml index 5a8085b..7f74855 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,6 @@ language: node_js sudo: false node_js: - - "iojs" - "node" - "5.1" - "5.0" @@ -17,8 +16,4 @@ before_script: - mysql -v -uroot test < ./test/sql/mysql.sql - psql test postgres -f ./test/sql/pgsql.sql -script: gulp - -matrix: - allow_failures: - - node_js: iojs \ No newline at end of file +script: gulp \ No newline at end of file diff --git a/gulpfile.js b/gulpfile.js index 9aafd5d..8182f18 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -9,7 +9,10 @@ const documentation = require('gulp-documentation'), sloc = require('gulp-sloc'); const SRC_FILES = ['lib/**/*.js']; -const TEST_FILES = ['test/**/*_test.js']; +const TEST_FILES = [ + 'test/*_test.js', + 'test/adapters/*_test.js' +]; const ESLINT_SETTINGS = { "env": { @@ -22,7 +25,9 @@ const ESLINT_SETTINGS = { "no-constant-condition": [1], "no-extra-semi": [1], "no-func-assign": [1], + "no-obj-calls": [2], "no-unexpected-multiline" : [2], + "no-unneeded-ternary": [2], "radix": [2], "no-with": [2], "no-eval": [2], @@ -42,7 +47,9 @@ const ESLINT_SETTINGS = { "no-var": [2], "valid-jsdoc": [1], "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'], () => { - return pipe(gulp.src(TEST_FILES), [ + return pipe(gulp.src(['test/**/*.js']), [ eslint(ESLINT_SETTINGS), eslint.format(), eslint.failAfterError() @@ -81,7 +88,13 @@ gulp.task('mocha', ['lint-tests', 'sloc'], () => { bail: true, //reporter: 'dot', //reporter: 'landing', - })); + })) + .once('error', () => { + process.exit(1); + }) + .once('end', () => { + process.exit(); + }); }); gulp.task('test', ['test-sloc', 'lint-tests'], function(cb) { @@ -98,6 +111,12 @@ gulp.task('test', ['test-sloc', 'lint-tests'], function(cb) { dir: './coverage', reporters: ['lcov', 'lcovonly', 'html', 'text'] }) + .once('error', () => { + process.exit(1); + }) + .once('end', () => { + process.exit(); + }) ]); }); }); diff --git a/lib/NodeQuery.js b/lib/NodeQuery.js index d49c8fc..ed43bf9 100755 --- a/lib/NodeQuery.js +++ b/lib/NodeQuery.js @@ -1,6 +1,5 @@ "use strict"; -let instance = null; let fs = require('fs'), helpers = require('./helpers'), QueryBuilder = require('./QueryBuilder'); @@ -22,7 +21,6 @@ class NodeQuery { /** * Create a query builder object * - * @memberOf NodeQuery * @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 {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; let paths = { - driver: `${__dirname}/drivers/` + helpers.upperCaseFirst(driverType), + driver: `${__dirname}/drivers/${helpers.upperCaseFirst(driverType)}`, adapter: `${__dirname}/adapters/${connLib}` }; Object.keys(paths).forEach(type => { - if ( ! fs.existsSync(paths[type])) - { + try { + fs.statSync(`${paths[type]}.js`); + } catch(e) { throw new Error( - `Selected ${type} (` + - helpers.upperCaseFirst(driverType) + - `) does not exist!` + `Selected ${type} (${helpers.upperCaseFirst(driverType)}) does not exist!` ); } }); @@ -62,13 +59,12 @@ class NodeQuery { * @return {QueryBuilder} - The Query Builder object */ getQuery() { - if ( ! this.instance) { + if (this.instance == null) { throw new Error("No Query Builder instance to return"); } return this.instance; } - } module.exports = new NodeQuery(); \ No newline at end of file diff --git a/lib/QueryBuilder.js b/lib/QueryBuilder.js index 4c14a25..063814b 100755 --- a/lib/QueryBuilder.js +++ b/lib/QueryBuilder.js @@ -261,7 +261,7 @@ module.exports = class QueryBuilder { let args = getArgs('key:string, val:array, inClause:string, conj:string', arguments); args.key = this.driver.quoteIdentifiers(args.key); - let params = new Array(args.val.length); + let params = Array(args.val.length); params.fill('?'); args.val.forEach(value => { diff --git a/lib/QueryParser.js b/lib/QueryParser.js index a0510de..8a46351 100644 --- a/lib/QueryParser.js +++ b/lib/QueryParser.js @@ -114,8 +114,6 @@ module.exports = class QueryParser { */ compileJoin(condition) { let parts = this.parseJoin(condition); - let count = parts.identifiers.length; - let i; // Quote the identifiers parts.combined.forEach((part, i) => { diff --git a/lib/drivers/Firebird.js b/lib/drivers/Firebird.js index 2962456..c1bdcce 100644 --- a/lib/drivers/Firebird.js +++ b/lib/drivers/Firebird.js @@ -1,6 +1,6 @@ "use strict"; -var helpers = require('../helpers'); +let helpers = require('../helpers'); /** * Driver for Firebird databases @@ -9,7 +9,7 @@ var helpers = require('../helpers'); */ module.exports = (function() { delete require.cache[require.resolve('../Driver')]; - var driver = require('../Driver'); + let driver = require('../Driver'); driver.hasTruncate = false; @@ -22,7 +22,7 @@ module.exports = (function() { * @return {String} */ driver.limit = function(origSql, limit, offset) { - var sql = 'FIRST ' + limit; + let sql = 'FIRST ' + limit; if (helpers.isNumber(offset)) { diff --git a/lib/drivers/Mysql.js b/lib/drivers/Mysql.js index 0a6d02f..e73d214 100755 --- a/lib/drivers/Mysql.js +++ b/lib/drivers/Mysql.js @@ -7,7 +7,7 @@ */ module.exports = (function() { delete require.cache[require.resolve('../Driver')]; - var driver = require('../Driver'), + let driver = require('../Driver'), helpers = require('../helpers'); driver.identifierStartChar = '`'; diff --git a/lib/drivers/Pg.js b/lib/drivers/Pg.js index 1263c71..4ae2bc5 100755 --- a/lib/drivers/Pg.js +++ b/lib/drivers/Pg.js @@ -7,7 +7,7 @@ */ module.exports = (function() { delete require.cache[require.resolve('../Driver')]; - var driver = require('../Driver'); + let driver = require('../Driver'); return driver; }()); \ No newline at end of file diff --git a/lib/drivers/Sqlite.js b/lib/drivers/Sqlite.js index f3ce53c..ce8f74a 100644 --- a/lib/drivers/Sqlite.js +++ b/lib/drivers/Sqlite.js @@ -7,7 +7,7 @@ */ module.exports = (function() { delete require.cache[require.resolve('../Driver')]; - var driver = require('../Driver'), + let driver = require('../Driver'), helpers = require('../helpers'); // Sqlite doesn't have a truncate command @@ -25,7 +25,7 @@ module.exports = (function() { // Get the data values to insert, so they can // be parameterized - var sql = "", + let sql = "", vals = [], cols = [], fields = [], @@ -36,7 +36,7 @@ module.exports = (function() { data.forEach(function(obj) { - var row = []; + let row = []; Object.keys(obj).forEach(function(key) { row.push(obj[key]); }); @@ -55,7 +55,7 @@ module.exports = (function() { sql += "SELECT " + cols.join(', ') + "\n"; vals.forEach(function(row_values) { - var quoted = row_values.map(function(value) { + let quoted = row_values.map(function(value) { return String(value).replace("'", "'\'"); }); sql += "UNION ALL SELECT '" + quoted.join("', '") + "'\n"; diff --git a/package.json b/package.json index d78025c..17b5644 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "chai": "", "documentation": "", "eslint": "", + "glob": "^6.0.1", "gulp": "", "gulp-documentation": "^2.1.0", "gulp-eslint": "", diff --git a/test/adapters/adapterTestBase.js b/test/adapters/adapterTestBase.js new file mode 100644 index 0000000..bebf4b3 --- /dev/null +++ b/test/adapters/adapterTestBase.js @@ -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); + } + }); + }); + }); + }); + }); +}; \ No newline at end of file diff --git a/test/adapters/dblite_test.js b/test/adapters/dblite_test.js new file mode 100644 index 0000000..dbe5b6c --- /dev/null +++ b/test/adapters/dblite_test.js @@ -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(); + }); + }); +} \ No newline at end of file diff --git a/test/adapters/mysql2_test.js b/test/adapters/mysql2_test.js new file mode 100644 index 0000000..a4a8950 --- /dev/null +++ b/test/adapters/mysql2_test.js @@ -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(); + }); +}); \ No newline at end of file diff --git a/test/adapters/mysql_test.js b/test/adapters/mysql_test.js new file mode 100644 index 0000000..6319fc0 --- /dev/null +++ b/test/adapters/mysql_test.js @@ -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(); + }); +}); \ No newline at end of file diff --git a/test/adapters/node-firebird_test.js b/test/adapters/node-firebird_test.js new file mode 100644 index 0000000..c92f2a4 --- /dev/null +++ b/test/adapters/node-firebird_test.js @@ -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(); + }); + }); +}); \ No newline at end of file diff --git a/test/adapters/pg_test.js b/test/adapters/pg_test.js new file mode 100644 index 0000000..c81680e --- /dev/null +++ b/test/adapters/pg_test.js @@ -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(); + }); +}); \ No newline at end of file diff --git a/test/base_test.js b/test/base_test.js index f0c773d..177fc34 100644 --- a/test/base_test.js +++ b/test/base_test.js @@ -1,39 +1,35 @@ 'use strict'; -let assert = require('chai').assert; -let nodeQuery = require('../lib/NodeQuery'); +let expect = require('chai').expect, + reload = require('require-reload')(require), + glob = require('glob'), + nodeQuery = reload('../lib/NodeQuery'); suite('Base tests', () => { - test('Sanity check', () => { - let modules = { - helpers: require('../lib/helpers'), - driver: require('../lib/DriverBase'), - qb: require('../lib/QueryBuilder'), - 'node-query': require('../lib/NodeQuery'), - 'state': require('../lib/State'), - '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"); + suite('Sanity check', () => { + let files = glob.sync(`${__dirname}/../lib/**/*.js`); + files.forEach(mod => { + let obj = require(mod); + let shortName = mod.replace(/^\/(.*?)\/lib\/(.*?)\.js$/g, "$2"); + test(`${shortName} module is sane`, () => { + expect(obj).to.be.ok; + }); }); }); test('NodeQuery.getQuery with no instance', () => { - assert.throws(() => { - nodeQuery.getQuery(); - }, Error, "No Query Builder instance to return"); + // Hack for testing to work around node + // module caching + let nodeQueryCopy = Object.create(nodeQuery); + nodeQueryCopy.instance = null; + expect(() => { + nodeQueryCopy.getQuery(); + }).to.throw(Error, "No Query Builder instance to return"); }); test('Invalid driver type', () => { - assert.throws(() => { + expect(() => { nodeQuery.init('foo', {}, 'bar'); - }, Error, "Selected driver (Foo) does not exist!"); + }).to.throw(Error, "Selected driver (Foo) does not exist!"); }); }); \ No newline at end of file diff --git a/test/mocha.opts b/test/mocha.opts new file mode 100644 index 0000000..8e91e62 --- /dev/null +++ b/test/mocha.opts @@ -0,0 +1,4 @@ +--ui tdd +--bail +--recursive +--reporter nyan \ No newline at end of file diff --git a/test/query-parser_test.js b/test/query-parser_test.js index d136f6c..750be54 100644 --- a/test/query-parser_test.js +++ b/test/query-parser_test.js @@ -5,7 +5,7 @@ let expect = require('chai').expect; // Use the base driver as a mock for testing let getArgs = require('getargs'); let helpers = require('../lib/helpers'); -let driver = require('../lib/DriverBase'); +let driver = require('../lib/Driver'); let p = require('../lib/QueryParser'); let parser = new p(driver);