Skip to content

Commit

Permalink
feat(server): verbose MySQL errors (#1521)
Browse files Browse the repository at this point in the history
Previous versions of BHIMA avoided showing errors to the client,
resulting in a large red bar with no context.  This commit makes sure
that we can at least echo the errors, as well as making sure the client
sees something .. the raw MySQL error.  This will help super users
inform us what kind of errors they are seeing in the meantime.

Additionally, it groups all tests under a new `test.sh` script so that
the "Ctrl-C" interrupt works on unix-based systems to halt the tests.
  • Loading branch information
jniles authored Apr 19, 2017
1 parent d667b73 commit d27d615
Show file tree
Hide file tree
Showing 4 changed files with 40 additions and 14 deletions.
3 changes: 3 additions & 0 deletions .env.development
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,8 @@ LOG_LEVEL='warn'
# LOG_LEVEL='debug'
UPLOAD_DIR='client/upload'

# this will toggle all MYSQL errors to be reflected to the client.
LOG_ALL_MYSQL_ERRORS=false

# control Redis Pub/Sub
ENABLE_EVENTS=false
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"scripts": {
"app": "NODE_ENV=production ./node_modules/.bin/gulp build && cd bin && NODE_ENV=production node server/app.js",
"dev": "NODE_ENV=development ./node_modules/.bin/gulp build && cd bin && NODE_ENV=development node server/app.js",
"test": "npm run test:lint && npm run test:integration && npm run test:client-unit && npm run test:server-unit && npm run test:ends",
"test": "./sh/test.sh",
"test:integration": "./sh/integration-tests.sh",
"test:ends": "./sh/test-ends.sh",
"test:lint": "./sh/lint.sh",
Expand Down
31 changes: 18 additions & 13 deletions server/config/interceptors.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,35 +15,37 @@
const winston = require('winston');
const BadRequest = require('../lib/errors/BadRequest');

const LOG_ALL_MYSQL_ERRORS = process.env.LOG_ALL_MYSQL_ERRORS;

// map MySQL error codes to HTTP status codes
const map = {
ER_DUP_KEY :
`A key collided in a unique database field. Please retry your action. If
the problem persists, contact the developers.`,
ER_BAD_FIELD_ERROR : 'Column does not exist in database.',
ER_BAD_FIELD_ERROR : 'Column does not exist in database.',
ER_ROW_IS_REFERENCED :
'Cannot delete entity because entity is used in another table.',
ER_ROW_IS_REFERENCED_2 :
'Cannot delete entity because entity is used in another table.',
ER_BAD_NULL_ERROR : 'A column was left NULL that cannot be NULL.',
ER_PARSE_ERROR :
ER_PARSE_ERROR :
`Your request could not be translated into valid SQL. Please modify your
request and try again.`,
ER_EMPTY_QUERY : 'Your request had an empty body.',
ER_EMPTY_QUERY : 'Your request had an empty body.',
ER_NO_DEFAULT_FOR_FIELD :
'You did not include enough information in your query.',
ER_DATA_TOO_LONG :
'The value provided is longer than the database record limit.'
'The value provided is longer than the database record limit.',
};

// these are custom errors defined by
const SQL_STATES = {
'45001' : 'ERRORS.NO_ENTERPRISE',
'45002' : 'ERRORS.NO_PROJECT',
'45003' : 'ERRORS.NO_FISCAL_YEAR',
'45004' : 'ERRORS.NO_PERIOD',
'45005' : 'ERRORS.NO_EXCHANGE_RATE',
'45501' : 'ERRORS.OVERPAID_INVOICE'
45001 : 'ERRORS.NO_ENTERPRISE',
45002 : 'ERRORS.NO_PROJECT',
45003 : 'ERRORS.NO_FISCAL_YEAR',
45004 : 'ERRORS.NO_PERIOD',
45005 : 'ERRORS.NO_EXCHANGE_RATE',
45501 : 'ERRORS.OVERPAID_INVOICE',
};

/**
Expand All @@ -52,7 +54,6 @@ const SQL_STATES = {
* This error handler interprets all errors and sends them to the client.
*/
exports.handler = function handler(error, req, res, next) {

// log the error to the error log (NOTE: in production, this should be 'error')
winston.log('debug', error);

Expand All @@ -63,13 +64,17 @@ exports.handler = function handler(error, req, res, next) {

// todo(jniles) - unify this error handing
if (error.code === 'ER_SIGNAL_EXCEPTION') {
key = SQL_STATES[error.sqlState];
key = SQL_STATES[error.sqlState] || error.sqlState;
description = error.toString();
} else {
key = `ERRORS.${error.code}`;
key = `ERRORS.${error.code || error.sqlState}`;
description = map[error.code];
}

if (LOG_ALL_MYSQL_ERRORS) {
winston.log('error', error);
}

error = new BadRequest(description, key);
}

Expand Down
18 changes: 18 additions & 0 deletions sh/test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#!/bin/bash

# bash script mode
set -euo pipefail

# run lint tests
./sh/lint.sh

# run integration tests
./sh/integration-tests.sh

# run karma (client unit) tests
./node_modules/.bin/karma start --single-run --no-auto-watch --concurrency 1 karma.conf.js

# run end to end tests
./sh/test-ends.sh

exit 0;

0 comments on commit d27d615

Please sign in to comment.