diff --git a/README.md b/README.md index 922034e..eedb4b8 100644 --- a/README.md +++ b/README.md @@ -29,16 +29,16 @@ Same as query but resolves an empty array if no records found. 'use strict'; -var mysql = require('mysql'), - config = require('config'), - dbPool = mysql.createPool(config.mysql) +const mysql = require('mysql'), + config = require('config'), + dbPool = mysql.createPool(config.mysql); -var DB = require('alien-node-mysql-utils')(dbPool), - validateAccountData = require('../some-validator'); +const DB = require('alien-node-mysql-utils')(dbPool), + validateAccountData = require('../some-validator'); -var createAndExecuteQuery = function(status) { - var query = 'SELECT * FROM accounts WHERE status = ?', - queryStatement = [query, [status]]; +const createAndExecuteQuery = (status) => { + const query = 'SELECT * FROM accounts WHERE status = ?', + queryStatement = [query, [status]]; return DB.query(queryStatement); }; @@ -48,8 +48,8 @@ var createAndExecuteQuery = function(status) { * @param {Number} status * @returns {Promise} */ -function getAccountsByStatus(status) { - validateAccountData({status : status}); +const getAccountsByStatus = status => { + validateAccountData({ status }); return createAndExecuteQuery(status); } @@ -62,12 +62,12 @@ module.exports = getAccountsByStatus; ```js -var getAccountsByStatus = require('../models/getAccountsByStatus'); +const getAccountsByStatus = require('../models/getAccountsByStatus'); -getAccountsByStatus('active').then(function(accounts) { +getAccountsByStatus('active').then(accounts => { // handle array of accounts here }) - .catch(function(err) { + .catch(err => { // handle "No records found" or other errors here }); @@ -77,12 +77,12 @@ getAccountsByStatus('active').then(function(accounts) { ```js -var getAccountsByStatus = require('../models/getAccountsByStatus'); +const getAccountsByStatus = require('../models/getAccountsByStatus'); -getAccountsByStatus('active').then(function(maybeAccounts) { +getAccountsByStatus('active').then(maybeAccounts => { // handle array of accounts or empty array here }) - .catch(function(err) { + .catch(err => { // handle errors here }); @@ -99,16 +99,16 @@ Same as lookup, but resolves `undefined` if no records are found. 'use strict'; -var mysql = require('mysql'), - config = require('config'), - dbPool = mysql.createPool(config.mysql) +const mysql = require('mysql'), + config = require('config'), + dbPool = mysql.createPool(config.mysql); -var DB = require('alien-node-mysql-utils')(dbPool), - validateAccountData = require('../some-validator'); +const DB = require('alien-node-mysql-utils')(dbPool), + validateAccountData = require('../some-validator'); -var createAndExecuteQuery = function(id) { - var query = 'SELECT * FROM accounts WHERE id = ?', - queryStatement = [query, [id]]; +const createAndExecuteQuery = id => { + const query = 'SELECT * FROM accounts WHERE id = ?', + queryStatement = [query, [id]]; return DB.lookup(queryStatement); }; @@ -118,8 +118,8 @@ var createAndExecuteQuery = function(id) { * @param {Number} id * @returns {Promise} */ -function getAccountById(id) { - validateAccountData({id : id}); +const getAccountById = id => { + validateAccountData({ id }); return createAndExecuteQuery(id); } @@ -132,13 +132,13 @@ module.exports = getAccountById; ```js -var getAccountById = require('../models/getAccountById'); +const getAccountById = require('../models/getAccountById'); -getAccountById(1234).then(function(account) { +getAccountById(1234).then(account => { // handle account object here }) - .catch(function(err) { + .catch(err => { // handle "No records found" or other errors here }); @@ -148,17 +148,121 @@ getAccountById(1234).then(function(account) { ```js -var getAccountById = require('../models/getAccountById'); +const getAccountById = require('../models/getAccountById'); -getAccountById(1234).then(function(maybeAccount) { +getAccountById(1234).then(maybeAccount => { // handle account object or undefined here }) - .catch(function(err) { + .catch(err => { // handle errors here }); ``` +## Transactions +This library supports some simple transaction abstractions to play nicely with your promise chains. + +The three methods you need to care about are : + - DB.beginTransaction() + - DB.addQueryToTransaction() + - DB.commit() + +These methods have a unique signature compared to the other methods for querying. Let's break them down: + +**DB.beginTransaction()** : `() -> Promise(connection)` + +This method will use the curried `dbPool` object provided during require... + +```js +const DB = require('alien-node-mysql-utils')(dbPool); +``` + +... and call the native `getConnection()` on it, then resolve the connection on its promise. + +This connection needs to be provided to the subsequent methods so the transaction knows how to commit and rollback. + +**DB.addQueryToTransaction()** : `connection -> query -> Promise({ data, connection })` + +This method accepts the connection object which you should have gotten from `DB.beginTransaction()`, along with the typical query which you give to +any other query method in this library. It behaves like `DB.querySafe()` in that it lets you +deal with all the data scrubbing and null-checks (resolves zero-or-more result sets and all `SELECT` statements +return an array). + +Please notice that this method returns the connection along with the data, so in the spirit of +keeping the unary promise chain data flow in mind, the promise will resolve a single object, +where the data lives in a `data` property, and the connection on a `connection` property. + +**DB.commit()** : `connection` + +This method accepts the connection object which you should have gotten from `DB.beginTransaction()`. It simply +resolves `true` if there are no errors, otherwise it rejects the promise with whatever error may happen to ruin your day. + +##### Suggested wrapper-model usage for transactions + +```js +const DB = require('alien-node-mysql-utils')(dbPool); + +const getUserBalance = id => connection => { + const query = 'SELECT balance FROM users WHERE id = ?', + queryStatement = [query, [id]]; + + return DB.addQueryToTransaction(connection, queryStatement); +}; + +const updateUserBalance = (id, amount) => connection => { + const query = 'UPDATE users SET balance = balance + ? WHERE id = ?', + queryStatement = [query, [amount, id]]; + + return DB.addQueryToTransaction(connection, queryStatement); +}; + +const ensurePositiveTransfer = amount => connection => { + if (amount > 0) { + return connection; + } else { + throw { + error : new Error('What are you doing?' ), + connection : transaction.connection + }; + }; +}; + +const ensureEnoughMoney = amount => transaction => { + const data = transaction.data || [{ balance : 0 }], + balance = data[0].balance || 0; + + if (amount <= balance) { + return transaction; + } else { + throw { + error : new Error('Broke ass' ), + connection : transaction.connection + }; + } +}; + +const senderUserId = 1234, + receiverUserId = 5678, + amountToSend = 500.45; + +const resolveConnection = o => o.connection; + +DB.beginTransaction() + .then(ensurePositiveTransfer(amountToSend)) + .then(getUserBalance(senderUserId)) + .then(ensureEnoughMoney(amountToSend)) + .then(resolveConnection) + .then(updateUserBalance(senderUserId, amountToSend * -1)) + .then(resolveConnection) + .then(updateUserBalance(receiverUserId, amountToSend)) + .then(resolveConnection) + .then(DB.commit) + .catch(exception => { + exception.connection.rollback(); + logger.error(exception.error); + }); + +``` ## TODO - Make the transform to/from column methods unbiased with decorator injection diff --git a/lib/DB.js b/lib/DB.js index 7f20d64..29ec845 100644 --- a/lib/DB.js +++ b/lib/DB.js @@ -1,10 +1,14 @@ 'use strict'; -var DB = function(dbPool) { +const DB = dbPool => { return { resolveOrRejectOnBooleanField : require('./methods/resolveOrRejectOnBooleanField'), + beginTransaction : require('./methods/beginTransaction')(dbPool), + getConnection : require('./methods/getConnection')(dbPool), query : require('./methods/query')(dbPool), querySafe : require('./methods/querySafe')(dbPool), + addQueryToTransaction : require('./methods/addQueryToTransaction'), + commit : require('./methods/commit'), lookup : require('./methods/lookup')(dbPool), lookupSafe : require('./methods/lookupSafe')(dbPool), fuzzify : require('./methods/fuzzify'), diff --git a/lib/constants.js b/lib/constants.js index 8275b16..35d439e 100644 --- a/lib/constants.js +++ b/lib/constants.js @@ -10,17 +10,21 @@ module.exports = { code : 6001, message : 'Cannot connect to MySQL. Make sure the database is running.' }, - DUPLICATE : function(message) { + DUPLICATE : (message) => { return { code : 6002, message : message }; }, - UNKNOWN : function(message) { + UNKNOWN : (message) => { return { code : 6999, message : message }; + }, + MISSING_CONNECTION : { + code : 6998, + message : 'There was a problem establishing a database connection. This is likely an application error and not a MySQL error.' } } }; diff --git a/lib/methods/_connectionHandle.js b/lib/methods/_connectionHandle.js index 4ee7983..9e117c7 100644 --- a/lib/methods/_connectionHandle.js +++ b/lib/methods/_connectionHandle.js @@ -1,29 +1,34 @@ 'use strict'; -var R = require('ramda'); +const _queryCallback = require('./_queryCallback'), + constants = require('../constants'); -var _queryCallback = require('./_queryCallback'), - constants = require('../constants'); +const _connectionHandle = (deferred, queryStatement, transaction, singleReturnItem, allowEmptyResponse) => (err, connection) => { -var _connectionHandle = R.curry(function(deferred, queryStatement, singleReturnItem, allowEmptyResponse, err, connection) { - - var preparedStatement = queryStatement[0], - valueSwapIns = queryStatement[1]; + const preparedStatement = queryStatement[0], + valueSwapIns = queryStatement[1]; if (!connection) { deferred.reject(constants.errors.NO_DB_CONNECTION); } if (err) { + + if (transaction) { + connection.rollback(); + } + + connection.release(); deferred.reject(err); } connection.query( preparedStatement, valueSwapIns, - _queryCallback(deferred, connection, singleReturnItem, allowEmptyResponse) + _queryCallback(deferred, connection, transaction, singleReturnItem, allowEmptyResponse) ); -}); + return deferred.promise; +}; module.exports = _connectionHandle; diff --git a/lib/methods/_queryCallback.js b/lib/methods/_queryCallback.js index 5890914..4ef02ac 100644 --- a/lib/methods/_queryCallback.js +++ b/lib/methods/_queryCallback.js @@ -1,44 +1,62 @@ 'use strict'; -var R = require('ramda'); +const R = require('ramda'); -var transformQueryResponse = require('./transformQueryResponse'), - ensureNotSingleItemArray = require('./ensureNotSingleItemArray'), - constants = require('../constants'); +const transformQueryResponse = require('./transformQueryResponse'), + ensureNotSingleItemArray = require('./ensureNotSingleItemArray'), + constants = require('../constants'); -var isNilOrEmpty = R.anyPass([R.isNil, R.isEmpty]); +const isNilOrEmpty = R.anyPass([R.isNil, R.isEmpty]); -var _queryCallback = R.curry(function(deferred, connection, singleReturnItem, allowEmptyResponse, err, data) { +const maybeRollbackAndRelease = (connection, transaction) => { + if (transaction) { + connection.rollback(); + } + connection.release(); +}; + +const maybeEnsureSingleItemArray = singleReturnItem => data => singleReturnItem ? ensureNotSingleItemArray(data) : data; + +const _queryCallback = (deferred, connection, transaction, singleReturnItem, allowEmptyResponse) => (err, data) => { if (err) { switch (R.prop('code', err)) { case 'ER_DUP_ENTRY' : - connection.release(); - return deferred.reject(constants.errors.DUPLICATE(R.prop('message', err))); + maybeRollbackAndRelease(connection, transaction); + deferred.reject(constants.errors.DUPLICATE(R.prop('message', err))); + break; default : - connection.release(); - return deferred.reject(constants.errors.UNKNOWN(R.prop('message', err))); + maybeRollbackAndRelease(connection, transaction); + deferred.reject(constants.errors.UNKNOWN(R.prop('message', err))); + break; } - } + return deferred.promise; + + } else { + + if (isNilOrEmpty(data)) { - if (isNilOrEmpty(data)) { - connection.release(); + if (allowEmptyResponse) { + deferred.resolve(singleReturnItem ? undefined : []) + } else { + connection.release(); + deferred.reject(constants.errors.NO_QUERY_RESULTS); + } + + return deferred.promise; - if (allowEmptyResponse) { - return deferred.resolve(singleReturnItem ? undefined : []) } else { - return deferred.reject(constants.errors.NO_QUERY_RESULTS); - } - } + const transformedData = R.compose(transformQueryResponse, maybeEnsureSingleItemArray(singleReturnItem))(data); - if (singleReturnItem === true) { - data = ensureNotSingleItemArray(data); - } + if (!transaction) { + connection.release(); + } - data = transformQueryResponse(data); + deferred.resolve(transaction ? { data : transformedData, connection } : transformedData); - connection.release(); - return deferred.resolve(data); -}); + return deferred.promise; + } + } +}; module.exports = _queryCallback; diff --git a/lib/methods/_transaction.js b/lib/methods/_transaction.js index 0b91f1c..aa4aac5 100644 --- a/lib/methods/_transaction.js +++ b/lib/methods/_transaction.js @@ -1,15 +1,18 @@ 'use strict'; -var R = require('ramda'), - Q = require('q'); +const R = require('ramda'), + Q = require('q'); -var _connectionHandle = require('./_connectionHandle'), - transformQueryResponse = require('./transformQueryResponse'); +const _connectionHandle = require('./_connectionHandle'); + +const _transaction = R.curry((connection, singleReturnItem, allowEmptyResponse, dbPool, queryStatement) => { + const deferred = Q.defer(), + transaction = !!connection, + cb = _connectionHandle(deferred, queryStatement, transaction, singleReturnItem, allowEmptyResponse); + + connection ? cb(null, connection) : dbPool.getConnection(cb); -var _transaction = R.curry(function(transformQueryResponse, singleReturnItem, allowEmptyResponse, dbPool, queryStatement) { - var deferred = Q.defer(); - dbPool.getConnection(_connectionHandle(deferred, queryStatement, singleReturnItem, allowEmptyResponse)); return deferred.promise; }); -module.exports = _transaction(transformQueryResponse); +module.exports = _transaction; diff --git a/lib/methods/addQueryToTransaction.js b/lib/methods/addQueryToTransaction.js new file mode 100644 index 0000000..7ba20d9 --- /dev/null +++ b/lib/methods/addQueryToTransaction.js @@ -0,0 +1,20 @@ +'use strict'; + +const R = require('ramda'); + +const _transaction = require('./_transaction'); + +const EXPECT_SINGLE_RETURN_ITEM = false, + ALLOW_EMPTY_RESPONSE = true, + DB_POOL = null; + +/** + * Execute a query on a transaction, expecting the connection returned. + */ +module.exports = R.curry((connection, queryStatement) => _transaction( + connection, + EXPECT_SINGLE_RETURN_ITEM, + ALLOW_EMPTY_RESPONSE, + DB_POOL, + queryStatement +)); diff --git a/lib/methods/beginTransaction.js b/lib/methods/beginTransaction.js new file mode 100644 index 0000000..a2ba18c --- /dev/null +++ b/lib/methods/beginTransaction.js @@ -0,0 +1,26 @@ +'use strict'; + +const Q = require('q'); + +const getConnection = require('./getConnection'); + +module.exports = dbPool => () => { + const deferred = Q.defer(); + + getConnection(dbPool)() + .then(connection => { + connection.beginTransaction(err => { + if (err) { + deferred.reject(err); + } else { + deferred.resolve(connection); + } + }); + }) + .catch(err => { + deferred.reject(err); + }); + + return deferred.promise; +}; + diff --git a/lib/methods/commit.js b/lib/methods/commit.js new file mode 100644 index 0000000..1552902 --- /dev/null +++ b/lib/methods/commit.js @@ -0,0 +1,20 @@ +'use strict'; + +const Q = require('q'); + +module.exports = connection => { + const deferred = Q.defer(); + + connection.commit(err => { + if (err) { + connection.rollback(); + connection.release(); + deferred.reject(err); + } else { + connection.release(); + deferred.resolve(true); + } + }); + + return deferred.promise; +}; diff --git a/lib/methods/createNowDateString.js b/lib/methods/createNowDateString.js index ddcc398..ff00bbd 100644 --- a/lib/methods/createNowDateString.js +++ b/lib/methods/createNowDateString.js @@ -1,13 +1,7 @@ 'use strict'; -var moment = require('moment'); - /** * Create a date string in the format [YYYY-MM-DD] * @returns {String} */ -var createNowDateString = function() { - return moment().format('YYYY-MM-DD'); -}; - -module.exports = createNowDateString; +module.exports = () => require('moment')().format('YYYY-MM-DD'); diff --git a/lib/methods/createNowTimestamp.js b/lib/methods/createNowTimestamp.js index 4b1f431..c90401c 100644 --- a/lib/methods/createNowTimestamp.js +++ b/lib/methods/createNowTimestamp.js @@ -1,13 +1,7 @@ 'use strict'; -var moment = require('moment'); - /** * Create a timestamp string in the format [YYYY-MM-DD HH:mm:ss] * @returns {String} */ -var createNowTimestamp = function() { - return moment().format('YYYY-MM-DD HH:mm:ss'); -}; - -module.exports = createNowTimestamp; +module.exports = () => require('moment')().format('YYYY-MM-DD HH:mm:ss'); diff --git a/lib/methods/createNowUnixTime.js b/lib/methods/createNowUnixTime.js index 0950aff..5361630 100644 --- a/lib/methods/createNowUnixTime.js +++ b/lib/methods/createNowUnixTime.js @@ -1,13 +1,7 @@ 'use strict'; -var moment = require('moment'); - /** * Create a unix time stamp for "now" * @returns {number} */ -var createNowUnixTime = function() { - return moment().format('X') * 1 -}; - -module.exports = createNowUnixTime; +module.exports = () => require('moment')().format('X') * 1; diff --git a/lib/methods/ensureNotSingleItemArray.js b/lib/methods/ensureNotSingleItemArray.js index 4f2b01c..a5b896c 100644 --- a/lib/methods/ensureNotSingleItemArray.js +++ b/lib/methods/ensureNotSingleItemArray.js @@ -1,6 +1,6 @@ 'use strict'; -var R = require('ramda'); +const R = require('ramda'); /** * Accepts an array. @@ -16,6 +16,6 @@ var R = require('ramda'); * @param {Array} * @returns {*} */ -var ensureNotSingleItemArray = R.ifElse(R.compose(R.identical(R.__, 1), R.length), R.head, R.identity); +const ensureNotSingleItemArray = R.ifElse(R.compose(R.identical(R.__, 1), R.length), R.head, R.identity); module.exports = ensureNotSingleItemArray; diff --git a/lib/methods/fuzzify.js b/lib/methods/fuzzify.js index 156da4f..d1e04a7 100644 --- a/lib/methods/fuzzify.js +++ b/lib/methods/fuzzify.js @@ -1,12 +1,12 @@ 'use strict'; -var R = require('ramda'); +const R = require('ramda'); /** * Return fuzzy search wrapper around provided search term. * @param {String} searchTerm * @returns {String} */ -var fuzzify = R.replace(/q/, R.__, '%q%'); +const fuzzify = R.replace(/q/, R.__, '%q%'); module.exports = fuzzify; diff --git a/lib/methods/getConnection.js b/lib/methods/getConnection.js new file mode 100644 index 0000000..ba3dbf0 --- /dev/null +++ b/lib/methods/getConnection.js @@ -0,0 +1,31 @@ +'use strict'; + +const Q = require('q'); + +const constants = require('../constants'); + +const getConnection = dbPool => () => { + const deferred = Q.defer(); + + try { + + dbPool.getConnection((err, connection) => { + if (err) { + deferred.reject(err); + } else { + if (connection) { + deferred.resolve(connection); + } else { + deferred.reject(constants.errors.MISSING_CONNECTION); + } + } + }); + + } catch(err) { + deferred.reject(err); + } + + return deferred.promise; +}; + +module.exports = getConnection; diff --git a/lib/methods/lookup.js b/lib/methods/lookup.js index 6d18ce3..e3079e5 100644 --- a/lib/methods/lookup.js +++ b/lib/methods/lookup.js @@ -1,9 +1,10 @@ 'use strict'; -var EXPECT_SINGLE_RETURN_ITEM = true, - ALLOW_EMPTY_RESPONSE = false; +const EXPECT_SINGLE_RETURN_ITEM = true, + ALLOW_EMPTY_RESPONSE = false, + CONNECTION = null; /** * Execute a query, expecting a single item returned. */ -module.exports = require('./_transaction')(EXPECT_SINGLE_RETURN_ITEM, ALLOW_EMPTY_RESPONSE); +module.exports = require('./_transaction')(CONNECTION, EXPECT_SINGLE_RETURN_ITEM, ALLOW_EMPTY_RESPONSE); diff --git a/lib/methods/lookupSafe.js b/lib/methods/lookupSafe.js index 038982a..4edf259 100644 --- a/lib/methods/lookupSafe.js +++ b/lib/methods/lookupSafe.js @@ -1,9 +1,10 @@ 'use strict'; -var EXPECT_SINGLE_RETURN_ITEM = true, - ALLOW_EMPTY_RESPONSE = true; +const EXPECT_SINGLE_RETURN_ITEM = true, + ALLOW_EMPTY_RESPONSE = true, + CONNECTION = null; /** * Execute a query, expecting a single item returned. */ -module.exports = require('./_transaction')(EXPECT_SINGLE_RETURN_ITEM, ALLOW_EMPTY_RESPONSE); +module.exports = require('./_transaction')(CONNECTION, EXPECT_SINGLE_RETURN_ITEM, ALLOW_EMPTY_RESPONSE); diff --git a/lib/methods/prepareProvidedFieldsForSet.js b/lib/methods/prepareProvidedFieldsForSet.js index e996612..a7ae1dc 100644 --- a/lib/methods/prepareProvidedFieldsForSet.js +++ b/lib/methods/prepareProvidedFieldsForSet.js @@ -1,21 +1,21 @@ 'use strict'; -var R = require('ramda'); +const R = require('ramda'); -var transformToColumn = require('./transformToColumn'), - commaSeparate = R.join(','); +const transformToColumn = require('./transformToColumn'), + commaSeparate = R.join(','); /** * Converts an array of fields into a prepared statement for SQL * @param {Array} fields * @returns {String} */ -var prepareProvidedFieldsForSet = function(fields) { - var fieldsCopy = R.clone(fields).sort(); - var prepareField = R.curry(function(marker, field) { +const prepareProvidedFieldsForSet = (fields) => { + const fieldsCopy = R.clone(fields).sort(); + const prepareField = R.curry((marker, field) => { return transformToColumn(field) + ' = ' + marker; }); - var prepareFields = R.compose(commaSeparate, R.map(prepareField('?'))); + const prepareFields = R.compose(commaSeparate, R.map(prepareField('?'))); return prepareFields(fieldsCopy); }; diff --git a/lib/methods/prepareValues.js b/lib/methods/prepareValues.js index 7e5f26a..0868de8 100644 --- a/lib/methods/prepareValues.js +++ b/lib/methods/prepareValues.js @@ -1,17 +1,17 @@ 'use strict'; -var R = require('ramda'); +const R = require('ramda'); /** * Prepares an array of values for a prepared statement. * @param {Object} data * @returns {Array} */ -var prepareValues = function(data) { - var dataCopy = R.clone(data), - getProp = R.flip(R.prop), - prop = getProp(dataCopy), - fields = Object.keys(dataCopy).sort(); +const prepareValues = (data) => { + const dataCopy = R.clone(data), + getProp = R.flip(R.prop), + prop = getProp(dataCopy), + fields = Object.keys(dataCopy).sort(); return fields.map(prop); }; diff --git a/lib/methods/query.js b/lib/methods/query.js index d7c5872..ff74fb5 100644 --- a/lib/methods/query.js +++ b/lib/methods/query.js @@ -1,9 +1,10 @@ 'use strict'; -var EXPECT_SINGLE_RETURN_ITEM = false, - ALLOW_EMPTY_RESPONSE = false; +const EXPECT_SINGLE_RETURN_ITEM = false, + ALLOW_EMPTY_RESPONSE = false, + CONNECTION = null; /** * Execute a query, expecting an array returned. */ -module.exports = require('./_transaction')(EXPECT_SINGLE_RETURN_ITEM, ALLOW_EMPTY_RESPONSE); +module.exports = require('./_transaction')(CONNECTION, EXPECT_SINGLE_RETURN_ITEM, ALLOW_EMPTY_RESPONSE); diff --git a/lib/methods/querySafe.js b/lib/methods/querySafe.js index c627f31..c1af2b8 100644 --- a/lib/methods/querySafe.js +++ b/lib/methods/querySafe.js @@ -1,9 +1,10 @@ 'use strict'; -var EXPECT_SINGLE_RETURN_ITEM = false, - ALLOW_EMPTY_RESPONSE = true; +const EXPECT_SINGLE_RETURN_ITEM = false, + ALLOW_EMPTY_RESPONSE = true, + CONNECTION = null; /** * Execute a query, expecting an array returned. */ -module.exports = require('./_transaction')(EXPECT_SINGLE_RETURN_ITEM, ALLOW_EMPTY_RESPONSE); +module.exports = require('./_transaction')(CONNECTION, EXPECT_SINGLE_RETURN_ITEM, ALLOW_EMPTY_RESPONSE); diff --git a/lib/methods/resolveOrRejectOnBooleanField.js b/lib/methods/resolveOrRejectOnBooleanField.js index 8473c45..01c1801 100644 --- a/lib/methods/resolveOrRejectOnBooleanField.js +++ b/lib/methods/resolveOrRejectOnBooleanField.js @@ -1,6 +1,6 @@ 'use strict'; -var R = require('ramda'); +const R = require('ramda'); /** * Accepts a promise, and based on the existence of the property value, resolves or @@ -9,7 +9,7 @@ var R = require('ramda'); * @param {String} field * @param {*} data */ -var resolveOrRejectOnBooleanField = R.curry(function(deferred, field, data) { +const resolveOrRejectOnBooleanField = R.curry((deferred, field, data) => { if (R.isNil(data[field])) { throw new Error('No field found matching "' + field + '"'); diff --git a/lib/methods/transformFromColumn.js b/lib/methods/transformFromColumn.js index d8cf6d1..52e3903 100644 --- a/lib/methods/transformFromColumn.js +++ b/lib/methods/transformFromColumn.js @@ -1,15 +1,15 @@ 'use strict'; -var R = require('ramda'); +const R = require('ramda'); /** * Properties in JS use camelCaseNames * @param {String} str * @returns {String} */ -var delimiterAndNextLetter = /_(.)/g; -var alphaNumeric = /[a-z0-9]/gi; -var uppercaseAlphaNumeric = R.compose(R.toUpper, R.head, R.match(alphaNumeric)); -var transformFromColumn = R.replace(delimiterAndNextLetter, uppercaseAlphaNumeric); +const delimiterAndNextLetter = /_(.)/g; +const alphaNumeric = /[a-z0-9]/gi; +const uppercaseAlphaNumeric = R.compose(R.toUpper, R.head, R.match(alphaNumeric)); +const transformFromColumn = R.replace(delimiterAndNextLetter, uppercaseAlphaNumeric); module.exports = transformFromColumn; diff --git a/lib/methods/transformQueryResponse.js b/lib/methods/transformQueryResponse.js index af7e66f..8b88e84 100644 --- a/lib/methods/transformQueryResponse.js +++ b/lib/methods/transformQueryResponse.js @@ -1,14 +1,14 @@ 'use strict'; -var R = require('ramda'); +const R = require('ramda'); -var transformFromColumn = require('./transformFromColumn'); +const transformFromColumn = require('./transformFromColumn'); -var transformPair = function(pair) { +const transformPair = (pair) => { return [transformFromColumn(pair[0]), pair[1]]; }; -var transformDBObject = function(obj) { +const transformDBObject = (obj) => { return R.fromPairs(R.map(transformPair, R.toPairs(obj))); }; @@ -19,8 +19,8 @@ var transformDBObject = function(obj) { * @param [Object|Array] data * @returns {Object|Array} */ -function transformQueryResponse(data) { +const transformQueryResponse = data => { return R.is(Array, data) ? R.map(transformDBObject, data) : transformDBObject(data); -} +}; module.exports = transformQueryResponse; diff --git a/lib/methods/transformToColumn.js b/lib/methods/transformToColumn.js index ec9e280..21cbd78 100644 --- a/lib/methods/transformToColumn.js +++ b/lib/methods/transformToColumn.js @@ -1,17 +1,17 @@ 'use strict'; -var R = require('ramda'); +const R = require('ramda'); /** * Fields in the DB use underscored_names * @param {String} str * @returns {String} */ -var preHumpCharacters = /([a-z\d])([A-Z0-9])/g; -var underscoreDelimit = R.replace(preHumpCharacters, '$1_$2'); -var wrapWithTicks = function(str) { +const preHumpCharacters = /([a-z\d])([A-Z0-9])/g; +const underscoreDelimit = R.replace(preHumpCharacters, '$1_$2'); +const wrapWithTicks = (str) => { return '`' + str + '`'; }; -var transformToColumn = R.compose(wrapWithTicks, R.toLower, underscoreDelimit); +const transformToColumn = R.compose(wrapWithTicks, R.toLower, underscoreDelimit); module.exports = transformToColumn; diff --git a/package.json b/package.json index 83ab507..64b82e1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name" : "alien-node-mysql-utils", - "version" : "0.5.1", + "version" : "0.5.2", "description" : "Helper functions for MySQL on NodeJS", "main" : "lib/DB.js", "dependencies" : { diff --git a/spec/_queryCallbackSpec.js b/spec/_queryCallbackSpec.js new file mode 100644 index 0000000..52f0554 --- /dev/null +++ b/spec/_queryCallbackSpec.js @@ -0,0 +1,625 @@ +'use strict'; + +const Q = require('q'), + R = require('ramda'); + +const constants = require('../lib/constants'), + _queryCallback = require('../lib/methods/_queryCallback'); + +const MYSQL_DUPLICATE_ENTRY_ERROR_CODE = 'ER_DUP_ENTRY'; + +const FAKE_CONNECTION = { + rollback : () => {}, + release : () => {} +}; + +const FAKE_ERROR = new Error('foobar'), + FAKE_DUPLICATE_RECORD_ERROR = {code : MYSQL_DUPLICATE_ENTRY_ERROR_CODE, message : 'foo duplicate'}, + FAKE_RESPONSE_SINGLE = {foo : 'bar'}, + FAKE_RESPONSE_SINGLE_ARRAY = [FAKE_RESPONSE_SINGLE], + FAKE_RESPONSE_ARRAY = [{foo : 'bar'}, {baz : 'bat'}, {biz : 'buz'}]; + +const IS_TRANSACTION_FALSE = false, + IS_TRANSACTION_TRUE = true, + SINGLE_RETURN_ITEM_FALSE = false, + SINGLE_RETURN_ITEM_TRUE = true, + ALLOW_EMPTY_RESPONSE_FALSE = false, + ALLOW_EMPTY_RESPONSE_TRUE = true; + +describe('_queryCallback', () => { + + beforeEach(() => { + spyOn(FAKE_CONNECTION, 'rollback'); + spyOn(FAKE_CONNECTION, 'release'); + }); + + it('invokes a transactional queryOnTransaction() callback with no errors on a multi-item array', done => { + + const deferred = Q.defer(); + + deferred.promise.then(res => { + expect(res.connection).toEqual(FAKE_CONNECTION); + expect(res.data).toEqual(FAKE_RESPONSE_ARRAY); + done(); + }); + + _queryCallback( + deferred, + FAKE_CONNECTION, + IS_TRANSACTION_TRUE, + SINGLE_RETURN_ITEM_FALSE, + ALLOW_EMPTY_RESPONSE_FALSE + )(null, FAKE_RESPONSE_ARRAY); + }); + + it('invokes a transactional, queryOnTransaction() callback with errors on a multi-item array', done => { + + const deferred = Q.defer(); + + deferred.promise.catch(err => { + expect(err).toEqual(constants.errors.UNKNOWN(FAKE_ERROR.message)); + expect(FAKE_CONNECTION.rollback).toHaveBeenCalled(); + done(); + }); + + _queryCallback( + deferred, + FAKE_CONNECTION, + IS_TRANSACTION_TRUE, + SINGLE_RETURN_ITEM_FALSE, + ALLOW_EMPTY_RESPONSE_FALSE + )(FAKE_ERROR); + }); + + it('invokes a non-transaction, query() callback with no errors on a multi-item array', done => { + + const deferred = Q.defer(); + + deferred.promise.then(res => { + expect(res).toEqual(FAKE_RESPONSE_ARRAY); + expect(FAKE_CONNECTION.release).toHaveBeenCalled(); + done(); + }); + + _queryCallback( + deferred, + FAKE_CONNECTION, + IS_TRANSACTION_FALSE, + SINGLE_RETURN_ITEM_FALSE, + ALLOW_EMPTY_RESPONSE_FALSE + )(null, FAKE_RESPONSE_ARRAY); + }); + + it('invokes a non-transaction, query() callback with no errors on an empty result', done => { + + const deferred = Q.defer(); + + deferred.promise.catch(err => { + expect(err).toEqual(constants.errors.NO_QUERY_RESULTS); + expect(FAKE_CONNECTION.release).toHaveBeenCalled(); + done(); + }); + + _queryCallback( + deferred, + FAKE_CONNECTION, + IS_TRANSACTION_FALSE, + SINGLE_RETURN_ITEM_FALSE, + ALLOW_EMPTY_RESPONSE_FALSE + )(null, []); + }); + + it('invokes a non-transaction, query() callback with no errors on a single-item array', done => { + + const deferred = Q.defer(); + + deferred.promise.then(res => { + expect(res).toEqual(FAKE_RESPONSE_SINGLE_ARRAY); + expect(FAKE_CONNECTION.release).toHaveBeenCalled(); + done(); + }); + + _queryCallback( + deferred, + FAKE_CONNECTION, + IS_TRANSACTION_FALSE, + SINGLE_RETURN_ITEM_FALSE, + ALLOW_EMPTY_RESPONSE_FALSE + )(null, FAKE_RESPONSE_SINGLE_ARRAY); + }); + + it('invokes a non-transaction, query() callback with no errors on a response object', done => { + + const deferred = Q.defer(); + + deferred.promise.then(res => { + expect(res).toEqual(FAKE_RESPONSE_SINGLE); + expect(FAKE_CONNECTION.release).toHaveBeenCalled(); + done(); + }); + + _queryCallback( + deferred, + FAKE_CONNECTION, + IS_TRANSACTION_FALSE, + SINGLE_RETURN_ITEM_FALSE, + ALLOW_EMPTY_RESPONSE_FALSE + )(null, FAKE_RESPONSE_SINGLE); + }); + + it('invokes a non-transaction, querySafe() callback with no errors on a multi-item array', done => { + + const deferred = Q.defer(); + + deferred.promise.then(res => { + expect(res).toEqual(FAKE_RESPONSE_ARRAY); + expect(FAKE_CONNECTION.release).toHaveBeenCalled(); + done(); + }); + + _queryCallback( + deferred, + FAKE_CONNECTION, + IS_TRANSACTION_FALSE, + SINGLE_RETURN_ITEM_FALSE, + ALLOW_EMPTY_RESPONSE_TRUE + )(null, FAKE_RESPONSE_ARRAY); + }); + + it('invokes a non-transaction, querySafe() callback with no errors on an empty response', done => { + + const deferred = Q.defer(); + + deferred.promise.then(res => { + expect(res).toEqual([]); + done(); + }); + + _queryCallback( + deferred, + FAKE_CONNECTION, + IS_TRANSACTION_FALSE, + SINGLE_RETURN_ITEM_FALSE, + ALLOW_EMPTY_RESPONSE_TRUE + )(null, []); + }); + + it('invokes a non-transaction, querySafe() callback with no errors on a single-item array', done => { + + const deferred = Q.defer(); + + deferred.promise.then(res => { + expect(res).toEqual(FAKE_RESPONSE_SINGLE_ARRAY); + expect(FAKE_CONNECTION.release).toHaveBeenCalled(); + done(); + }); + + _queryCallback( + deferred, + FAKE_CONNECTION, + IS_TRANSACTION_FALSE, + SINGLE_RETURN_ITEM_FALSE, + ALLOW_EMPTY_RESPONSE_TRUE + )(null, FAKE_RESPONSE_SINGLE_ARRAY); + }); + + it('invokes a non-transaction, querySafe() callback with no errors on a response object', done => { + + const deferred = Q.defer(); + + deferred.promise.then(res => { + expect(res).toEqual(FAKE_RESPONSE_SINGLE); + expect(FAKE_CONNECTION.release).toHaveBeenCalled(); + done(); + }); + + _queryCallback( + deferred, + FAKE_CONNECTION, + IS_TRANSACTION_FALSE, + SINGLE_RETURN_ITEM_FALSE, + ALLOW_EMPTY_RESPONSE_TRUE + )(null, FAKE_RESPONSE_SINGLE); + }); + + + it('invokes a non-transaction, lookup() callback with no errors on a multi-item array', done => { + + const deferred = Q.defer(); + + deferred.promise.then(res => { + expect(res).toEqual(FAKE_RESPONSE_ARRAY); + expect(FAKE_CONNECTION.release).toHaveBeenCalled(); + done(); + }); + + _queryCallback( + deferred, + FAKE_CONNECTION, + IS_TRANSACTION_FALSE, + SINGLE_RETURN_ITEM_TRUE, + ALLOW_EMPTY_RESPONSE_FALSE + )(null, FAKE_RESPONSE_ARRAY); + }); + + it('invokes a non-transaction, lookup() callback with no errors on an empty response', done => { + + const deferred = Q.defer(); + + deferred.promise.catch(err => { + expect(err).toEqual(constants.errors.NO_QUERY_RESULTS); + expect(FAKE_CONNECTION.release).toHaveBeenCalled(); + done(); + }); + + _queryCallback( + deferred, + FAKE_CONNECTION, + IS_TRANSACTION_FALSE, + SINGLE_RETURN_ITEM_TRUE, + ALLOW_EMPTY_RESPONSE_FALSE + )(null, []); + }); + + it('invokes a non-transaction, lookup() callback with no errors on a single-item array', done => { + + const deferred = Q.defer(); + + deferred.promise.then(res => { + expect(res).toEqual(FAKE_RESPONSE_SINGLE); + expect(FAKE_CONNECTION.release).toHaveBeenCalled(); + done(); + }); + + _queryCallback( + deferred, + FAKE_CONNECTION, + IS_TRANSACTION_FALSE, + SINGLE_RETURN_ITEM_TRUE, + ALLOW_EMPTY_RESPONSE_FALSE + )(null, FAKE_RESPONSE_SINGLE_ARRAY); + }); + + it('invokes a non-transaction, lookup() callback with no errors on a response object', done => { + + const deferred = Q.defer(); + + deferred.promise.then(res => { + expect(res).toEqual(FAKE_RESPONSE_SINGLE); + expect(FAKE_CONNECTION.release).toHaveBeenCalled(); + done(); + }); + + _queryCallback( + deferred, + FAKE_CONNECTION, + IS_TRANSACTION_FALSE, + SINGLE_RETURN_ITEM_TRUE, + ALLOW_EMPTY_RESPONSE_FALSE + )(null, FAKE_RESPONSE_SINGLE); + }); + + it('invokes a non-transaction, lookupSafe() callback with no errors on a multi-item array', done => { + + const deferred = Q.defer(); + + deferred.promise.then(res => { + expect(res).toEqual(FAKE_RESPONSE_ARRAY); + expect(FAKE_CONNECTION.release).toHaveBeenCalled(); + done(); + }); + + _queryCallback( + deferred, + FAKE_CONNECTION, + IS_TRANSACTION_FALSE, + SINGLE_RETURN_ITEM_TRUE, + ALLOW_EMPTY_RESPONSE_TRUE + )(null, FAKE_RESPONSE_ARRAY); + }); + + it('invokes a non-transaction, lookupSafe() callback with no errors on an empty response', done => { + + const deferred = Q.defer(); + + deferred.promise.then(res => { + expect(res).toEqual(undefined); + done(); + }); + + _queryCallback( + deferred, + FAKE_CONNECTION, + IS_TRANSACTION_FALSE, + SINGLE_RETURN_ITEM_TRUE, + ALLOW_EMPTY_RESPONSE_TRUE + )(null, []); + }); + + it('invokes a non-transaction, lookupSafe() callback with no errors on a single-item array', done => { + + const deferred = Q.defer(); + + deferred.promise.then(res => { + expect(res).toEqual(FAKE_RESPONSE_SINGLE); + expect(FAKE_CONNECTION.release).toHaveBeenCalled(); + done(); + }); + + _queryCallback( + deferred, + FAKE_CONNECTION, + IS_TRANSACTION_FALSE, + SINGLE_RETURN_ITEM_TRUE, + ALLOW_EMPTY_RESPONSE_TRUE + )(null, FAKE_RESPONSE_SINGLE_ARRAY); + }); + + it('invokes a non-transaction, lookupSafe() callback with no errors on a response object', done => { + + const deferred = Q.defer(); + + deferred.promise.then(res => { + expect(res).toEqual(FAKE_RESPONSE_SINGLE); + expect(FAKE_CONNECTION.release).toHaveBeenCalled(); + done(); + }); + + _queryCallback( + deferred, + FAKE_CONNECTION, + IS_TRANSACTION_FALSE, + SINGLE_RETURN_ITEM_TRUE, + ALLOW_EMPTY_RESPONSE_TRUE + )(null, FAKE_RESPONSE_SINGLE); + }); + + it('handles a duplicate-record error no differently than other errors', done => { + + const deferred = Q.defer(); + + deferred.promise.catch(err => { + expect(err).toEqual(constants.errors.DUPLICATE(FAKE_DUPLICATE_RECORD_ERROR.message)); + expect(FAKE_CONNECTION.release).toHaveBeenCalled(); + done(); + }); + + _queryCallback( + deferred, + FAKE_CONNECTION, + IS_TRANSACTION_FALSE, + SINGLE_RETURN_ITEM_FALSE, + ALLOW_EMPTY_RESPONSE_FALSE + )(FAKE_DUPLICATE_RECORD_ERROR); + }); + + it('invokes a non-transaction, query() callback with errors on a multi-item array', done => { + + const deferred = Q.defer(); + + deferred.promise.catch(err => { + expect(err).toEqual(constants.errors.UNKNOWN(FAKE_ERROR.message)); + expect(FAKE_CONNECTION.release).toHaveBeenCalled(); + done(); + }); + + _queryCallback( + deferred, + FAKE_CONNECTION, + IS_TRANSACTION_FALSE, + SINGLE_RETURN_ITEM_FALSE, + ALLOW_EMPTY_RESPONSE_FALSE + )(FAKE_ERROR); + }); + + it('invokes a non-transaction, query() callback with errors on a single-item array', done => { + + const deferred = Q.defer(); + + deferred.promise.catch(err => { + expect(err).toEqual(constants.errors.UNKNOWN(FAKE_ERROR.message)); + expect(FAKE_CONNECTION.release).toHaveBeenCalled(); + done(); + }); + + _queryCallback( + deferred, + FAKE_CONNECTION, + IS_TRANSACTION_FALSE, + SINGLE_RETURN_ITEM_FALSE, + ALLOW_EMPTY_RESPONSE_FALSE + )(FAKE_ERROR); + }); + + it('invokes a non-transaction, query() callback with errors on a response object', done => { + + const deferred = Q.defer(); + + deferred.promise.catch(err => { + expect(err).toEqual(constants.errors.UNKNOWN(FAKE_ERROR.message)); + expect(FAKE_CONNECTION.release).toHaveBeenCalled(); + done(); + }); + + _queryCallback( + deferred, + FAKE_CONNECTION, + IS_TRANSACTION_FALSE, + SINGLE_RETURN_ITEM_FALSE, + ALLOW_EMPTY_RESPONSE_FALSE + )(FAKE_ERROR); + }); + + it('invokes a non-transaction, querySafe() callback with errors on a multi-item array', done => { + + const deferred = Q.defer(); + + deferred.promise.catch(err => { + expect(err).toEqual(constants.errors.UNKNOWN(FAKE_ERROR.message)); + expect(FAKE_CONNECTION.release).toHaveBeenCalled(); + done(); + }); + + _queryCallback( + deferred, + FAKE_CONNECTION, + IS_TRANSACTION_FALSE, + SINGLE_RETURN_ITEM_FALSE, + ALLOW_EMPTY_RESPONSE_TRUE + )(FAKE_ERROR); + }); + + it('invokes a non-transaction, querySafe() callback with errors on a single-item array', done => { + + const deferred = Q.defer(); + + deferred.promise.catch(err => { + expect(err).toEqual(constants.errors.UNKNOWN(FAKE_ERROR.message)); + expect(FAKE_CONNECTION.release).toHaveBeenCalled(); + done(); + }); + + _queryCallback( + deferred, + FAKE_CONNECTION, + IS_TRANSACTION_FALSE, + SINGLE_RETURN_ITEM_FALSE, + ALLOW_EMPTY_RESPONSE_TRUE + )(FAKE_ERROR); + }); + + it('invokes a non-transaction, querySafe() callback with errors on a response object', done => { + + const deferred = Q.defer(); + + deferred.promise.catch(err => { + expect(err).toEqual(constants.errors.UNKNOWN(FAKE_ERROR.message)); + expect(FAKE_CONNECTION.release).toHaveBeenCalled(); + done(); + }); + + _queryCallback( + deferred, + FAKE_CONNECTION, + IS_TRANSACTION_FALSE, + SINGLE_RETURN_ITEM_FALSE, + ALLOW_EMPTY_RESPONSE_TRUE + )(FAKE_ERROR); + }); + + + it('invokes a non-transaction, lookup() callback with errors on a multi-item array', done => { + + const deferred = Q.defer(); + + deferred.promise.catch(err => { + expect(err).toEqual(constants.errors.UNKNOWN(FAKE_ERROR.message)); + expect(FAKE_CONNECTION.release).toHaveBeenCalled(); + done(); + }); + + _queryCallback( + deferred, + FAKE_CONNECTION, + IS_TRANSACTION_FALSE, + SINGLE_RETURN_ITEM_TRUE, + ALLOW_EMPTY_RESPONSE_FALSE + )(FAKE_ERROR); + }); + + it('invokes a non-transaction, lookup() callback with errors on a single-item array', done => { + + const deferred = Q.defer(); + + deferred.promise.catch(err => { + expect(err).toEqual(constants.errors.UNKNOWN(FAKE_ERROR.message)); + expect(FAKE_CONNECTION.release).toHaveBeenCalled(); + done(); + }); + + _queryCallback( + deferred, + FAKE_CONNECTION, + IS_TRANSACTION_FALSE, + SINGLE_RETURN_ITEM_TRUE, + ALLOW_EMPTY_RESPONSE_FALSE + )(FAKE_ERROR); + }); + + it('invokes a non-transaction, lookup() callback with errors on a response object', done => { + + const deferred = Q.defer(); + + deferred.promise.catch(err => { + expect(err).toEqual(constants.errors.UNKNOWN(FAKE_ERROR.message)); + expect(FAKE_CONNECTION.release).toHaveBeenCalled(); + done(); + }); + + _queryCallback( + deferred, + FAKE_CONNECTION, + IS_TRANSACTION_FALSE, + SINGLE_RETURN_ITEM_TRUE, + ALLOW_EMPTY_RESPONSE_FALSE + )(FAKE_ERROR); + }); + + it('invokes a non-transaction, lookupSafe() callback with errors on a multi-item array', done => { + + const deferred = Q.defer(); + + deferred.promise.catch(err => { + expect(err).toEqual(constants.errors.UNKNOWN(FAKE_ERROR.message)); + expect(FAKE_CONNECTION.release).toHaveBeenCalled(); + done(); + }); + + _queryCallback( + deferred, + FAKE_CONNECTION, + IS_TRANSACTION_FALSE, + SINGLE_RETURN_ITEM_TRUE, + ALLOW_EMPTY_RESPONSE_TRUE + )(FAKE_ERROR); + }); + + it('invokes a non-transaction, lookupSafe() callback with errors on a single-item array', done => { + + const deferred = Q.defer(); + + deferred.promise.catch(err => { + expect(err).toEqual(constants.errors.UNKNOWN(FAKE_ERROR.message)); + expect(FAKE_CONNECTION.release).toHaveBeenCalled(); + done(); + }); + + _queryCallback( + deferred, + FAKE_CONNECTION, + IS_TRANSACTION_FALSE, + SINGLE_RETURN_ITEM_TRUE, + ALLOW_EMPTY_RESPONSE_TRUE + )(FAKE_ERROR); + }); + + it('invokes a non-transaction, lookupSafe() callback with errors on a response object', done => { + + const deferred = Q.defer(); + + deferred.promise.catch(err => { + expect(err).toEqual(constants.errors.UNKNOWN(FAKE_ERROR.message)); + expect(FAKE_CONNECTION.release).toHaveBeenCalled(); + done(); + }); + + _queryCallback( + deferred, + FAKE_CONNECTION, + IS_TRANSACTION_FALSE, + SINGLE_RETURN_ITEM_TRUE, + ALLOW_EMPTY_RESPONSE_TRUE + )(FAKE_ERROR); + }); + +}); diff --git a/spec/beginTransactionSpec.js b/spec/beginTransactionSpec.js new file mode 100644 index 0000000..fff9252 --- /dev/null +++ b/spec/beginTransactionSpec.js @@ -0,0 +1,77 @@ +'use strict'; + +const beginTransaction = require('../lib/methods/beginTransaction'); + +const constants = require('../lib/constants'); + +const FAKE_ERROR = new Error('fake error'); + +const FAKE_CONNECTION_GOOD = { + beginTransaction : cb => cb(null, { query : 'cool' }) +}; + +const FAKE_CONNECTION_BAD = { + beginTransaction : cb => cb(FAKE_ERROR) +}; + +const FAKE_DB_POOL_GOOD = { + getConnection : cb => cb(null, FAKE_CONNECTION_GOOD) +}; + +const FAKE_DB_POOL_BAD = { + getConnection : cb => cb(FAKE_ERROR) +}; + +const FAKE_DB_POOL_GOOD_WITH_BAD_CONNECTION = { + getConnection : cb => cb(null, FAKE_CONNECTION_BAD) +}; + +const FAKE_DB_POOL_GOOD_WITH_MISSING_CONNECTION = { + getConnection : cb => cb(null, null) +}; + +const FAKE_CORRUPTED_DB_POOL_OBJ = {}; + +describe('beginTransaction', () => { + + it('takes a db pool, gets a connection, invokes the beginTransaction method, and resolves the connection if successful', done => { + beginTransaction(FAKE_DB_POOL_GOOD)() + .then(connection => { + expect(connection).toBe(FAKE_CONNECTION_GOOD); + done(); + }); + }); + + it('takes a db pool, and rejects the promise if there is an error', done => { + beginTransaction(FAKE_DB_POOL_BAD)() + .catch(err => { + expect(err).toBe(FAKE_ERROR); + done(); + }) + }); + + it('takes a db pool, gets a connection, invokes the beginTransaction method, and rejects the promise if there is an error', done => { + beginTransaction(FAKE_DB_POOL_GOOD_WITH_BAD_CONNECTION)() + .catch(err => { + expect(err).toBe(FAKE_ERROR); + done(); + }) + }); + + it('takes a db pool, gets a connection, invokes the beginTransaction method, and rejects the promise if there is an error', done => { + beginTransaction(FAKE_DB_POOL_GOOD_WITH_MISSING_CONNECTION)() + .catch(err => { + expect(err).toBe(constants.errors.MISSING_CONNECTION); + done(); + }) + }); + + it('takes a db pool, and rejects the promise if there are any thrown exceptions', done => { + beginTransaction(FAKE_CORRUPTED_DB_POOL_OBJ)() + .catch(err => { + expect(err.message).toBe('dbPool.getConnection is not a function'); + done(); + }) + }); + +}); diff --git a/spec/commitSpec.js b/spec/commitSpec.js new file mode 100644 index 0000000..7026258 --- /dev/null +++ b/spec/commitSpec.js @@ -0,0 +1,36 @@ +'use strict'; + +const commit = require('../lib/methods/commit'); + +const FAKE_ERROR = 'fake error'; + +const FAKE_CONNECTION_GOOD = { + commit : cb => cb(), + release : () => {} +}; + +const FAKE_CONNECTION_BAD = { + commit : cb => cb(FAKE_ERROR), + rollback : () => {}, + release : () => {} +}; + +describe('commit', () => { + + it('takes a connection, invokes the commit method, and resolves the connection if successful', (done) => { + commit(FAKE_CONNECTION_GOOD) + .then(res => { + expect(res).toBe(true); + done(); + }) + }); + + it('takes a connection, invokes the commit method, and rejects the promise if there is an error', (done) => { + commit(FAKE_CONNECTION_BAD) + .catch(err => { + expect(err).toBe(FAKE_ERROR); + done(); + }) + }); + +}); diff --git a/spec/createNowDateStringSpec.js b/spec/createNowDateStringSpec.js index 386cac8..cd4de03 100644 --- a/spec/createNowDateStringSpec.js +++ b/spec/createNowDateStringSpec.js @@ -1,19 +1,19 @@ 'use strict'; -var R = require('ramda'); +const R = require('ramda'); -var createNowDateString = require('../lib/methods/createNowDateString'); +const createNowDateString = require('../lib/methods/createNowDateString'); -var _getPrefixedMonth = function(date) { +const _getPrefixedMonth = date => { return ('0' + (date.getMonth() + 1)).slice(-2); }; -var _getPrefixedDay = function(date) { +const _getPrefixedDay = date => { return ('0' + date.getDate()).slice(-2); }; -var ymdFormatted = function() { - var date = new Date(); +const ymdFormatted = () => { + const date = new Date(); return R.join('-', [ date.getFullYear(), @@ -22,8 +22,8 @@ var ymdFormatted = function() { ]); }; -describe('createNowDateString', function() { - it('should create a YYYY-MM-DD date string', function() { +describe('createNowDateString', () => { + it('creates a YYYY-MM-DD date string', () => { expect(createNowDateString()).toBe(ymdFormatted()) }); }); diff --git a/spec/createNowUnixTimeSpec.js b/spec/createNowUnixTimeSpec.js index 379e5f0..36c3a37 100644 --- a/spec/createNowUnixTimeSpec.js +++ b/spec/createNowUnixTimeSpec.js @@ -1,9 +1,9 @@ 'use strict'; -var createNowUnixTime = require('../lib/methods/createNowUnixTime'); +const createNowUnixTime = require('../lib/methods/createNowUnixTime'); -describe('createNowUnixTime', function() { - it('should create a Unix time stamp', function() { +describe('createNowUnixTime', () => { + it('creates a Unix time stamp', () => { expect(createNowUnixTime()).toBe(Math.floor(new Date().getTime() / 1000)); }); }); diff --git a/spec/ensureNotSingleItemArraySpec.js b/spec/ensureNotSingleItemArraySpec.js index 5d937e5..096ff15 100644 --- a/spec/ensureNotSingleItemArraySpec.js +++ b/spec/ensureNotSingleItemArraySpec.js @@ -1,17 +1,17 @@ 'use strict'; -var ensureNotSingleItemArray = require('../lib/methods/ensureNotSingleItemArray'); +const ensureNotSingleItemArray = require('../lib/methods/ensureNotSingleItemArray'); -var FAKE_MULTI_ITEM_ARRAY = ['foo', 'bar'], - FAKE_SINGLE_ITEM_ARRAY = ['foo']; +const FAKE_MULTI_ITEM_ARRAY = ['foo', 'bar'], + FAKE_SINGLE_ITEM_ARRAY = ['foo']; -describe('ensureNotSingleItemArray', function() { +describe('ensureNotSingleItemArray', () => { - it('should return the given array if it contains two or more items', function() { + it('returns the given array if it contains two or more items', () => { expect(ensureNotSingleItemArray(FAKE_MULTI_ITEM_ARRAY)).toBe(FAKE_MULTI_ITEM_ARRAY); }); - it('should return the first item in a given array if the array contains only one item', function() { + it('returns the first item in a given array if the array contains only one item', () => { expect(ensureNotSingleItemArray(FAKE_SINGLE_ITEM_ARRAY)).toBe('foo'); }); diff --git a/spec/fuzzifySpec.js b/spec/fuzzifySpec.js index 1ee094e..1701d3e 100644 --- a/spec/fuzzifySpec.js +++ b/spec/fuzzifySpec.js @@ -1,9 +1,9 @@ 'use strict'; -var fuzzify = require('../lib/methods/fuzzify'); +const fuzzify = require('../lib/methods/fuzzify'); -describe('fuzzify', function() { - it('should take a search string and wrap it in MySQL fuzzy delimiters `%`', function() { +describe('fuzzify', () => { + it('takes a search string and wrap it in MySQL fuzzy delimiters `%`', () => { expect(fuzzify('foo bar')).toBe('%foo bar%'); }); }); diff --git a/spec/prepareProvidedFieldsForSetSpec.js b/spec/prepareProvidedFieldsForSetSpec.js index 955be33..c41dfab 100644 --- a/spec/prepareProvidedFieldsForSetSpec.js +++ b/spec/prepareProvidedFieldsForSetSpec.js @@ -1,9 +1,9 @@ 'use strict'; -var prepareProvidedFieldsForSet = require('../lib/methods/prepareProvidedFieldsForSet'); +const prepareProvidedFieldsForSet = require('../lib/methods/prepareProvidedFieldsForSet'); -describe('prepareProvidedFieldsForSet', function() { - it('should make a prepared statement for each field', function() { +describe('prepareProvidedFieldsForSet', () => { + it('makes a prepared statement for each field', () => { expect(prepareProvidedFieldsForSet(['foo', 'bar'])).toBe('`bar` = ?,`foo` = ?'); }); }); diff --git a/spec/prepareValuesSpec.js b/spec/prepareValuesSpec.js index 289588b..1345307 100644 --- a/spec/prepareValuesSpec.js +++ b/spec/prepareValuesSpec.js @@ -1,19 +1,19 @@ 'use strict'; -var prepareValues = require('../lib/methods/prepareValues'); +const prepareValues = require('../lib/methods/prepareValues'); -var FAKE_LOCATION_ID = 1, - FAKE_FIRST_NAME = 'Sean', - FAKE_LAST_NAME = 'Cannon', - FAKE_EMAIL = 'alienwebguy@gmailz.com', - FAKE_PHONE = '763-807-9338', - FAKE_ADDRESS = '2304 DeMartini Lane', - FAKE_CITY = 'Brentwood', - FAKE_STATE = 'CA', - FAKE_ZIP = '94513', - FAKE_COUNTRY = 'US'; +const FAKE_LOCATION_ID = 1, + FAKE_FIRST_NAME = 'Sean', + FAKE_LAST_NAME = 'Cannon', + FAKE_EMAIL = 'alienwebguy@gmailz.com', + FAKE_PHONE = '763-807-9338', + FAKE_ADDRESS = '2304 DeMartini Lane', + FAKE_CITY = 'Brentwood', + FAKE_STATE = 'CA', + FAKE_ZIP = '94513', + FAKE_COUNTRY = 'US'; -var FAKE_UNSORTED_VALUES_OBJECT = { +const FAKE_UNSORTED_VALUES_OBJECT = { locationId : 1, firstName : FAKE_FIRST_NAME, lastName : FAKE_LAST_NAME, @@ -26,7 +26,7 @@ var FAKE_UNSORTED_VALUES_OBJECT = { country : FAKE_COUNTRY }; -var FAKE_SORTED_VALUES_ARRAY = [ +const FAKE_SORTED_VALUES_ARRAY = [ FAKE_ADDRESS, FAKE_CITY, FAKE_COUNTRY, @@ -39,8 +39,8 @@ var FAKE_SORTED_VALUES_ARRAY = [ FAKE_ZIP ]; -describe('prepareValues', function() { - it('should ensure provided data is prepped for DB insertion', function() { +describe('prepareValues', () => { + it('ensures provided data is prepped for DB insertion', () => { expect(prepareValues(FAKE_UNSORTED_VALUES_OBJECT)).toEqual(FAKE_SORTED_VALUES_ARRAY); }); }); diff --git a/spec/resolveOrRejectOnBooleanFieldSpec.js b/spec/resolveOrRejectOnBooleanFieldSpec.js index 527da86..e11a424 100644 --- a/spec/resolveOrRejectOnBooleanFieldSpec.js +++ b/spec/resolveOrRejectOnBooleanFieldSpec.js @@ -1,32 +1,32 @@ 'use strict'; -var Q = require('q'); +const Q = require('q'); -var resolveOrRejectOnBooleanField = require('../lib/methods/resolveOrRejectOnBooleanField'); +const resolveOrRejectOnBooleanField = require('../lib/methods/resolveOrRejectOnBooleanField'); -describe('resolveOrRejectOnBooleanField', function() { - it('should resolve when given a true field', function(done) { - var deferred = Q.defer(); +describe('resolveOrRejectOnBooleanField', () => { + it('resolves when given a true field', (done) => { + const deferred = Q.defer(); - resolveOrRejectOnBooleanField(deferred, 'foo', { foo : true }).then(function(bool) { + resolveOrRejectOnBooleanField(deferred, 'foo', { foo : true }).then((bool) => { expect(bool).toBe(true); done(); }); }); - it('should reject when given a false field', function(done) { - var deferred = Q.defer(); + it('rejects when given a false field', (done) => { + const deferred = Q.defer(); - resolveOrRejectOnBooleanField(deferred, 'foo', { foo : false }).catch(function(bool) { + resolveOrRejectOnBooleanField(deferred, 'foo', { foo : false }).catch((bool) => { expect(bool).toBe(false); done(); }); }); - it('should reject when given an unrecognized field', function() { - var deferred = Q.defer(); + it('rejects when given an unrecognized field', () => { + const deferred = Q.defer(); - expect(function() { + expect(() => { resolveOrRejectOnBooleanField(deferred, 'bar', {foo : true}); }).toThrow(new Error('No field found matching "bar"')); }); diff --git a/spec/transformFromColumnSpec.js b/spec/transformFromColumnSpec.js index 5b6ddbb..200910a 100644 --- a/spec/transformFromColumnSpec.js +++ b/spec/transformFromColumnSpec.js @@ -1,10 +1,10 @@ 'use strict'; -var transformFromColumn = require('../lib/methods/transformFromColumn'); +const transformFromColumn = require('../lib/methods/transformFromColumn'); -describe('transformFromColumn', function() { - it('should transform a string from the case used ' + - 'for MySQL fields to the case used for JS variables', function() { +describe('transformFromColumn', () => { + it('transforms a string from the case used ' + + 'for MySQL fields to the case used for JS variables', () => { expect(transformFromColumn('some_db_field')).toBe('someDbField'); }); }); diff --git a/spec/transformQueryResponseSpec.js b/spec/transformQueryResponseSpec.js index 5f11200..1f5b979 100644 --- a/spec/transformQueryResponseSpec.js +++ b/spec/transformQueryResponseSpec.js @@ -1,38 +1,38 @@ 'use strict'; -var R = require('ramda'); +const R = require('ramda'); -var transformQueryResponse = require('../lib/methods/transformQueryResponse'); +const transformQueryResponse = require('../lib/methods/transformQueryResponse'); -var fakeRecordBefore = { +const fakeRecordBefore = { first_name : 'John', last_name : 'Doe' }; -var fakeRecordAfter = { +const fakeRecordAfter = { firstName : 'John', lastName : 'Doe' }; -var makeFakeRecord = function() { +const makeFakeRecord = () => { return R.merge(fakeRecordBefore, {}); }; -var fakeQueryResponse = [makeFakeRecord(), makeFakeRecord()], - fakeLookupResponse = makeFakeRecord(); +const fakeQueryResponse = [makeFakeRecord(), makeFakeRecord()], + fakeLookupResponse = makeFakeRecord(); -describe('transformQueryResponse', function() { +describe('transformQueryResponse', () => { - it('should transform all strings from the case used ' + + it('transforms all strings from the case used ' + 'for MySQL fields to the case used for JS variables ' + - 'in a given object', function() { + 'in a given object', () => { expect(transformQueryResponse(fakeLookupResponse)) .toEqual(fakeRecordAfter); }); - it('should transform all strings from the case used ' + + it('transforms all strings from the case used ' + 'for MySQL fields to the case used for JS variables ' + - 'in a given array of objects', function() { + 'in a given array of objects', () => { expect(transformQueryResponse(fakeQueryResponse)) .toEqual([fakeRecordAfter, fakeRecordAfter]); }); diff --git a/spec/transformToColumnSpec.js b/spec/transformToColumnSpec.js index 8b7f1f9..09cb11b 100644 --- a/spec/transformToColumnSpec.js +++ b/spec/transformToColumnSpec.js @@ -1,10 +1,10 @@ 'use strict'; -var transformToColumn = require('../lib/methods/transformToColumn'); +const transformToColumn = require('../lib/methods/transformToColumn'); -describe('transformToColumn', function() { - it('should transform a string from the case used ' + - 'for MySQL fields to the case used for JS variables', function() { +describe('transformToColumn', () => { + it('transforms a string from the case used ' + + 'for MySQL fields to the case used for JS variables', () => { expect(transformToColumn('someDbField')).toBe('`some_db_field`'); }); });