From 0f464770a7e793b584b9ab199d58aa5b4f9fa85f Mon Sep 17 00:00:00 2001 From: Elias Date: Fri, 23 Sep 2016 16:34:52 +0100 Subject: [PATCH 01/40] Add skeleton of plugin and example hapi server. Issue #30 --- examples/index.js | 19 +++++++++++++++++++ lib/index.js | 11 +++++++++++ package.json | 1 + 3 files changed, 31 insertions(+) create mode 100644 examples/index.js create mode 100644 lib/index.js diff --git a/examples/index.js b/examples/index.js new file mode 100644 index 0000000..28f4e9e --- /dev/null +++ b/examples/index.js @@ -0,0 +1,19 @@ +'use strict'; + +var Hapi = require('hapi'); +var plugin = require('../lib/index.js'); +var server = new Hapi.Server(); + +server.connection({ + port: process.env.PORT || 8888, +}); + +server.register(plugin, function (err) { + if (err) { + throw err; + } + + server.start(function () { + console.log('Server started'); + }); +}); diff --git a/lib/index.js b/lib/index.js new file mode 100644 index 0000000..4638d7b --- /dev/null +++ b/lib/index.js @@ -0,0 +1,11 @@ +'use strict'; + +exports.register = function (server, options, next) { + next(); +}; + +exports.register.attributes = { + pkg: { + name: 'Abase' + } +}; diff --git a/package.json b/package.json index 5b6f55e..1070ed0 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "homepage": "https://github.com/dwyl/abase#readme", "devDependencies": { "eslint": "^3.5.0", + "hapi": "^15.0.3", "istanbul": "^0.4.5", "pre-commit": "^1.1.3", "tape": "^4.6.0" From 70b3e00d9aff82042e1a49b0197a1ae0d647708b Mon Sep 17 00:00:00 2001 From: Elias Date: Fri, 23 Sep 2016 17:03:57 +0100 Subject: [PATCH 02/40] Add env2 for DB config: issue #34 --- .gitignore | 1 + examples/index.js | 1 + package.json | 4 +++- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 3c4d67a..4d4e3b8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ node_modules .DS_Store coverage +config.env diff --git a/examples/index.js b/examples/index.js index 28f4e9e..b9fb938 100644 --- a/examples/index.js +++ b/examples/index.js @@ -1,5 +1,6 @@ 'use strict'; +require('env2')('config.env'); var Hapi = require('hapi'); var plugin = require('../lib/index.js'); var server = new Hapi.Server(); diff --git a/package.json b/package.json index 1070ed0..822606b 100644 --- a/package.json +++ b/package.json @@ -30,5 +30,7 @@ "pre-commit": [ "lint", "test" - ] + ], + "dependencies": { + "env2": "^2.1.1", } From 80522d4c6f5bd4f50ccb4456173305a8feb296de Mon Sep 17 00:00:00 2001 From: Elias Date: Fri, 23 Sep 2016 17:06:47 +0100 Subject: [PATCH 03/40] add hapi-postgres-connection as a plugin to example server #33 --- examples/index.js | 5 +++-- lib/index.js | 15 +++++++++++++++ package.json | 2 ++ 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/examples/index.js b/examples/index.js index b9fb938..447ec32 100644 --- a/examples/index.js +++ b/examples/index.js @@ -2,14 +2,15 @@ require('env2')('config.env'); var Hapi = require('hapi'); -var plugin = require('../lib/index.js'); +var Abase = require('../lib/index.js'); +var HapiPg = require('hapi-postgres-connection'); var server = new Hapi.Server(); server.connection({ port: process.env.PORT || 8888, }); -server.register(plugin, function (err) { +server.register([HapiPg, Abase], function (err) { if (err) { throw err; } diff --git a/lib/index.js b/lib/index.js index 4638d7b..9a03fb6 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,6 +1,21 @@ 'use strict'; exports.register = function (server, options, next) { + server.dependency(['hapi-postgres-connection'], function (_server, _next) { + _server.route({ + method: 'GET', + path: '/', + handler: function (request, reply) { + var select = 'select table_name from information_schema.tables'; + + request.pg.client.query(select, function(err, result) { + return reply(result); + }); + } + }); + + _next(); + }); next(); }; diff --git a/package.json b/package.json index 822606b..914fb52 100644 --- a/package.json +++ b/package.json @@ -33,4 +33,6 @@ ], "dependencies": { "env2": "^2.1.1", + "hapi-postgres-connection": "^6.1.0" + } } From e834b0b523815bf48f795a0650b62b1c8c76665f Mon Sep 17 00:00:00 2001 From: Elias Date: Fri, 23 Sep 2016 18:11:32 +0100 Subject: [PATCH 04/40] use pg instead of hapi-postgres-connection because we need access to the DB outside the request lifecycle: issue #33 --- example_config.json => examples/config.json | 0 examples/index.js | 4 +-- lib/index.js | 36 +++++++++++++-------- package.json | 2 +- 4 files changed, 26 insertions(+), 16 deletions(-) rename example_config.json => examples/config.json (100%) diff --git a/example_config.json b/examples/config.json similarity index 100% rename from example_config.json rename to examples/config.json diff --git a/examples/index.js b/examples/index.js index 447ec32..486d1b7 100644 --- a/examples/index.js +++ b/examples/index.js @@ -3,14 +3,14 @@ require('env2')('config.env'); var Hapi = require('hapi'); var Abase = require('../lib/index.js'); -var HapiPg = require('hapi-postgres-connection'); var server = new Hapi.Server(); +var config = require('./config.json'); server.connection({ port: process.env.PORT || 8888, }); -server.register([HapiPg, Abase], function (err) { +server.register({ register: Abase, options: config }, function (err) { if (err) { throw err; } diff --git a/lib/index.js b/lib/index.js index 9a03fb6..d35f12d 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,22 +1,32 @@ 'use strict'; +var pg = require('pg'); + exports.register = function (server, options, next) { - server.dependency(['hapi-postgres-connection'], function (_server, _next) { - _server.route({ - method: 'GET', - path: '/', - handler: function (request, reply) { - var select = 'select table_name from information_schema.tables'; - request.pg.client.query(select, function(err, result) { - return reply(result); - }); - } - }); + var dbUrl = process.env.DATABASE_URL; + server.app.pool = new pg.Pool(dbUrl); + + server.route({ + method: 'GET', + path: '/', + handler: function (request, reply) { + var select = 'select table_name from information_schema.tables'; + + _server.app.pool.query(select, function(err, result) { + return reply(result); + }); + } + }); - _next(); + server.app.pool.connect().then(function (client) { + client.query('CREATE TABLE IF NOT EXISTS "' + options.table + '" ()') + .then(function (result) { + console.log(result); + next(); + }) + .catch(next); }); - next(); }; exports.register.attributes = { diff --git a/package.json b/package.json index 914fb52..da1682e 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,6 @@ ], "dependencies": { "env2": "^2.1.1", - "hapi-postgres-connection": "^6.1.0" + "pg": "^6.1.0" } } From 228ebc4b2e5997dcb5c00a19cb9100b191ce75e4 Mon Sep 17 00:00:00 2001 From: Elias Date: Mon, 26 Sep 2016 10:12:29 +0100 Subject: [PATCH 05/40] update sql queries in plugin setup --- lib/index.js | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/lib/index.js b/lib/index.js index d35f12d..919d1c8 100644 --- a/lib/index.js +++ b/lib/index.js @@ -11,21 +11,22 @@ exports.register = function (server, options, next) { method: 'GET', path: '/', handler: function (request, reply) { - var select = 'select table_name from information_schema.tables'; + var select = 'select $1::text from information_schema.tables'; - _server.app.pool.query(select, function(err, result) { + server.app.pool.query(select, ['table_name'], function(err, result) { return reply(result); }); } }); - server.app.pool.connect().then(function (client) { - client.query('CREATE TABLE IF NOT EXISTS "' + options.table + '" ()') - .then(function (result) { - console.log(result); - next(); - }) - .catch(next); + server.app.pool.query('CREATE TABLE IF NOT EXISTS ' + + '"' + options.tableName + '"' + + ' (' + + 'email varchar(80),' + + 'username varchar(80)' + + ')' + , function (err, result) { + console.log(err, result); }); }; From b3c23d1ee83998f8b89945b844a2d2b121e8b5b8 Mon Sep 17 00:00:00 2001 From: Elias Date: Mon, 26 Sep 2016 10:12:41 +0100 Subject: [PATCH 06/40] add first pass of sql data map --- lib/psql.datatype.map.js | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 lib/psql.datatype.map.js diff --git a/lib/psql.datatype.map.js b/lib/psql.datatype.map.js new file mode 100644 index 0000000..dd49fa6 --- /dev/null +++ b/lib/psql.datatype.map.js @@ -0,0 +1,24 @@ +'use strict'; + +module.exports = { + int: function () { + return 'BIGINT'; + }, + text: function (opts) { + var length = opts.length || 80; + + return 'VARCHAR(' + length + ')'; + }, + bool: function () { + return 'BOOLEAN'; + }, + date: function () { + return 'DATE'; + }, + float: function () { + return 'DOUBLE PRECISION'; + }, + timestamp: function () { + return 'TIMESTAMP'; + } +}; From c435d441831ac2607eb0ca55280e8c64573ede53 Mon Sep 17 00:00:00 2001 From: Elias Date: Mon, 26 Sep 2016 10:13:31 +0100 Subject: [PATCH 07/40] [WIP] add module file for generating sql, and corresponding test file --- lib/sql.gen.js | 5 +++++ test/sql.gen.test.js | 17 +++++++++++++++++ 2 files changed, 22 insertions(+) create mode 100644 lib/sql.gen.js create mode 100644 test/sql.gen.test.js diff --git a/lib/sql.gen.js b/lib/sql.gen.js new file mode 100644 index 0000000..7135a00 --- /dev/null +++ b/lib/sql.gen.js @@ -0,0 +1,5 @@ +'use strict'; + +exports.init = function init (tableName) { + +}; diff --git a/test/sql.gen.test.js b/test/sql.gen.test.js new file mode 100644 index 0000000..614945d --- /dev/null +++ b/test/sql.gen.test.js @@ -0,0 +1,17 @@ +'use strict'; + +var tape = require('tape'); +var sqlGen = require('../lib/sql.gen.js'); + +tape('::init should generate empty string on invalid input', function () {}); + +tape('::init should generate SQL to create a table if none exists', function (t) { + var tableName = 'User'; + var query = sqlGen.init(tableName); + t.equal(query[0], 'CREATE TABLE IF NOT EXISTS $1'); + t.equal(query[1], tableName); + t.end(); +}); + +tape('::update should generate empty string on invalid input', function () {}); +tape('::update should generate SQL to update a column in a table', function () {}); From 582139910cf7d67d317d7ef52289861f231170d1 Mon Sep 17 00:00:00 2001 From: Jack Rans Date: Mon, 26 Sep 2016 11:15:34 +0100 Subject: [PATCH 08/40] :heavy_plus_sign: mapper for creating sql columns + mapper that takes name, type and extra config options and produces sql string to create a column for setting up table + works for both pqsl and sql --- ...ql.datatype.map.js => create_table_map.js} | 11 +++- lib/{sql.gen.js => sql_gen.js} | 0 test/create_table_map.test.js | 55 +++++++++++++++++++ test/sql.gen.test.js | 17 ------ test/sql_gen.test.js | 17 ++++++ 5 files changed, 81 insertions(+), 19 deletions(-) rename lib/{psql.datatype.map.js => create_table_map.js} (62%) rename lib/{sql.gen.js => sql_gen.js} (100%) create mode 100644 test/create_table_map.test.js delete mode 100644 test/sql.gen.test.js create mode 100644 test/sql_gen.test.js diff --git a/lib/psql.datatype.map.js b/lib/create_table_map.js similarity index 62% rename from lib/psql.datatype.map.js rename to lib/create_table_map.js index dd49fa6..019fdc4 100644 --- a/lib/psql.datatype.map.js +++ b/lib/create_table_map.js @@ -1,11 +1,11 @@ 'use strict'; -module.exports = { +var mapObj = { int: function () { return 'BIGINT'; }, text: function (opts) { - var length = opts.length || 80; + var length = (opts && opts.length) || 80; return 'VARCHAR(' + length + ')'; }, @@ -22,3 +22,10 @@ module.exports = { return 'TIMESTAMP'; } }; + +function mapper (name, type, options) { + return name + ' ' + mapObj[type](options); +}; + +module.exports = mapper; +module.exports.mapObj = mapObj; diff --git a/lib/sql.gen.js b/lib/sql_gen.js similarity index 100% rename from lib/sql.gen.js rename to lib/sql_gen.js diff --git a/test/create_table_map.test.js b/test/create_table_map.test.js new file mode 100644 index 0000000..cd0e941 --- /dev/null +++ b/test/create_table_map.test.js @@ -0,0 +1,55 @@ +'use strict'; + +var test = require('tape'); + +var mapper = require('../lib/create_table_map.js'); + +var mapObj = mapper.mapObj; + +test('Create Table Mapper Obj', function (t) { + t.equal( + mapObj['bool'](), + 'BOOLEAN', + 'bool type' + ); + t.equal( + mapObj['date'](), + 'DATE', + 'date type' + ); + t.equal( + mapObj['float'](), + 'DOUBLE PRECISION', + 'float type' + ); + t.equal( + mapObj['timestamp'](), + 'TIMESTAMP', + 'timestamp type' + ); + t.equal( + mapObj['int'](), + 'BIGINT', + 'int type, note uses big int to allow for big numbers!!!!' + ); + t.equal( + mapObj['text'](), + 'VARCHAR(80)', + 'text type: default' + ); + t.equal( + mapObj['text']({ length: 12 }), + 'VARCHAR(12)', + 'text type: specifies length' + ); + t.end(); +}); + +test('Create Table Mapper Function', function (t) { + t.equal( + mapper('field', 'text', { length: 140 }), + 'field VARCHAR(140)', + 'name added to sql query and options passed through' + ); + t.end(); +}); diff --git a/test/sql.gen.test.js b/test/sql.gen.test.js deleted file mode 100644 index 614945d..0000000 --- a/test/sql.gen.test.js +++ /dev/null @@ -1,17 +0,0 @@ -'use strict'; - -var tape = require('tape'); -var sqlGen = require('../lib/sql.gen.js'); - -tape('::init should generate empty string on invalid input', function () {}); - -tape('::init should generate SQL to create a table if none exists', function (t) { - var tableName = 'User'; - var query = sqlGen.init(tableName); - t.equal(query[0], 'CREATE TABLE IF NOT EXISTS $1'); - t.equal(query[1], tableName); - t.end(); -}); - -tape('::update should generate empty string on invalid input', function () {}); -tape('::update should generate SQL to update a column in a table', function () {}); diff --git a/test/sql_gen.test.js b/test/sql_gen.test.js new file mode 100644 index 0000000..5e7e85f --- /dev/null +++ b/test/sql_gen.test.js @@ -0,0 +1,17 @@ +// 'use strict'; +// +// var tape = require('tape'); +// var sqlGen = require('../lib/sql.gen.js'); +// +// tape('::init should generate empty string on invalid input', function () {}); +// +// tape('::init should generate SQL to create a table if none exists', function (t) { +// var tableName = 'User'; +// var query = sqlGen.init(tableName); +// t.equal(query[0], 'CREATE TABLE IF NOT EXISTS $1'); +// t.equal(query[1], tableName); +// t.end(); +// }); +// +// tape('::update should generate empty string on invalid input', function () {}); +// tape('::update should generate SQL to update a column in a table', function () {}); From e059f22017b19a5e27d4057aa0c03ead56425085 Mon Sep 17 00:00:00 2001 From: Elias Date: Mon, 26 Sep 2016 12:27:55 +0100 Subject: [PATCH 09/40] change field-names to align with Joi API; moved away from JSON schema, because too complicated. Issue #11, #26 --- lib/create_table_map.js | 24 ++++++--------- test/create_table_map.test.js | 57 ++++++++++++++++++++++++----------- 2 files changed, 49 insertions(+), 32 deletions(-) diff --git a/lib/create_table_map.js b/lib/create_table_map.js index 019fdc4..701e5a5 100644 --- a/lib/create_table_map.js +++ b/lib/create_table_map.js @@ -1,30 +1,26 @@ 'use strict'; var mapObj = { - int: function () { - return 'BIGINT'; + number: function (opts) { + return opts.integer ? 'BIGINT' : 'DOUBLE PRECISION'; }, - text: function (opts) { - var length = (opts && opts.length) || 80; + string: function (opts) { + var length = opts.max || 80; return 'VARCHAR(' + length + ')'; }, - bool: function () { + boolean: function () { return 'BOOLEAN'; }, - date: function () { - return 'DATE'; - }, - float: function () { - return 'DOUBLE PRECISION'; - }, - timestamp: function () { - return 'TIMESTAMP'; + date: function (opts) { + return opts.timestamp ? 'TIMESTAMP' : 'DATE'; } }; function mapper (name, type, options) { - return name + ' ' + mapObj[type](options); + var opts = options || {}; + + return name + ' ' + mapObj[type](opts); }; module.exports = mapper; diff --git a/test/create_table_map.test.js b/test/create_table_map.test.js index cd0e941..389575e 100644 --- a/test/create_table_map.test.js +++ b/test/create_table_map.test.js @@ -6,50 +6,71 @@ var mapper = require('../lib/create_table_map.js'); var mapObj = mapper.mapObj; -test('Create Table Mapper Obj', function (t) { +test('Boolean type', function (t) { t.equal( - mapObj['bool'](), + mapObj['boolean']({}), 'BOOLEAN', - 'bool type' + 'boolean type: default' ); + t.end(); +}); + +test('Date type', function (t) { t.equal( - mapObj['date'](), + mapObj['date']({}), 'DATE', - 'date type' + 'date type: default' ); t.equal( - mapObj['float'](), - 'DOUBLE PRECISION', - 'float type' + mapObj['date']({ timestamp: true }), + 'TIMESTAMP', + 'date type: timestamp' ); + t.end(); +}); + +test('Number type', function (t) { t.equal( - mapObj['timestamp'](), - 'TIMESTAMP', - 'timestamp type' + mapObj['number']({}), + 'DOUBLE PRECISION', + 'number type: default' ); t.equal( - mapObj['int'](), + mapObj['number']({ integer: true }), 'BIGINT', - 'int type, note uses big int to allow for big numbers!!!!' + 'number type: integer' ); + t.end(); +}); + +test('String type', function (t) { t.equal( - mapObj['text'](), + mapObj['string']({}), 'VARCHAR(80)', - 'text type: default' + 'string type: default' ); t.equal( - mapObj['text']({ length: 12 }), + mapObj['string']({ max: 12 }), 'VARCHAR(12)', - 'text type: specifies length' + 'string type: specifies length' ); t.end(); }); test('Create Table Mapper Function', function (t) { t.equal( - mapper('field', 'text', { length: 140 }), + mapper('field', 'string', { max: 140 }), 'field VARCHAR(140)', 'name added to sql query and options passed through' ); t.end(); }); + +test('Create Table Mapper Function w/ no options', function (t) { + t.equal( + mapper('field', 'string'), + 'field VARCHAR(80)', + 'name added to sql query and default options used' + ); + t.end(); +}); From da0130570a5203368995882a70abb1d6021f0950 Mon Sep 17 00:00:00 2001 From: Elias Date: Mon, 26 Sep 2016 12:43:05 +0100 Subject: [PATCH 10/40] write and test init method of sqlGen module to make query to create table with the correct fields and types; issue #11 --- lib/sql_gen.js | 19 ++++++++++++-- test/sql_gen.test.js | 61 +++++++++++++++++++++++++++++++++----------- 2 files changed, 63 insertions(+), 17 deletions(-) diff --git a/lib/sql_gen.js b/lib/sql_gen.js index 7135a00..cf858d5 100644 --- a/lib/sql_gen.js +++ b/lib/sql_gen.js @@ -1,5 +1,20 @@ 'use strict'; -exports.init = function init (tableName) { - +var mapper = require('./create_table_map.js'); + +exports.init = function init (config) { + var tableName = config.table_name; + var fields = config.fields; + var query = 'CREATE TABLE IF NOT EXISTS "' + tableName + '" '; + + var columns = Object.keys(fields).map(function (key) { + var type = fields[key].type; + var opts = fields[key]; + + return mapper(key, type, opts); + }); + + query += '(' + columns.join(', ') + ')'; + + return query; }; diff --git a/test/sql_gen.test.js b/test/sql_gen.test.js index 5e7e85f..8066b7f 100644 --- a/test/sql_gen.test.js +++ b/test/sql_gen.test.js @@ -1,17 +1,48 @@ -// 'use strict'; -// -// var tape = require('tape'); -// var sqlGen = require('../lib/sql.gen.js'); -// -// tape('::init should generate empty string on invalid input', function () {}); -// -// tape('::init should generate SQL to create a table if none exists', function (t) { -// var tableName = 'User'; -// var query = sqlGen.init(tableName); -// t.equal(query[0], 'CREATE TABLE IF NOT EXISTS $1'); -// t.equal(query[1], tableName); -// t.end(); -// }); -// +'use strict'; + +var tape = require('tape'); +var sqlGen = require('../lib/sql_gen.js'); + +var schema = { + table_name: 'user_data', + fields: { + email: { + type: 'string', + email: true + }, + username: { + type: 'string', + min: 3, + max: 20 + }, + dob: { + type: 'date' + } + } +}; + +tape('::init should throw on empty or invalid input', function (t) { + t.throws(function () { + sqlGen.init(); + }); + t.end(); +}); + +tape('::init should generate SQL to create a table if none exists', function (t) { + var query = sqlGen.init(schema); + + t.equal( + query, + 'CREATE TABLE IF NOT EXISTS "user_data" (' + + 'email VARCHAR(80), ' + + 'username VARCHAR(20), ' + + 'dob DATE' + + ')', + 'Create table query generation from config object' + ); + t.end(); +}); + // tape('::update should generate empty string on invalid input', function () {}); + // tape('::update should generate SQL to update a column in a table', function () {}); From 3cb409a0815af88b523a4d7176290ff77b821283 Mon Sep 17 00:00:00 2001 From: Elias Date: Mon, 26 Sep 2016 13:20:33 +0100 Subject: [PATCH 11/40] write and test function to generate update SQL queries for arbitrary numbers of columns. --- lib/sql_gen.js | 17 +++++++++++++++++ test/sql_gen.test.js | 8 +++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/lib/sql_gen.js b/lib/sql_gen.js index cf858d5..d745305 100644 --- a/lib/sql_gen.js +++ b/lib/sql_gen.js @@ -18,3 +18,20 @@ exports.init = function init (config) { return query; }; + + +exports.update = function update (schema, fields) { + var tableName = schema.table_name; + + var columns = Object.keys(fields); + var values = columns.map(function (k) {return fields[k];}); + var params = columns.map(function (e, i) {return '$' + (i + 1);}); + + var query = ['INSERT INTO "' + tableName + '"'] + .concat('(' + columns.join(', ') + ')') + .concat('VALUES') + .concat('(' + params.join(', ') + ')') + .join(' '); + + return [query, values]; +}; diff --git a/test/sql_gen.test.js b/test/sql_gen.test.js index 8066b7f..4c4282d 100644 --- a/test/sql_gen.test.js +++ b/test/sql_gen.test.js @@ -45,4 +45,10 @@ tape('::init should generate SQL to create a table if none exists', function (t) // tape('::update should generate empty string on invalid input', function () {}); -// tape('::update should generate SQL to update a column in a table', function () {}); +tape('::update should generate SQL to update a column in a table', function (t) { + var query = sqlGen.update(schema, {email: 'me@poop.com'}); + + t.equal(query[0], 'INSERT INTO "user_data" (email) VALUES ($1)', 'Generate parameterised query'); + t.deepEqual(query[1], ['me@poop.com'], 'Generate values for parameterised query'); + t.end(); +}); From 3110924d56e92ea924433602b5df69ab645b9380 Mon Sep 17 00:00:00 2001 From: Elias Date: Mon, 26 Sep 2016 15:24:24 +0100 Subject: [PATCH 12/40] rename previous 'update' method to 'insert' (since that's what it does). Write and test an update method; issue #11 --- lib/sql_gen.js | 18 +++++++++++++++++- test/sql_gen.test.js | 10 +++++++++- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/lib/sql_gen.js b/lib/sql_gen.js index d745305..8a88695 100644 --- a/lib/sql_gen.js +++ b/lib/sql_gen.js @@ -20,7 +20,7 @@ exports.init = function init (config) { }; -exports.update = function update (schema, fields) { +exports.insert = function insert (schema, fields) { var tableName = schema.table_name; var columns = Object.keys(fields); @@ -35,3 +35,19 @@ exports.update = function update (schema, fields) { return [query, values]; }; + +exports.update = function update (schema, fields) { + var tableName = schema.table_name; + + var columns = Object.keys(fields).map(function (k, i) { + return k + '=$' + (i + 1); + }); + var values = Object.keys(fields).map(function (k) {return fields[k];}); + + var query = ['UPDATE "' + tableName + '"'] + .concat('SET') + .concat(columns.join(', ')) + .join(' '); + + return [query, values]; +}; diff --git a/test/sql_gen.test.js b/test/sql_gen.test.js index 4c4282d..d719693 100644 --- a/test/sql_gen.test.js +++ b/test/sql_gen.test.js @@ -45,10 +45,18 @@ tape('::init should generate SQL to create a table if none exists', function (t) // tape('::update should generate empty string on invalid input', function () {}); +tape('::insert should generate SQL to insert a column into a table', function (t) { + var query = sqlGen.insert(schema, {email: 'me@poop.com'}); + + t.equal(query[0], 'INSERT INTO "user_data" (email) VALUES ($1)', 'Generate parameterised query'); + t.deepEqual(query[1], ['me@poop.com'], 'Generate values for parameterised query'); + t.end(); +}); + tape('::update should generate SQL to update a column in a table', function (t) { var query = sqlGen.update(schema, {email: 'me@poop.com'}); - t.equal(query[0], 'INSERT INTO "user_data" (email) VALUES ($1)', 'Generate parameterised query'); + t.equal(query[0], 'UPDATE "user_data" SET email=$1', 'Generate parameterised query'); t.deepEqual(query[1], ['me@poop.com'], 'Generate values for parameterised query'); t.end(); }); From 9c5fb297634a13507f81da1ce358673b4567bfa8 Mon Sep 17 00:00:00 2001 From: Elias Date: Mon, 26 Sep 2016 15:30:44 +0100 Subject: [PATCH 13/40] delete dead line --- test/sql_gen.test.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/test/sql_gen.test.js b/test/sql_gen.test.js index d719693..571a2ac 100644 --- a/test/sql_gen.test.js +++ b/test/sql_gen.test.js @@ -43,10 +43,8 @@ tape('::init should generate SQL to create a table if none exists', function (t) t.end(); }); -// tape('::update should generate empty string on invalid input', function () {}); - tape('::insert should generate SQL to insert a column into a table', function (t) { - var query = sqlGen.insert(schema, {email: 'me@poop.com'}); + var query = sqlGen.insert(schema, { email: 'me@poop.com' }); t.equal(query[0], 'INSERT INTO "user_data" (email) VALUES ($1)', 'Generate parameterised query'); t.deepEqual(query[1], ['me@poop.com'], 'Generate values for parameterised query'); @@ -54,7 +52,7 @@ tape('::insert should generate SQL to insert a column into a table', function (t }); tape('::update should generate SQL to update a column in a table', function (t) { - var query = sqlGen.update(schema, {email: 'me@poop.com'}); + var query = sqlGen.update(schema, { email: 'me@poop.com' }); t.equal(query[0], 'UPDATE "user_data" SET email=$1', 'Generate parameterised query'); t.deepEqual(query[1], ['me@poop.com'], 'Generate values for parameterised query'); From a45b93f4ba56b4d498c8f32cbe31707df029ae1a Mon Sep 17 00:00:00 2001 From: Jack Rans Date: Mon, 26 Sep 2016 15:21:01 +0100 Subject: [PATCH 14/40] :heavy_plus_sign: config validator + use joi to validate the config for creating a table Related: #39 --- lib/config_validator.js | 20 +++++++++++++ test/config_validator.test.js | 55 +++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) create mode 100644 lib/config_validator.js create mode 100644 test/config_validator.test.js diff --git a/lib/config_validator.js b/lib/config_validator.js new file mode 100644 index 0000000..f6a7c3d --- /dev/null +++ b/lib/config_validator.js @@ -0,0 +1,20 @@ +var Joi = require('joi'); + +var mapObj = require('./create_table_map.js').mapObj; + +var fieldTypes = Object.keys(mapObj); +var dbNameRegEx = /^[A-Za-z_]\w{0,62}$/; + +var configSchema = Joi.object().keys({ + table_name: Joi.string().regex(dbNameRegEx).required(), + fields: Joi.object().pattern( + dbNameRegEx, + Joi.object().keys({type: Joi.any().valid(fieldTypes)}) + ).required() +}); + +module.exports = function (config) { + return Joi.validate(config, configSchema); +}; + +module.exports.dbNameRegEx = dbNameRegEx; diff --git a/test/config_validator.test.js b/test/config_validator.test.js new file mode 100644 index 0000000..92454f8 --- /dev/null +++ b/test/config_validator.test.js @@ -0,0 +1,55 @@ +var test = require('tape'); + +var validator = require('../lib/config_validator.js'); +var dbNameRegEx = validator.dbNameRegEx; + +test('config validator', function (t) { + t.ok( + validator({ fields: {} }).error, + 'error if no table_name property' + ); + t.ok( + validator({ table_name: 'test' }).error, + 'error if no fields property' + ); + t.ok( + validator({ table_name: '2test', fields: {} }).error, + 'error if table name doesn\t pass db name regex' + ); + t.ok( + validator({ table_name: '2test', fields: {} }).error, + 'error if table name doesn\t pass db name regex' + ); + t.ok( + validator({ + table_name: 'test', + fields: {'2field': {type: 'string'}} + }).error, + 'error if field name doesn\'t pass db name regex' + ); + + t.end(); +}); + +test('dbNameRegEx', function (t) { + t.ok( + dbNameRegEx.exec('_a1pha_Numer1c'), + 'alpha numeric keys allowed only' + ); + t.notOk( + dbNameRegEx.exec('no£way'), + 'no other characters allowed' + ); + t.notOk( + dbNameRegEx.exec('3Numer1c'), + 'must only start with a _ or letter' + ); + t.notOk( + dbNameRegEx.exec( + '_morethan63characters_morethan63characters_morethan63characters_' + ), + '63 character limit for field names' + ); + + t.end(); +}); From 431e786368b876e1eeb8639989611792230c2d05 Mon Sep 17 00:00:00 2001 From: Elias Date: Mon, 26 Sep 2016 16:12:03 +0100 Subject: [PATCH 15/40] change module inputs to inject table_name directly instead of entire schema object --- lib/sql_gen.js | 10 +++++----- test/sql_gen.test.js | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/sql_gen.js b/lib/sql_gen.js index 8a88695..09a9371 100644 --- a/lib/sql_gen.js +++ b/lib/sql_gen.js @@ -14,15 +14,16 @@ exports.init = function init (config) { return mapper(key, type, opts); }); - query += '(' + columns.join(', ') + ')'; + var query = ['CREATE TABLE IF NOT EXISTS "' + tableName + '"'] + .concat('(' + columns.join(', ') + ')') + .join(' '); return query; }; -exports.insert = function insert (schema, fields) { - var tableName = schema.table_name; +exports.insert = function insert (tableName, fields) { var columns = Object.keys(fields); var values = columns.map(function (k) {return fields[k];}); var params = columns.map(function (e, i) {return '$' + (i + 1);}); @@ -36,9 +37,8 @@ exports.insert = function insert (schema, fields) { return [query, values]; }; -exports.update = function update (schema, fields) { - var tableName = schema.table_name; +exports.update = function update (tableName, fields) { var columns = Object.keys(fields).map(function (k, i) { return k + '=$' + (i + 1); }); diff --git a/test/sql_gen.test.js b/test/sql_gen.test.js index 571a2ac..3079152 100644 --- a/test/sql_gen.test.js +++ b/test/sql_gen.test.js @@ -44,7 +44,7 @@ tape('::init should generate SQL to create a table if none exists', function (t) }); tape('::insert should generate SQL to insert a column into a table', function (t) { - var query = sqlGen.insert(schema, { email: 'me@poop.com' }); + var query = sqlGen.insert(schema.table_name, { email: 'me@poop.com' }); t.equal(query[0], 'INSERT INTO "user_data" (email) VALUES ($1)', 'Generate parameterised query'); t.deepEqual(query[1], ['me@poop.com'], 'Generate values for parameterised query'); @@ -52,7 +52,7 @@ tape('::insert should generate SQL to insert a column into a table', function (t }); tape('::update should generate SQL to update a column in a table', function (t) { - var query = sqlGen.update(schema, { email: 'me@poop.com' }); + var query = sqlGen.update(schema.table_name, { email: 'me@poop.com' }); t.equal(query[0], 'UPDATE "user_data" SET email=$1', 'Generate parameterised query'); t.deepEqual(query[1], ['me@poop.com'], 'Generate values for parameterised query'); From 9ee0209728ca41845791eea5b17ef68889986ea4 Mon Sep 17 00:00:00 2001 From: Elias Date: Mon, 26 Sep 2016 16:12:40 +0100 Subject: [PATCH 16/40] write and test sql generator for SELECT queries --- lib/sql_gen.js | 11 +++++++++++ test/sql_gen.test.js | 9 +++++++++ 2 files changed, 20 insertions(+) diff --git a/lib/sql_gen.js b/lib/sql_gen.js index 09a9371..cad9270 100644 --- a/lib/sql_gen.js +++ b/lib/sql_gen.js @@ -22,6 +22,17 @@ exports.init = function init (config) { }; +exports.select = function select (tableName, columns) { + var query = ['SELECT'] + .concat(columns.join(', ')) + .concat('FROM') + .concat('"' + tableName + '"') + .join(' '); + var values = []; + + return [query, values]; +}; + exports.insert = function insert (tableName, fields) { var columns = Object.keys(fields); diff --git a/test/sql_gen.test.js b/test/sql_gen.test.js index 3079152..5612be1 100644 --- a/test/sql_gen.test.js +++ b/test/sql_gen.test.js @@ -43,6 +43,15 @@ tape('::init should generate SQL to create a table if none exists', function (t) t.end(); }); +tape('::select should generate SQL to select columns from a table', function (t) { + var query = sqlGen.select(schema.table_name, ['email', 'dob']); + + t.equal(query[0], 'SELECT email, dob FROM "user_data"', 'Generate parameterised query'); + t.deepEqual(query[1], [], 'Generate values for parameterised query'); + t.end(); +}); + + tape('::insert should generate SQL to insert a column into a table', function (t) { var query = sqlGen.insert(schema.table_name, { email: 'me@poop.com' }); From 5cd25362d20057fe1ce3cde11986b951b312c143 Mon Sep 17 00:00:00 2001 From: Elias Date: Mon, 26 Sep 2016 16:13:14 +0100 Subject: [PATCH 17/40] write and test SQL generator function for DELETE queries --- lib/sql_gen.js | 15 +++++++++++++++ test/sql_gen.test.js | 8 ++++++++ 2 files changed, 23 insertions(+) diff --git a/lib/sql_gen.js b/lib/sql_gen.js index cad9270..2cdeb11 100644 --- a/lib/sql_gen.js +++ b/lib/sql_gen.js @@ -62,3 +62,18 @@ exports.update = function update (tableName, fields) { return [query, values]; }; + + +exports.delete = function _delete (tableName, where) { + var conditions = Object.keys(where).map(function (k, i) { + return k + '=$' + (i + 1); + }); + var values = Object.keys(where).map(function (k) {return where[k];}); + + var query = ['DELETE FROM "' + tableName + '"'] + .concat('WHERE') + .concat(conditions.join(', ')) + .join(' '); + + return [query, values]; +}; diff --git a/test/sql_gen.test.js b/test/sql_gen.test.js index 5612be1..2a40ace 100644 --- a/test/sql_gen.test.js +++ b/test/sql_gen.test.js @@ -67,3 +67,11 @@ tape('::update should generate SQL to update a column in a table', function (t) t.deepEqual(query[1], ['me@poop.com'], 'Generate values for parameterised query'); t.end(); }); + +tape('::delete should generate SQL to delete a row from a table', function (t) { + var query = sqlGen.delete(schema.table_name, { username: 'bob' }); + + t.equal(query[0], 'DELETE FROM "user_data" WHERE username=$1', 'Generate parameterised query'); + t.deepEqual(query[1], ['bob'], 'Generate values for parameterised query'); + t.end(); +}); From ad3139b3e591f42f5e2e1362042d45162c942634 Mon Sep 17 00:00:00 2001 From: Elias Date: Mon, 26 Sep 2016 19:18:14 +0100 Subject: [PATCH 18/40] migrate to new interface, expecting an options object instead to configure the query --- lib/sql_gen.js | 31 +++++++++++++++++++++++++------ test/sql_gen.test.js | 20 +++++++++++++++----- 2 files changed, 40 insertions(+), 11 deletions(-) diff --git a/lib/sql_gen.js b/lib/sql_gen.js index 2cdeb11..f7a8869 100644 --- a/lib/sql_gen.js +++ b/lib/sql_gen.js @@ -22,7 +22,8 @@ exports.init = function init (config) { }; -exports.select = function select (tableName, columns) { +exports.select = function select (tableName, options) { + var columns = options.select || ['*']; var query = ['SELECT'] .concat(columns.join(', ')) .concat('FROM') @@ -34,7 +35,8 @@ exports.select = function select (tableName, columns) { }; -exports.insert = function insert (tableName, fields) { +exports.insert = function insert (tableName, options) { + var fields = options.fields || {}; var columns = Object.keys(fields); var values = columns.map(function (k) {return fields[k];}); var params = columns.map(function (e, i) {return '$' + (i + 1);}); @@ -49,7 +51,8 @@ exports.insert = function insert (tableName, fields) { }; -exports.update = function update (tableName, fields) { +exports.update = function update (tableName, options) { + var fields = options.fields || {}; var columns = Object.keys(fields).map(function (k, i) { return k + '=$' + (i + 1); }); @@ -57,14 +60,30 @@ exports.update = function update (tableName, fields) { var query = ['UPDATE "' + tableName + '"'] .concat('SET') - .concat(columns.join(', ')) - .join(' '); + .concat(columns.join(', ')); + + if (options.where) { + var keys = Object.keys(options.where); + var conds = keys.map(function (k, i) { + return k + '=$' + (values.length + i + 1); + }); + var vals = keys.map(function (k) {return options.where[k];}); + + query = query + .concat('WHERE') + .concat(conds.join(', ')); + + values = values.concat(vals); + } + + query = query.join(' '); return [query, values]; }; -exports.delete = function _delete (tableName, where) { +exports.delete = function _delete (tableName, options) { + var where = options.where; var conditions = Object.keys(where).map(function (k, i) { return k + '=$' + (i + 1); }); diff --git a/test/sql_gen.test.js b/test/sql_gen.test.js index 2a40ace..54538ee 100644 --- a/test/sql_gen.test.js +++ b/test/sql_gen.test.js @@ -44,16 +44,15 @@ tape('::init should generate SQL to create a table if none exists', function (t) }); tape('::select should generate SQL to select columns from a table', function (t) { - var query = sqlGen.select(schema.table_name, ['email', 'dob']); + var query = sqlGen.select(schema.table_name, {select: ['email', 'dob']}); t.equal(query[0], 'SELECT email, dob FROM "user_data"', 'Generate parameterised query'); t.deepEqual(query[1], [], 'Generate values for parameterised query'); t.end(); }); - tape('::insert should generate SQL to insert a column into a table', function (t) { - var query = sqlGen.insert(schema.table_name, { email: 'me@poop.com' }); + var query = sqlGen.insert(schema.table_name, {fields: { email: 'me@poop.com' }}); t.equal(query[0], 'INSERT INTO "user_data" (email) VALUES ($1)', 'Generate parameterised query'); t.deepEqual(query[1], ['me@poop.com'], 'Generate values for parameterised query'); @@ -61,15 +60,26 @@ tape('::insert should generate SQL to insert a column into a table', function (t }); tape('::update should generate SQL to update a column in a table', function (t) { - var query = sqlGen.update(schema.table_name, { email: 'me@poop.com' }); + var query = sqlGen.update(schema.table_name, {fields: { email: 'me@poop.com' }}); t.equal(query[0], 'UPDATE "user_data" SET email=$1', 'Generate parameterised query'); t.deepEqual(query[1], ['me@poop.com'], 'Generate values for parameterised query'); t.end(); }); +tape('::update should generate SQL to update a column in a table w/ where clauses', function (t) { + var query = sqlGen.update(schema.table_name, { + fields: { email: 'me@poop.com' }, + where: { foo: 'bar' } + }); + + t.equal(query[0], 'UPDATE "user_data" SET email=$1 WHERE foo=$2', 'Generate parameterised query'); + t.deepEqual(query[1], ['me@poop.com', 'bar'], 'Generate values for parameterised query'); + t.end(); +}); + tape('::delete should generate SQL to delete a row from a table', function (t) { - var query = sqlGen.delete(schema.table_name, { username: 'bob' }); + var query = sqlGen.delete(schema.table_name, { where:{ username: 'bob' } }); t.equal(query[0], 'DELETE FROM "user_data" WHERE username=$1', 'Generate parameterised query'); t.deepEqual(query[1], ['bob'], 'Generate values for parameterised query'); From 5b08cf660aca292170a2fba59d905f6f1c6aaea4 Mon Sep 17 00:00:00 2001 From: Elias Date: Mon, 26 Sep 2016 19:22:37 +0100 Subject: [PATCH 19/40] add where clause to select method --- lib/sql_gen.js | 21 ++++++++++++++++++--- test/sql_gen.test.js | 11 +++++++++++ 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/lib/sql_gen.js b/lib/sql_gen.js index f7a8869..b77ee8e 100644 --- a/lib/sql_gen.js +++ b/lib/sql_gen.js @@ -24,12 +24,27 @@ exports.init = function init (config) { exports.select = function select (tableName, options) { var columns = options.select || ['*']; + var values = []; var query = ['SELECT'] .concat(columns.join(', ')) .concat('FROM') - .concat('"' + tableName + '"') - .join(' '); - var values = []; + .concat('"' + tableName + '"'); + + if (options.where) { + var keys = Object.keys(options.where); + var conds = keys.map(function (k, i) { + return k + '=$' + (values.length + i + 1); + }); + var vals = keys.map(function (k) {return options.where[k];}); + + query = query + .concat('WHERE') + .concat(conds.join(', ')); + + values = values.concat(vals); + } + + query = query.join(' '); return [query, values]; }; diff --git a/test/sql_gen.test.js b/test/sql_gen.test.js index 54538ee..4ada337 100644 --- a/test/sql_gen.test.js +++ b/test/sql_gen.test.js @@ -51,6 +51,17 @@ tape('::select should generate SQL to select columns from a table', function (t) t.end(); }); +tape('::select should generate SQL to select columns from a table w/ where clause', function (t) { + var query = sqlGen.select(schema.table_name, { + select: ['email', 'dob'], + where: { foo: 'bar' } + }); + + t.equal(query[0], 'SELECT email, dob FROM "user_data" WHERE foo=$1', 'Generate parameterised query'); + t.deepEqual(query[1], ['bar'], 'Generate values for parameterised query'); + t.end(); +}); + tape('::insert should generate SQL to insert a column into a table', function (t) { var query = sqlGen.insert(schema.table_name, {fields: { email: 'me@poop.com' }}); From aff1574a75b1ab449843dc0a2bbdde12ff0a9c79 Mon Sep 17 00:00:00 2001 From: Jack Rans Date: Mon, 26 Sep 2016 20:17:40 +0100 Subject: [PATCH 20/40] :bug: allow extra keys on field config #39 --- lib/config_validator.js | 2 +- test/config_validator.test.js | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/config_validator.js b/lib/config_validator.js index f6a7c3d..660ee12 100644 --- a/lib/config_validator.js +++ b/lib/config_validator.js @@ -9,7 +9,7 @@ var configSchema = Joi.object().keys({ table_name: Joi.string().regex(dbNameRegEx).required(), fields: Joi.object().pattern( dbNameRegEx, - Joi.object().keys({type: Joi.any().valid(fieldTypes)}) + Joi.object().keys({type: Joi.any().valid(fieldTypes)}).unknown() ).required() }); diff --git a/test/config_validator.test.js b/test/config_validator.test.js index 92454f8..aff0ea1 100644 --- a/test/config_validator.test.js +++ b/test/config_validator.test.js @@ -27,6 +27,13 @@ test('config validator', function (t) { }).error, 'error if field name doesn\'t pass db name regex' ); + t.notOk( + validator({ + table_name: 'test', + fields: {'email': {type: 'string', unknown: 'allowed'}} + }).error, + 'no error when extra options unknown' + ); t.end(); }); From 0f670c88c311ce4317620ce0c6d4ad627977e483 Mon Sep 17 00:00:00 2001 From: Jack Rans Date: Mon, 26 Sep 2016 20:20:31 +0100 Subject: [PATCH 21/40] :art: :fire: refactor out test fixture --- test/example_schema.js | 17 +++++++++++++++++ test/index.test.js | 6 ------ test/sql_gen.test.js | 20 ++------------------ 3 files changed, 19 insertions(+), 24 deletions(-) create mode 100644 test/example_schema.js delete mode 100644 test/index.test.js diff --git a/test/example_schema.js b/test/example_schema.js new file mode 100644 index 0000000..2f4eb5d --- /dev/null +++ b/test/example_schema.js @@ -0,0 +1,17 @@ +module.exports = { + table_name: 'user_data', + fields: { + email: { + type: 'string', + email: true + }, + username: { + type: 'string', + min: 3, + max: 20 + }, + dob: { + type: 'date' + } + } +}; diff --git a/test/index.test.js b/test/index.test.js deleted file mode 100644 index 5dd063b..0000000 --- a/test/index.test.js +++ /dev/null @@ -1,6 +0,0 @@ -var tape = require('tape'); - -tape('first test', function (t) { - t.equal(true, true); - t.end(); -}); diff --git a/test/sql_gen.test.js b/test/sql_gen.test.js index 2a40ace..5ed47e3 100644 --- a/test/sql_gen.test.js +++ b/test/sql_gen.test.js @@ -1,25 +1,9 @@ 'use strict'; var tape = require('tape'); -var sqlGen = require('../lib/sql_gen.js'); -var schema = { - table_name: 'user_data', - fields: { - email: { - type: 'string', - email: true - }, - username: { - type: 'string', - min: 3, - max: 20 - }, - dob: { - type: 'date' - } - } -}; +var sqlGen = require('../lib/sql_gen.js'); +var schema = require('./example_schema.js'); tape('::init should throw on empty or invalid input', function (t) { t.throws(function () { From 3296cab4e56949f7d5b7d27e7aa33f14a63b4ca3 Mon Sep 17 00:00:00 2001 From: Jack Rans Date: Tue, 27 Sep 2016 09:34:13 +0100 Subject: [PATCH 22/40] :heavy_plus_sign: Expose database methods #11 --- lib/config_validator.js | 9 +++-- lib/db.js | 23 +++++++++++ test/db.test.js | 86 +++++++++++++++++++++++++++++++++++++++++ test/test_pg_client.js | 6 +++ 4 files changed, 120 insertions(+), 4 deletions(-) create mode 100644 lib/db.js create mode 100644 test/db.test.js create mode 100644 test/test_pg_client.js diff --git a/lib/config_validator.js b/lib/config_validator.js index 660ee12..1a0473a 100644 --- a/lib/config_validator.js +++ b/lib/config_validator.js @@ -5,12 +5,13 @@ var mapObj = require('./create_table_map.js').mapObj; var fieldTypes = Object.keys(mapObj); var dbNameRegEx = /^[A-Za-z_]\w{0,62}$/; +var fieldSchema = Joi.object() + .keys({ type: Joi.any().valid(fieldTypes) }) + .unknown() +; var configSchema = Joi.object().keys({ table_name: Joi.string().regex(dbNameRegEx).required(), - fields: Joi.object().pattern( - dbNameRegEx, - Joi.object().keys({type: Joi.any().valid(fieldTypes)}).unknown() - ).required() + fields: Joi.object().pattern(dbNameRegEx, fieldSchema).required() }); module.exports = function (config) { diff --git a/lib/db.js b/lib/db.js new file mode 100644 index 0000000..7c9351d --- /dev/null +++ b/lib/db.js @@ -0,0 +1,23 @@ +var sqlGen = require('./sql_gen.js'); +var configValidator = require('./config_validator.js'); + +var methods = { + init: function (client, config, _ , cb) { + var error = configValidator(config).error; + + if (error) { return cb(error); } + + return client.query(sqlGen.init(config), cb); + } +}; + +['select', 'update', 'delete', 'insert'].forEach(function (method) { + methods[method] = function (client, config, options, cb) { + var tableName = config.table_name; + var args = sqlGen[method](tableName, options).concat([cb]); + + return client.query.apply(client, args); + }; +}); + +module.exports = methods; diff --git a/test/db.test.js b/test/db.test.js new file mode 100644 index 0000000..b56d36b --- /dev/null +++ b/test/db.test.js @@ -0,0 +1,86 @@ +var test = require('tape'); + +var client = require('./test_pg_client.js'); +var db = require('../lib/db.js'); +var schema = require('./example_schema.js'); + +var testInsert = { + email: 'test@gmail.com', + dob: '9/28/2001', + username: 'test' +}; + +test('init test client', function (t) { + client.connect(function () { + client.query('DROP TABLE ' + schema.table_name, t.end); + }); +}); + +test('db.init', function (t) { + t.plan(2); + + db.init(client, { rubbish: 'schema' }, {}, function (error) { + t.ok(error, 'error given when using invalid schema'); + }); + + db.init(client, schema) + .then(function () { return client.query('SELECT * from user_data'); }) + .then(function (res) { + t.ok( + res.fields + .map(function (field) { return field.name; }) + .indexOf('dob') > -1 + , 'table created with a correct field' + ); + }) + ; +}); + + + +test('db.insert & default select w custom where', function (t) { + t.plan(1); + + db.insert(client, schema, { fields: testInsert }) + .then(function () { + return db.select(client, schema, { where: { dob: '2001-09-28'}}); + }) + .then(function (res) { + res.rows[0].dob = res.rows[0].dob.toLocaleDateString(); + + t.deepEqual( + res.rows[0], + testInsert, + 'get same object back' + ); + }) + .catch(t.fail) + ; +}); + +test('db.update w where & custom select w default where', function (t) { + t.plan(1); + db.update(client, schema, { + fields: { username: 'bob' }, + where: { email: 'test@gmail.com' } + }).then(function () { + return db.select(client, schema, { select: ['email', 'username'] }); + }).then(function (res) { + t.deepEqual( + res.rows[0], + { email: 'test@gmail.com', username: 'bob' }, + 'username updated' + ); + }).catch(t.fail); +}); + +test('db.delete w db.select', function (t) { + t.plan(1); + db.delete(client, schema, { where: { username: 'bob' }}) + .then(function () { return db.select(client, schema, {}); }) + .then(function (res) { t.equal(res.rows.length, 0, 'nothing left in db'); }) + .catch(t.fail) + ; +}); + +test('close test client', function (t) { client.end(t.end); }); diff --git a/test/test_pg_client.js b/test/test_pg_client.js new file mode 100644 index 0000000..07a4a92 --- /dev/null +++ b/test/test_pg_client.js @@ -0,0 +1,6 @@ +require('env2')('config.env'); +var pg = require('pg'); + +var testDBUrl = process.env.TEST_DATABASE_URL; + +module.exports = new pg.Client(testDBUrl); From 743656accdf6e8331d83a23a0bfa806ee9587575 Mon Sep 17 00:00:00 2001 From: Elias Date: Tue, 27 Sep 2016 10:57:21 +0100 Subject: [PATCH 23/40] Fix tests --- test/db.test.js | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/test/db.test.js b/test/db.test.js index b56d36b..d03f063 100644 --- a/test/db.test.js +++ b/test/db.test.js @@ -6,7 +6,7 @@ var schema = require('./example_schema.js'); var testInsert = { email: 'test@gmail.com', - dob: '9/28/2001', + dob: '2001-09-27', username: 'test' }; @@ -39,22 +39,32 @@ test('db.init', function (t) { test('db.insert & default select w custom where', function (t) { - t.plan(1); - db.insert(client, schema, { fields: testInsert }) .then(function () { - return db.select(client, schema, { where: { dob: '2001-09-28'}}); + return db.select(client, schema, { where: { dob: '2001-09-27'}}); }) .then(function (res) { - res.rows[0].dob = res.rows[0].dob.toLocaleDateString(); - - t.deepEqual( - res.rows[0], - testInsert, - 'get same object back' + t.equal( + res.rows[0].email, + testInsert.email, + 'email correct' + ); + t.equal( + res.rows[0].username, + testInsert.username, + 'username correct' ); + t.equal( + res.rows[0].dob.toLocaleDateString('GMT'), + new Date(testInsert.dob).toLocaleDateString('GMT'), + 'get same date back, though now a date object' + ); + t.end(); + }) + .catch(function (err) { + t.fail(err); + t.end(); }) - .catch(t.fail) ; }); From b2d42f610ef68ca7f6b955d9cf246a689cae2eed Mon Sep 17 00:00:00 2001 From: Jack Rans Date: Tue, 27 Sep 2016 11:08:09 +0100 Subject: [PATCH 24/40] :memo: Update readme on test config --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 9a1d08d..95d4dd7 100644 --- a/README.md +++ b/README.md @@ -79,3 +79,11 @@ a well-managed PostgreSQL cluster copes very well. > If you're still Curious or Worried about scaling PostgreSQL? see: https://www.citusdata.com > Want to model the network of people as a graph? https://github.com/cayleygraph/cayley + +## Running the Project + +For the tests to work you will need to have the url to a test database in a `config.env` file as followed: +``` +TEST_DATABASE_URL=psql://localhost:5432/testdb +``` +**Note** before tests are run the tables may be removed from the database so don't keep your family photos there. From f3b5da61e72c49cf3883e4b67fefeefaa028fe49 Mon Sep 17 00:00:00 2001 From: Elias Date: Tue, 27 Sep 2016 12:36:08 +0100 Subject: [PATCH 25/40] delete .eslintrc file, b/c will be using goodparts package v. soon --- .eslintrc.js | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 .eslintrc.js diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index d6a6169..0000000 --- a/.eslintrc.js +++ /dev/null @@ -1,5 +0,0 @@ -module.exports = { - rules: { - 'semi': 'error' - } -}; From 866aae6afa0ef5e1f333b3f95fc4f084c80021fb Mon Sep 17 00:00:00 2001 From: Elias Date: Tue, 27 Sep 2016 12:36:40 +0100 Subject: [PATCH 26/40] add utils module to help with DRYing out the sql_gen module --- lib/utils.js | 15 +++++++++++++++ test/utils.test.js | 13 +++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 lib/utils.js create mode 100644 test/utils.test.js diff --git a/lib/utils.js b/lib/utils.js new file mode 100644 index 0000000..2026ec7 --- /dev/null +++ b/lib/utils.js @@ -0,0 +1,15 @@ +'use strict'; + +exports.values = function (obj, keys) { + return (keys || Object.keys(obj)).map(function (k) {return obj[k];}); +}; + +exports.except = function (fields, obj) { + var o = {}; + Object.keys(obj).forEach(function (k) { + if (fields.indexOf(k) === -1) { + o[k] = obj[k]; + } + }); + return o; +}; diff --git a/test/utils.test.js b/test/utils.test.js new file mode 100644 index 0000000..9e6ca9d --- /dev/null +++ b/test/utils.test.js @@ -0,0 +1,13 @@ +'use strict'; + +var test = require('tape'); +var _ = require('../lib/utils.js'); + +test('::values w/ default keys value', function (t) { + var o = {a: 1, b: 2}; + var result = _.values(o); + + t.equal(result[0], o.a, 'Key "a" matches'); + t.equal(result[1], o.b, 'Key "b" matches'); + t.end(); +}); From e11d6976923f0fff5118536056c70a0346175b62 Mon Sep 17 00:00:00 2001 From: Elias Date: Tue, 27 Sep 2016 12:36:58 +0100 Subject: [PATCH 27/40] only drop table if it exists, to avoid error --- test/db.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/db.test.js b/test/db.test.js index d03f063..7f93911 100644 --- a/test/db.test.js +++ b/test/db.test.js @@ -12,7 +12,7 @@ var testInsert = { test('init test client', function (t) { client.connect(function () { - client.query('DROP TABLE ' + schema.table_name, t.end); + client.query('DROP TABLE IF EXISTS ' + schema.table_name, t.end); }); }); From a8f20c09be4d28009dfdba73ca60624da79b7eca Mon Sep 17 00:00:00 2001 From: Elias Date: Tue, 27 Sep 2016 12:37:09 +0100 Subject: [PATCH 28/40] DRY out sql_gen --- lib/sql_gen.js | 90 ++++++++++++++++++++++++++------------------------ 1 file changed, 47 insertions(+), 43 deletions(-) diff --git a/lib/sql_gen.js b/lib/sql_gen.js index b77ee8e..9949994 100644 --- a/lib/sql_gen.js +++ b/lib/sql_gen.js @@ -1,24 +1,47 @@ 'use strict'; var mapper = require('./create_table_map.js'); +var _ = require('./utils.js'); + + +function paramStr (columns, opts) { + var offset = (opts && opts.offset) || 0; + var assign = (opts && opts.assign) || false; + + return columns.map(function (k, i) { + var suff = '$' + (1 + i + (offset || 0)); + var pref = assign ? k + '=' : ''; + return pref + suff; + }); +} + + +function processWhere (where, query, values) { + var keys = Object.keys(where); + var conds = paramStr(keys, { offset: values.length, assign: true }); + var vals = _.values(where, keys); + + return { + query: query.concat('WHERE').concat(conds.join(', ')), + values: values.concat(vals) + }; +} + exports.init = function init (config) { var tableName = config.table_name; var fields = config.fields; - var query = 'CREATE TABLE IF NOT EXISTS "' + tableName + '" '; var columns = Object.keys(fields).map(function (key) { var type = fields[key].type; - var opts = fields[key]; + var opts = _.except(['type'], fields[key]); return mapper(key, type, opts); }); - var query = ['CREATE TABLE IF NOT EXISTS "' + tableName + '"'] + return ['CREATE TABLE IF NOT EXISTS "' + tableName + '"'] .concat('(' + columns.join(', ') + ')') .join(' '); - - return query; }; @@ -31,17 +54,9 @@ exports.select = function select (tableName, options) { .concat('"' + tableName + '"'); if (options.where) { - var keys = Object.keys(options.where); - var conds = keys.map(function (k, i) { - return k + '=$' + (values.length + i + 1); - }); - var vals = keys.map(function (k) {return options.where[k];}); - - query = query - .concat('WHERE') - .concat(conds.join(', ')); - - values = values.concat(vals); + var result = processWhere(options.where, query, values); + query = result.query; + values = result.values; } query = query.join(' '); @@ -53,8 +68,8 @@ exports.select = function select (tableName, options) { exports.insert = function insert (tableName, options) { var fields = options.fields || {}; var columns = Object.keys(fields); - var values = columns.map(function (k) {return fields[k];}); - var params = columns.map(function (e, i) {return '$' + (i + 1);}); + var values = _.values(fields, columns); + var params = paramStr(columns); var query = ['INSERT INTO "' + tableName + '"'] .concat('(' + columns.join(', ') + ')') @@ -68,27 +83,18 @@ exports.insert = function insert (tableName, options) { exports.update = function update (tableName, options) { var fields = options.fields || {}; - var columns = Object.keys(fields).map(function (k, i) { - return k + '=$' + (i + 1); - }); - var values = Object.keys(fields).map(function (k) {return fields[k];}); + var columns = Object.keys(fields); + var conditions = paramStr(columns, {assign: true}); + var values = _.values(fields, columns); var query = ['UPDATE "' + tableName + '"'] .concat('SET') - .concat(columns.join(', ')); + .concat(conditions.join(', ')); if (options.where) { - var keys = Object.keys(options.where); - var conds = keys.map(function (k, i) { - return k + '=$' + (values.length + i + 1); - }); - var vals = keys.map(function (k) {return options.where[k];}); - - query = query - .concat('WHERE') - .concat(conds.join(', ')); - - values = values.concat(vals); + var result = processWhere(options.where, query, values); + query = result.query; + values = result.values; } query = query.join(' '); @@ -98,16 +104,14 @@ exports.update = function update (tableName, options) { exports.delete = function _delete (tableName, options) { - var where = options.where; - var conditions = Object.keys(where).map(function (k, i) { - return k + '=$' + (i + 1); - }); - var values = Object.keys(where).map(function (k) {return where[k];}); + var query = ['DELETE FROM "' + tableName + '"']; + var values = []; - var query = ['DELETE FROM "' + tableName + '"'] - .concat('WHERE') - .concat(conditions.join(', ')) - .join(' '); + var result = processWhere(options.where, query, values); + query = result.query; + values = result.values; + + query = query.join(' '); return [query, values]; }; From 22f864394dd9f9b4ec7e36ae432a0e4907afd99a Mon Sep 17 00:00:00 2001 From: Elias Date: Wed, 28 Sep 2016 09:41:55 +0100 Subject: [PATCH 29/40] symlink the good parts eslint config for now, because we'd need a plugin to get it working w/ atom otherwise --- package.json | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 89544e2..d61ae14 100644 --- a/package.json +++ b/package.json @@ -15,16 +15,18 @@ }, "homepage": "https://github.com/dwyl/abase#readme", "devDependencies": { - "eslint": "^3.5.0", + "goodparts": "^1.0.3", "istanbul": "^0.4.5", "pre-commit": "^1.1.3", "tape": "^4.6.0" }, "scripts": { "test": "node_modules/.bin/tape ./test/*.test.js", - "lint": "node_modules/.bin/eslint .", - "lint:fix": "node_modules/.bin/eslint . --fix", - "coverage": "node_modules/.bin/istanbul cover node_modules/.bin/tape ./test/*.test.js" + "lint": "node_modules/.bin/goodparts .", + "lint:fix": "node_modules/.bin/goodparts . --fix", + "lint:init": "ln -sf node_modules/goodparts/.eslintrc.js ./.eslintrc.js", + "coverage": "node_modules/.bin/istanbul cover node_modules/.bin/tape ./test/*.test.js", + "postinstall": "npm run lint:init" }, "pre-commit": [ "lint", From 208b98211a59bbaeab6c3da281ad91455e18a18b Mon Sep 17 00:00:00 2001 From: Jack Rans Date: Wed, 28 Sep 2016 11:35:49 +0100 Subject: [PATCH 30/40] :art: :bug: Change confgig validator to throw #39 + Readd joi and hoek to package.json as needed --- lib/config_validator.js | 5 +++-- lib/db.js | 4 +--- package.json | 2 ++ test/config_validator.test.js | 34 ++++++++++++++++++++-------------- test/db.test.js | 11 +++++------ 5 files changed, 31 insertions(+), 25 deletions(-) diff --git a/lib/config_validator.js b/lib/config_validator.js index 1a0473a..64d9e60 100644 --- a/lib/config_validator.js +++ b/lib/config_validator.js @@ -2,8 +2,9 @@ var Joi = require('joi'); var mapObj = require('./create_table_map.js').mapObj; -var fieldTypes = Object.keys(mapObj); +// non empty, alphanumeric, no leading number, less than 64 var dbNameRegEx = /^[A-Za-z_]\w{0,62}$/; +var fieldTypes = Object.keys(mapObj); var fieldSchema = Joi.object() .keys({ type: Joi.any().valid(fieldTypes) }) @@ -15,7 +16,7 @@ var configSchema = Joi.object().keys({ }); module.exports = function (config) { - return Joi.validate(config, configSchema); + return Joi.assert(config, configSchema); }; module.exports.dbNameRegEx = dbNameRegEx; diff --git a/lib/db.js b/lib/db.js index 7c9351d..f3fe4ad 100644 --- a/lib/db.js +++ b/lib/db.js @@ -3,9 +3,7 @@ var configValidator = require('./config_validator.js'); var methods = { init: function (client, config, _ , cb) { - var error = configValidator(config).error; - - if (error) { return cb(error); } + configValidator(config); return client.query(sqlGen.init(config), cb); } diff --git a/package.json b/package.json index eca95ea..425a33b 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,8 @@ ], "dependencies": { "env2": "^2.1.1", + "hoek": "^4.1.0", + "joi": "^9.0.4", "pg": "^6.1.0" } } diff --git a/test/config_validator.test.js b/test/config_validator.test.js index aff0ea1..4f5cc4d 100644 --- a/test/config_validator.test.js +++ b/test/config_validator.test.js @@ -1,37 +1,39 @@ var test = require('tape'); -var validator = require('../lib/config_validator.js'); -var dbNameRegEx = validator.dbNameRegEx; +var validate = require('../lib/config_validator.js'); +var dbNameRegEx = validate.dbNameRegEx; + +function validator (config) { return function () { validate(config); } } test('config validator', function (t) { - t.ok( - validator({ fields: {} }).error, + t.throws( + validator({ fields: {} }), 'error if no table_name property' ); - t.ok( - validator({ table_name: 'test' }).error, + t.throws( + validator({ table_name: 'test' }), 'error if no fields property' ); - t.ok( - validator({ table_name: '2test', fields: {} }).error, + t.throws( + validator({ table_name: '2test', fields: {} }), 'error if table name doesn\t pass db name regex' ); - t.ok( - validator({ table_name: '2test', fields: {} }).error, + t.throws( + validator({ table_name: '2test', fields: {} }), 'error if table name doesn\t pass db name regex' ); - t.ok( + t.throws( validator({ table_name: 'test', fields: {'2field': {type: 'string'}} - }).error, + }), 'error if field name doesn\'t pass db name regex' ); - t.notOk( + t.doesNotThrow( validator({ table_name: 'test', fields: {'email': {type: 'string', unknown: 'allowed'}} - }).error, + }), 'no error when extra options unknown' ); @@ -43,6 +45,10 @@ test('dbNameRegEx', function (t) { dbNameRegEx.exec('_a1pha_Numer1c'), 'alpha numeric keys allowed only' ); + t.notOk( + dbNameRegEx.exec(''), + 'alpha numeric keys allowed only' + ); t.notOk( dbNameRegEx.exec('no£way'), 'no other characters allowed' diff --git a/test/db.test.js b/test/db.test.js index 7f93911..d09db55 100644 --- a/test/db.test.js +++ b/test/db.test.js @@ -17,12 +17,10 @@ test('init test client', function (t) { }); test('db.init', function (t) { - t.plan(2); - - db.init(client, { rubbish: 'schema' }, {}, function (error) { - t.ok(error, 'error given when using invalid schema'); - }); - + t.throws( + function () { db.init(client, { rubbish: 'schema' }); }, + 'error thrown when given when using invalid schema' + ); db.init(client, schema) .then(function () { return client.query('SELECT * from user_data'); }) .then(function (res) { @@ -32,6 +30,7 @@ test('db.init', function (t) { .indexOf('dob') > -1 , 'table created with a correct field' ); + t.end(); }) ; }); From a57d5d3240ded9b2e1f6d75246e64c6d84727e4a Mon Sep 17 00:00:00 2001 From: shouston3 Date: Wed, 28 Sep 2016 13:32:59 +0100 Subject: [PATCH 31/40] fixing linting errors --- lib/add_default_values.js | 4 ++-- lib/index.js | 5 ++--- lib/load_schema.js | 7 +++++-- test/add_default_values.test.js | 10 +++++----- test/helpers/hooks.js | 9 ++++----- test/index.test.js | 2 -- test/load_schema.test.js | 25 +++++++++++++++++++------ test/update_schema.test.js | 5 +++-- 8 files changed, 40 insertions(+), 27 deletions(-) diff --git a/lib/add_default_values.js b/lib/add_default_values.js index 33f6079..10aac57 100644 --- a/lib/add_default_values.js +++ b/lib/add_default_values.js @@ -15,8 +15,8 @@ var defaultSchema = { module.exports = function (schema) { var anyChanges = false; - Object.keys(defaultSchema).forEach(function(field) { - if(!schema.hasOwnProperty(field)){ + Object.keys(defaultSchema).forEach(function (field) { + if (!schema.hasOwnProperty(field)) { schema[field] = defaultSchema[field]; anyChanges = true; }; diff --git a/lib/index.js b/lib/index.js index c4a8031..b54d98a 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,20 +1,19 @@ var addDefaultValues = require('./add_default_values.js'); var loadSchema = require('./load_schema.js'); var updateSchema = require('./update_schema.js'); -var fs = require('fs'); module.exports = function (server, options, next) { if (!options.hasOwnProperty('user_schema_path')) { return next('Error: user_schema_path is not defined, please define a user_schema_path'); } - loadSchema(options.user_schema_path, function(errorLoad, schema) { + loadSchema(options.user_schema_path, function (errorLoad, schema) { if (errorLoad) { return next(errorLoad); } if (addDefaultValues(schema)) { - updateSchema(options.user_schema_path, schema, function(errorUpdateSchema) { + updateSchema(options.user_schema_path, schema, function (errorUpdateSchema) { return next(errorUpdateSchema); }); } else { diff --git a/lib/load_schema.js b/lib/load_schema.js index ac98c95..8388060 100644 --- a/lib/load_schema.js +++ b/lib/load_schema.js @@ -13,8 +13,11 @@ module.exports = function (schemaPath, cb) { try { var schema = JSON.parse(data); } catch (parseError) { - return cb('Error: the schema user file contains unconventianal type, please make sure an schema object is defined'); + return cb( + 'Error: the schema user file contains unconventianal type,' + + 'please make sure an schema object is defined' + ); } - return cb(undefined, schema); + return cb(null, schema); }); }; diff --git a/test/add_default_values.test.js b/test/add_default_values.test.js index 96eedb8..2b2bd40 100644 --- a/test/add_default_values.test.js +++ b/test/add_default_values.test.js @@ -9,7 +9,7 @@ tape('add default values to empty object', function (t) { tape('add email to schema if not defined yet', function (t) { var obj = { - password: {type: 'password'} + password: { type: 'password' } }; var anyChanges = addDefaultValues(obj); @@ -20,7 +20,7 @@ tape('add email to schema if not defined yet', function (t) { tape('add password to schema if not defined yet', function (t) { var obj = { - email: {type: 'email'} + email: { type: 'email' } }; var anyChanges = addDefaultValues(obj); @@ -31,11 +31,11 @@ tape('add password to schema if not defined yet', function (t) { tape('do not add default values if they are already defined', function (t) { var obj = { - email: {type: 'email'}, - password: {type: 'password'} + email: { type: 'email' }, + password: { type: 'password' } }; var anyChanges = addDefaultValues(obj); t.ok(!anyChanges, 'No changes applied to the schema'); t.end(); -}); \ No newline at end of file +}); diff --git a/test/helpers/hooks.js b/test/helpers/hooks.js index 5771de4..84b9034 100644 --- a/test/helpers/hooks.js +++ b/test/helpers/hooks.js @@ -2,11 +2,10 @@ * Test hooks for tape taken from: * https://github.com/substack/tape/issues/59#issuecomment-32808769 */ -'use strict'; -exports.beforeEach = function beforeEach (test, handler) { +exports.beforeEach = function (_test, handler) { return function tapish (name, listener) { - test(name, function (assert) { + _test(name, function (assert) { var _end = assert.end; assert.end = function () { @@ -19,9 +18,9 @@ exports.beforeEach = function beforeEach (test, handler) { }; }; -exports.afterEach = function afterEach (test, handler) { +exports.afterEach = function (_test, handler) { return function tapish (name, listener) { - test(name, function (assert) { + _test(name, function (assert) { var _end = assert.end; assert.end = function () { diff --git a/test/index.test.js b/test/index.test.js index 8edb00f..e88382b 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -1,5 +1,3 @@ -'use strict'; - var fs = require('fs'); var Hapi = require('hapi'); var tape = require('tape'); diff --git a/test/load_schema.test.js b/test/load_schema.test.js index dcd377b..fcbc355 100644 --- a/test/load_schema.test.js +++ b/test/load_schema.test.js @@ -3,17 +3,26 @@ var loadSchema = require('../lib/load_schema.js'); var path = require('path'); tape('attempt to load a schema with a wrong path file', function (t) { - loadSchema('./wrongpathoffile.json', function (error, schema) { + loadSchema('./wrongpathoffile.json', function (error) { t.ok(error, 'can\'t read a file with a wrong path'); - t.equal(error, 'Error: sorry impossible to read the file at ./wrongpathoffile.json', 'error message is passed to the callback'); + t.equal( + error, + 'Error: sorry impossible to read the file at ./wrongpathoffile.json', + 'error message is passed to the callback' + ); t.end(); }); }); tape('the schema is not a json object', function (t) { - loadSchema(path.join(__dirname, 'fixtures', 'wrong_schema.json'), function (error, schema) { + loadSchema(path.join(__dirname, 'fixtures', 'wrong_schema.json'), function (error) { t.ok(error, 'can\'t parse the content of the schema'); - t.equal(error, 'Error: the schema user file contains unconventianal type, please make sure an schema object is defined', 'error schema parse displayed properly'); + t.equal( + error, + 'Error: the schema user file contains unconventianal type,' + + 'please make sure an schema object is defined', + 'error schema parse displayed properly' + ); t.end(); }); }); @@ -21,7 +30,11 @@ tape('the schema is not a json object', function (t) { tape('load the schema', function (t) { loadSchema(path.join(__dirname, 'fixtures', 'right_schema.json'), function (error, schema) { t.ok(!error, 'no errors'); - t.deepEqual(schema, {email: {type: "email"}, password: {type: "password"}}, 'the schema is loaded properly'); + t.deepEqual( + schema, + { email: { type: 'email' }, password: { type: 'password' } }, + 'the schema is loaded properly' + ); t.end(); }); -}); \ No newline at end of file +}); diff --git a/test/update_schema.test.js b/test/update_schema.test.js index 0a99359..497f055 100644 --- a/test/update_schema.test.js +++ b/test/update_schema.test.js @@ -4,8 +4,9 @@ var path = require('path'); tape('attempt to update a schema with a wrong schema path', function (t) { var wrongPath = path.join(__dirname, 'fixtures', 'wrongpath', 'schema.json'); - updateSchema(wrongPath, {}, function (error, response) { + updateSchema(wrongPath, {}, function (error) { t.ok(error, 'can\'t write a file with a wrong path'); t.end(); }); -}); \ No newline at end of file +}); + From cd802d452ac152193a1db5fb9edb36b6d76f0d10 Mon Sep 17 00:00:00 2001 From: Elias Date: Wed, 28 Sep 2016 15:21:44 +0100 Subject: [PATCH 32/40] change eslint back to goodparts --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 8c74e39..60d5ea7 100644 --- a/package.json +++ b/package.json @@ -25,9 +25,9 @@ }, "scripts": { "test": "node_modules/.bin/tape ./test/*.test.js", - "lint": "node_modules/.bin/eslint .", + "lint": "node_modules/.bin/goodparts .", "lint:init": "ln -sf node_modules/goodparts/.eslintrc.js ./.eslintrc.js", - "lint:fix": "node_modules/.bin/eslint . --fix", + "lint:fix": "node_modules/.bin/goodparts . --fix", "coverage": "node_modules/.bin/istanbul cover node_modules/.bin/tape ./test/*.test.js", "codecov": "node_modules/.bin/codecov --token=${CODECOV_TOKEN}", "postinstall": "npm run lint:init" From d9eefed1a2d7b1572c9184e53329991583d54af0 Mon Sep 17 00:00:00 2001 From: Elias Date: Wed, 28 Sep 2016 15:22:00 +0100 Subject: [PATCH 33/40] auto fix linter errors --- lib/add_default_values.js | 11 ++++------- lib/index.js | 4 +--- lib/load_schema.js | 1 + test/add_default_values.test.js | 8 ++------ test/load_schema.test.js | 4 +++- 5 files changed, 11 insertions(+), 17 deletions(-) diff --git a/lib/add_default_values.js b/lib/add_default_values.js index 10aac57..bc947e8 100644 --- a/lib/add_default_values.js +++ b/lib/add_default_values.js @@ -5,12 +5,8 @@ * @return {boolean} - any changes apply to the schema */ var defaultSchema = { - email: { - type: 'email' - }, - password: { - type: 'password' - } + email: { type: 'email' }, + password: { type: 'password' } }; module.exports = function (schema) { @@ -19,7 +15,8 @@ module.exports = function (schema) { if (!schema.hasOwnProperty(field)) { schema[field] = defaultSchema[field]; anyChanges = true; - }; + } }); + return anyChanges; }; diff --git a/lib/index.js b/lib/index.js index b54d98a..fdf4846 100644 --- a/lib/index.js +++ b/lib/index.js @@ -22,6 +22,4 @@ module.exports = function (server, options, next) { }); }; -module.exports.attributes = { - name: 'Abase' -}; +module.exports.attributes = { name: 'Abase' }; diff --git a/lib/load_schema.js b/lib/load_schema.js index 8388060..61cb779 100644 --- a/lib/load_schema.js +++ b/lib/load_schema.js @@ -18,6 +18,7 @@ module.exports = function (schemaPath, cb) { + 'please make sure an schema object is defined' ); } + return cb(null, schema); }); }; diff --git a/test/add_default_values.test.js b/test/add_default_values.test.js index 2b2bd40..fb264d3 100644 --- a/test/add_default_values.test.js +++ b/test/add_default_values.test.js @@ -8,9 +8,7 @@ tape('add default values to empty object', function (t) { }); tape('add email to schema if not defined yet', function (t) { - var obj = { - password: { type: 'password' } - }; + var obj = { password: { type: 'password' } }; var anyChanges = addDefaultValues(obj); t.ok(anyChanges, 'Changes applied to the schema'); @@ -19,9 +17,7 @@ tape('add email to schema if not defined yet', function (t) { }); tape('add password to schema if not defined yet', function (t) { - var obj = { - email: { type: 'email' } - }; + var obj = { email: { type: 'email' } }; var anyChanges = addDefaultValues(obj); t.ok(anyChanges, 'Changes applied to the schema'); diff --git a/test/load_schema.test.js b/test/load_schema.test.js index fcbc355..69790c4 100644 --- a/test/load_schema.test.js +++ b/test/load_schema.test.js @@ -32,7 +32,9 @@ tape('load the schema', function (t) { t.ok(!error, 'no errors'); t.deepEqual( schema, - { email: { type: 'email' }, password: { type: 'password' } }, + { + email: { type: 'email' }, password: { type: 'password' } + }, 'the schema is loaded properly' ); t.end(); From ae66662c3cd8fc3906cff44933efc53fec95282a Mon Sep 17 00:00:00 2001 From: Elias Date: Wed, 28 Sep 2016 15:35:04 +0100 Subject: [PATCH 34/40] fix linter errors --- lib/add_default_values.js | 3 +++ lib/index.js | 19 ++++++++----- lib/load_schema.js | 14 +++++++--- lib/update_schema.js | 13 +++++---- test/add_default_values.test.js | 9 ++++--- test/helpers/hooks.js | 1 + test/index.test.js | 2 ++ test/load_schema.test.js | 48 ++++++++++++++++++--------------- test/update_schema.test.js | 4 ++- 9 files changed, 72 insertions(+), 41 deletions(-) diff --git a/lib/add_default_values.js b/lib/add_default_values.js index bc947e8..ec2911a 100644 --- a/lib/add_default_values.js +++ b/lib/add_default_values.js @@ -1,3 +1,5 @@ +'use strict'; + /** * Initialse with the default values the schema if they are not defined Yet * Write schema in the file if the default values are added @@ -11,6 +13,7 @@ var defaultSchema = { module.exports = function (schema) { var anyChanges = false; + Object.keys(defaultSchema).forEach(function (field) { if (!schema.hasOwnProperty(field)) { schema[field] = defaultSchema[field]; diff --git a/lib/index.js b/lib/index.js index fdf4846..1fbf1ab 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,24 +1,29 @@ +'use strict'; + var addDefaultValues = require('./add_default_values.js'); var loadSchema = require('./load_schema.js'); var updateSchema = require('./update_schema.js'); module.exports = function (server, options, next) { if (!options.hasOwnProperty('user_schema_path')) { - return next('Error: user_schema_path is not defined, please define a user_schema_path'); + return next(new Error('user_schema_path is not defined')); } - loadSchema(options.user_schema_path, function (errorLoad, schema) { + return loadSchema(options.user_schema_path, function (errorLoad, schema) { if (errorLoad) { return next(errorLoad); } - if (addDefaultValues(schema)) { - updateSchema(options.user_schema_path, schema, function (errorUpdateSchema) { - return next(errorUpdateSchema); - }); - } else { + if (!addDefaultValues(schema)) { return next(); } + + return updateSchema( + options.user_schema_path, + schema, + function (errorUpdateSchema) { + return next(errorUpdateSchema); + }); }); }; diff --git a/lib/load_schema.js b/lib/load_schema.js index 61cb779..d8a81b9 100644 --- a/lib/load_schema.js +++ b/lib/load_schema.js @@ -1,17 +1,23 @@ +'use strict'; + +var fs = require('fs'); + /** -* load the shema user +* load the schema user * @param {sting} schemaPath - the file path of the schema * @param {function} cb - callback with error and the schema object +* @returns {void} */ -var fs = require('fs'); - module.exports = function (schemaPath, cb) { fs.readFile(schemaPath, function (err, data) { + var schema; + if (err) { return cb('Error: sorry impossible to read the file at ' + schemaPath); } + try { - var schema = JSON.parse(data); + schema = JSON.parse(data); } catch (parseError) { return cb( 'Error: the schema user file contains unconventianal type,' diff --git a/lib/update_schema.js b/lib/update_schema.js index 4ce7b1c..db5c7e5 100644 --- a/lib/update_schema.js +++ b/lib/update_schema.js @@ -1,11 +1,14 @@ +'use strict'; + +var fs = require('fs'); + /** * Update the schema json file -* @param {string} user_schema_path, the path of the schema file +* @param {string} userSchemaPath, the path of the schema file * @param {object} schema - the schema user object * @param {function} cb - callback with error +* @returns {void} */ - -var fs = require('fs'); -module.exports = function (user_schema_path, schema, cb) { - fs.writeFile(user_schema_path, JSON.stringify(schema), 'utf8', cb); +module.exports = function (userSchemaPath, schema, cb) { + fs.writeFile(userSchemaPath, JSON.stringify(schema), 'utf8', cb); }; diff --git a/test/add_default_values.test.js b/test/add_default_values.test.js index fb264d3..11bada7 100644 --- a/test/add_default_values.test.js +++ b/test/add_default_values.test.js @@ -1,16 +1,19 @@ +'use strict'; + var tape = require('tape'); var addDefaultValues = require('../lib/add_default_values.js'); tape('add default values to empty object', function (t) { var anyChanges = addDefaultValues({}); + t.ok(anyChanges, 'Default values added to the schema'); t.end(); }); tape('add email to schema if not defined yet', function (t) { var obj = { password: { type: 'password' } }; - var anyChanges = addDefaultValues(obj); + t.ok(anyChanges, 'Changes applied to the schema'); t.ok(obj.hasOwnProperty('email'), 'email added'); t.end(); @@ -18,8 +21,8 @@ tape('add email to schema if not defined yet', function (t) { tape('add password to schema if not defined yet', function (t) { var obj = { email: { type: 'email' } }; - var anyChanges = addDefaultValues(obj); + t.ok(anyChanges, 'Changes applied to the schema'); t.ok(obj.hasOwnProperty('password'), 'password added'); t.end(); @@ -30,8 +33,8 @@ tape('do not add default values if they are already defined', function (t) { email: { type: 'email' }, password: { type: 'password' } }; - var anyChanges = addDefaultValues(obj); + t.ok(!anyChanges, 'No changes applied to the schema'); t.end(); }); diff --git a/test/helpers/hooks.js b/test/helpers/hooks.js index 84b9034..0f3eaa4 100644 --- a/test/helpers/hooks.js +++ b/test/helpers/hooks.js @@ -2,6 +2,7 @@ * Test hooks for tape taken from: * https://github.com/substack/tape/issues/59#issuecomment-32808769 */ +'use strict'; exports.beforeEach = function (_test, handler) { return function tapish (name, listener) { diff --git a/test/index.test.js b/test/index.test.js index e88382b..8edb00f 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -1,3 +1,5 @@ +'use strict'; + var fs = require('fs'); var Hapi = require('hapi'); var tape = require('tape'); diff --git a/test/load_schema.test.js b/test/load_schema.test.js index 69790c4..5f1cd40 100644 --- a/test/load_schema.test.js +++ b/test/load_schema.test.js @@ -1,3 +1,5 @@ +'use strict'; + var tape = require('tape'); var loadSchema = require('../lib/load_schema.js'); var path = require('path'); @@ -15,28 +17,32 @@ tape('attempt to load a schema with a wrong path file', function (t) { }); tape('the schema is not a json object', function (t) { - loadSchema(path.join(__dirname, 'fixtures', 'wrong_schema.json'), function (error) { - t.ok(error, 'can\'t parse the content of the schema'); - t.equal( - error, - 'Error: the schema user file contains unconventianal type,' - + 'please make sure an schema object is defined', - 'error schema parse displayed properly' - ); - t.end(); - }); + loadSchema( + path.join(__dirname, 'fixtures', 'wrong_schema.json'), + function (error) { + t.ok(error, 'can\'t parse the content of the schema'); + t.equal( + error, + 'Error: the schema user file contains unconventianal type,' + + 'please make sure an schema object is defined', + 'error schema parse displayed properly' + ); + t.end(); + }); }); tape('load the schema', function (t) { - loadSchema(path.join(__dirname, 'fixtures', 'right_schema.json'), function (error, schema) { - t.ok(!error, 'no errors'); - t.deepEqual( - schema, - { - email: { type: 'email' }, password: { type: 'password' } - }, - 'the schema is loaded properly' - ); - t.end(); - }); + loadSchema( + path.join(__dirname, 'fixtures', 'right_schema.json'), + function (error, schema) { + t.ok(!error, 'no errors'); + t.deepEqual( + schema, + { + email: { type: 'email' }, password: { type: 'password' } + }, + 'the schema is loaded properly' + ); + t.end(); + }); }); diff --git a/test/update_schema.test.js b/test/update_schema.test.js index 497f055..3e0eaf2 100644 --- a/test/update_schema.test.js +++ b/test/update_schema.test.js @@ -1,12 +1,14 @@ +'use strict'; + var tape = require('tape'); var updateSchema = require('../lib/update_schema.js'); var path = require('path'); tape('attempt to update a schema with a wrong schema path', function (t) { var wrongPath = path.join(__dirname, 'fixtures', 'wrongpath', 'schema.json'); + updateSchema(wrongPath, {}, function (error) { t.ok(error, 'can\'t write a file with a wrong path'); t.end(); }); }); - From cc62b232c72d107b33b483eafae8455a71f5cb63 Mon Sep 17 00:00:00 2001 From: Elias Date: Wed, 28 Sep 2016 15:39:02 +0100 Subject: [PATCH 35/40] add config.env to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index deffcf2..f057392 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ node_modules coverage npm-debug.log .eslintrc* +config.env From 19889dac27502145dca695dc0e0e9548b2c3a183 Mon Sep 17 00:00:00 2001 From: Elias Date: Wed, 28 Sep 2016 17:45:10 +0100 Subject: [PATCH 36/40] bring branch coverage back up to 100% --- lib/sql_gen.js | 12 +++++++----- test/fixtures/schema.json | 2 +- test/sql_gen.test.js | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 40 insertions(+), 6 deletions(-) diff --git a/lib/sql_gen.js b/lib/sql_gen.js index b50307e..3cc4d01 100644 --- a/lib/sql_gen.js +++ b/lib/sql_gen.js @@ -44,7 +44,8 @@ exports.init = function init (config) { return ['CREATE TABLE IF NOT EXISTS "' + tableName + '"'] .concat('(' + columns.join(', ') + ')') - .join(' '); + .join(' ') + .trim(); }; @@ -63,7 +64,7 @@ exports.select = function select (tableName, options) { values = result.values; } - query = query.join(' '); + query = query.join(' ').trim(); return [query, values]; }; @@ -79,7 +80,8 @@ exports.insert = function insert (tableName, options) { .concat('(' + columns.join(', ') + ')') .concat('VALUES') .concat('(' + params.join(', ') + ')') - .join(' '); + .join(' ') + .trim(); return [query, values]; }; @@ -102,7 +104,7 @@ exports.update = function update (tableName, options) { values = result.values; } - query = query.join(' '); + query = query.join(' ').trim(); return [query, values]; }; @@ -116,7 +118,7 @@ exports.delete = function _delete (tableName, options) { query = result.query; values = result.values; - query = query.join(' '); + query = query.join(' ').trim(); return [query, values]; }; diff --git a/test/fixtures/schema.json b/test/fixtures/schema.json index 0967ef4..9e26dfe 100644 --- a/test/fixtures/schema.json +++ b/test/fixtures/schema.json @@ -1 +1 @@ -{} +{} \ No newline at end of file diff --git a/test/sql_gen.test.js b/test/sql_gen.test.js index c5a83ab..c6a6f9f 100644 --- a/test/sql_gen.test.js +++ b/test/sql_gen.test.js @@ -72,6 +72,22 @@ tape('::insert - generate SQL to insert a column into a table', function (t) { t.end(); }); +tape('::insert - generate SQL to insert blank col into table', function (t) { + var query = sqlGen.insert(schema.table_name, {}); + + t.equal( + query[0], + 'INSERT INTO "user_data" () VALUES ()', + 'Generate query for blank line' + ); + t.deepEqual( + query[1], + [], + 'Generate empty array' + ); + t.end(); +}); + tape('::update - generate SQL to update a column in a table', function (t) { var query = sqlGen.update( schema.table_name, { fields: { email: 'me@poop.com' } } @@ -90,6 +106,22 @@ tape('::update - generate SQL to update a column in a table', function (t) { t.end(); }); +tape('::update - generate SQL to update no fields of column', function (t) { + var query = sqlGen.update(schema.table_name, {}); + + t.equal( + query[0], + 'UPDATE "user_data" SET', + 'Generate query for blank line' + ); + t.deepEqual( + query[1], + [], + 'Generate empty array' + ); + t.end(); +}); + tape('::update - gen. SQL to update a col in table w/ where', function (t) { var query = sqlGen.update(schema.table_name, { fields: { email: 'me@poop.com' }, From 2148f3f88889a3bda89b861d3866e60288984215 Mon Sep 17 00:00:00 2001 From: Elias Date: Wed, 28 Sep 2016 17:47:32 +0100 Subject: [PATCH 37/40] add istanbul YAML --- .istanbul.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 .istanbul.yml diff --git a/.istanbul.yml b/.istanbul.yml new file mode 100644 index 0000000..d266ba9 --- /dev/null +++ b/.istanbul.yml @@ -0,0 +1,11 @@ +verbose: false +instrumentation: + root: ./src + excludes: [] + include-all-sources: true + check: + global: + branches : 100 + functions : 100 + lines : 100 + statements : 100 From 273bc9c1d1c611dec66a32141fefba5bcc973fe0 Mon Sep 17 00:00:00 2001 From: Elias Date: Wed, 28 Sep 2016 17:51:59 +0100 Subject: [PATCH 38/40] add postgres to travis to fix failing tests --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index cfcb81b..7730694 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,3 +4,5 @@ node_js: script: npm run coverage after_script: - npm run codecov +services: + - postgresql From 7389e597edeac1c80fa677988e2354e7d8ae95b8 Mon Sep 17 00:00:00 2001 From: Elias Date: Wed, 28 Sep 2016 18:38:24 +0100 Subject: [PATCH 39/40] create travis test database before script execution --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 7730694..34f1462 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,8 @@ language: node_js node_js: - "6" script: npm run coverage +before_script: + - psql -c 'CREATE DATABASE travis_ci_test;' -U postgres after_script: - npm run codecov services: From 281c8832d2497a491eaf04e2c4fdfb7ed6091fec Mon Sep 17 00:00:00 2001 From: Jack Rans Date: Wed, 28 Sep 2016 19:10:09 +0100 Subject: [PATCH 40/40] :art: Change ordering of require --- test/test_pg_client.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/test_pg_client.js b/test/test_pg_client.js index 3f2782a..a5356a7 100644 --- a/test/test_pg_client.js +++ b/test/test_pg_client.js @@ -1,8 +1,7 @@ 'use strict'; var pg = require('pg'); -var testDBUrl = process.env.TEST_DATABASE_URL; require('env2')('config.env'); -module.exports = new pg.Client(testDBUrl); +module.exports = new pg.Client(process.env.TEST_DATABASE_URL);