diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000000000000000000000000000000000000..834a48a40945a182db82602de135718f81d471b6 --- /dev/null +++ b/.babelrc @@ -0,0 +1 @@ +{ 'presets': ['es2015'] } diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000000000000000000000000000000000000..ca947a13e5b915ed936efcd6a255b28d40501862 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,3 @@ +[ + "**/test/*.js" +] diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000000000000000000000000000000000000..14152dfcb9ae24e77005322efbbfbe811504ae58 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,14 @@ +{ + "extends": "airbnb", + "root": true, + "plugins": [ + "react", + "jsx-a11y", + "import" + ], + "rules": { + "indent": [ "error", 4 ], + "no-unused-vars": [ "error", { "args": "none" }], + "no-param-reassign": [ "off" ] + } +} diff --git a/.gitignore b/.gitignore index 1be0326ac9a0235b55fa03f244f261b16eed1832..aba9677483cfad1c166ca57ec84c8fd263bbb56a 100644 --- a/.gitignore +++ b/.gitignore @@ -10,8 +10,10 @@ lib-cov *.gz pids -logs results npm-debug.log node_modules/ +build/* + +.gulp-cache diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 3171c9f7e5653d1e4edbfd1064779609693773c3..b3949c4f704e19351c9126df4c326bdfaa229998 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -2,11 +2,12 @@ stages: - test before_script: + - npm install --global gulp gulp-cli babel babel-cli babel-core babel-register mocha - npm install run_tests: stage: test script: - - npm test + - gulp test tags: - node diff --git a/README.md b/README.md index 0c36372077dc52f20e792ebe2e323236d109dbc5..41202e148f16e12cd9c3e6996b158f3bae87e0b7 100644 --- a/README.md +++ b/README.md @@ -1 +1,25 @@ # SIMCAQ + +# Dependencies + +Previous versions of Node.js do not support ECMAScript6, it is recommended to use at least version 4.5.0LTS. + +1) Install [NVM (Node Version Manager)](https://github.com/creationix/nvm) + +2) Install Node.js via NVM + +> source ~/.bashrc + +> nvm install v4.5.0 + +3) Enable Node.js + +> nvm use v4.5.0 + +4) Install babel and gulp globally + +> npm install -g gulp gulp-cli babel babel-cli babel-core babel-register mocha + +5) Install project dependencies + +> npm install diff --git a/config.json b/config.json index 8f60036adf20a0512e709e683f876de75124d714..0574d6e9cc2246ce45b4af102c92b4a082f42bcd 100644 --- a/config.json +++ b/config.json @@ -1,11 +1,13 @@ { "port": 3000, + "debug" : false, "monetdb": { "host": "simcaqdb1", "port": 50000, "dbname": "simcaq_dev", "user": "monetdb", - "password":"monetdb" + "password":"monetdb", + "nrConnections": "16" }, "default": { "api": { diff --git a/gulpfile.babel.js b/gulpfile.babel.js new file mode 100644 index 0000000000000000000000000000000000000000..cecae587a2e22e11052da3dc01371310086a74bd --- /dev/null +++ b/gulpfile.babel.js @@ -0,0 +1,72 @@ +const gulp = require('gulp'); + +const babel = require('gulp-babel'); + +const eslint = require('gulp-eslint'); + +const mocha = require('gulp-mocha'); + +const nodemon = require('gulp-nodemon'); + +const Cache = require('gulp-file-cache'); + +const cache = new Cache(); + +/** + * Compile source files + */ +function compile() { + // run ESLint + gulp.src('src/**/*.js') + .pipe(eslint()) + .pipe(eslint.format()); + + // compile source to ES5 + gulp.src('src/**/*.js') + .pipe(cache.filter()) // cache source files + .pipe(babel()) // compile only modified files + .pipe(cache.cache()) // cache compiled files + .pipe(gulp.dest('build')); // move compiled files to build directory + + // copy configuration file to build directory + gulp.src('config.json') + .pipe(gulp.dest('build')); + +} + +gulp.task('build', compile); + +gulp.task('test', () => { + gulp.src('test/test.js', {read: false}) + .pipe(mocha()) + .once('error', () => { + process.exit(1); + }) + .once('end', () => { + process.exit(); + }); +}); + +gulp.task('watch', [], () => { + console.log('Watching source directory for changes'); + compile(); + gulp.watch('src/**/*.js').on('change', () => { + console.log('Recompiling source'); + compile(); + console.log('Source recompilation done') + }); +}); + +gulp.task('run', () => { + process.chdir('build'); + nodemon({ + script: 'server.js', + tasks: ['watch'], + ignore: ["test/test.js", "gulpfile.babel.js"], + ext: 'js html json', + env: { 'NODE_ENV': 'development' } + }); +}); + +gulp.task('default', ['run']); + diff --git a/libs/app.js b/libs/app.js deleted file mode 100644 index 3fce3744a734395850a3fb02304c1c8a8881f5bc..0000000000000000000000000000000000000000 --- a/libs/app.js +++ /dev/null @@ -1,55 +0,0 @@ -var express = require('express') -var path = require('path') -var cookieParser = require('cookie-parser') -var bodyParser = require('body-parser') -var csv = require('csv-express') -var xml = require('js2xmlparser') -var methodOverride = require('method-override') -var cors = require('cors') -var apicache = require('apicache').options({ debug: true }).middleware - -var libs = process.cwd() + '/libs/' - -var config = require('./config') -var log = require('./log')(module) - -var api = require('./routes/api') -var states = require('./routes/states') -var regions = require('./routes/regions') -var cities = require('./routes/cities') - -var app = express() - -app.use(bodyParser.json()) -app.use(bodyParser.urlencoded({ extended: false })) -app.use(cookieParser()) -app.use(cors()) -app.use(methodOverride()) -app.use(apicache('1 hour')) - -app.use('/api/v1/', api) -app.use('/api/v1/states', states) -app.use('/api/v1/regions', regions) -app.use('/api/v1/cities', cities) - -// catch 404 and forward to error handler -app.use(function(req, res, next){ - res.status(404) - log.debug('%s %d %s', req.method, res.statusCode, req.url) - res.json({ - error: 'Not found' - }) - return -}) - -// error handlers -app.use(function(err, req, res, next){ - res.status(err.status || 500) - log.error('%s %d %s', req.method, res.statusCode, err.message) - res.json({ - error: err.message - }) - return -}) - -module.exports = app diff --git a/libs/config.js b/libs/config.js deleted file mode 100644 index 78f7831a7b1bb614fcebd8de04be18ff447d84a1..0000000000000000000000000000000000000000 --- a/libs/config.js +++ /dev/null @@ -1,9 +0,0 @@ -var nconf = require('nconf') - -nconf.argv() - .env() - .file({ - file: process.cwd() + '/config.json' - }) - -module.exports = nconf diff --git a/libs/db/monet.js b/libs/db/monet.js deleted file mode 100644 index 0b554285d1b9d889ca1f40f7607672490eda73b3..0000000000000000000000000000000000000000 --- a/libs/db/monet.js +++ /dev/null @@ -1,19 +0,0 @@ -var mdb = require('monetdb')() - -var libs = process.cwd() + '/libs/' - -var log = require(libs + 'log')(module) -var config = require(libs + 'config') - -var options = { - host: config.get('monetdb:host'), - port: config.get('monetdb:port'), - dbname: config.get('monetdb:dbname'), - user: config.get('monetdb:user'), - password: config.get('monetdb:password') -} - -var conn = new mdb(options) -conn.connect() - -module.exports = conn diff --git a/libs/log.js b/libs/log.js deleted file mode 100644 index 419b3e4b86d5d1e5a8e17858882c905204bab079..0000000000000000000000000000000000000000 --- a/libs/log.js +++ /dev/null @@ -1,35 +0,0 @@ -var winston = require('winston') - -winston.emitErrs = true - -function logger(module) { - - return new winston.Logger({ - transports : [ - new winston.transports.File({ - level: 'info', - filename: process.cwd() + '/logs/all.log', - handleException: true, - json: false, - maxSize: 5242880, //5mb - maxFiles: 2, - colorize: false - }), - new winston.transports.Console({ - level: 'debug', - label: getFilePath(module), - handleException: true, - json: true, - colorize: true - }) - ], - exitOnError: false - }) -} - -function getFilePath (module ) { - //using filename in log statements - return module.filename.split('/').slice(-2).join('/') -} - -module.exports = logger diff --git a/libs/middlewares/dimensions.js b/libs/middlewares/dimensions.js deleted file mode 100644 index c17e4a6d2e00c1e5dc47455f4959cf9f33a0ad09..0000000000000000000000000000000000000000 --- a/libs/middlewares/dimensions.js +++ /dev/null @@ -1,48 +0,0 @@ -/** -* Dimensions middleware -* -* EXAMPLE: -* Use it with no parameters to get all the dimensions specified -* app.get('/', dimensions(), function(req, res, next){}) -* -* Use it with an array of accepted values -* app.get('/', dimensions(['year', 'location']), function(req, res, next){}) -* -* Use it globally -* app.use(dimensions()) -*/ - -function intersect(a, b) { - var t - if (b.length > a.length) t = b, b = a, a = t - return a.filter(function (e) { - if (b.indexOf(e) !== -1) return true - }) -} - -function dimensions(dims) { - return function(req, res, next) { - req.dims = {} - if(req.query.dims) { - var params = req.query.dims.split(",") - var dimObj = {} - for(var i=0; i<params.length; ++i) { - var kv = params[i].split(":") - dimObj[kv[0]] = (typeof kv[1] === 'undefined') ? null : kv[1] - } - // If the dims array exists and is not empty - if(typeof dims !== 'undefined' && dims.length > 0) { - var intersection = intersect(dims, Object.keys(dimObj)) - for(var i=0; i<intersection.length; ++i) { - req.dims[intersection[i]] = dimObj[intersection[i]] - } - } else { - req.dims = dimObj - } - } - console.log(req.dims) - next() - } -} - -module.exports = dimensions diff --git a/libs/middlewares/query.js b/libs/middlewares/query.js deleted file mode 100644 index d14958dc43f6610cb3aa502ec6491db6a8095899..0000000000000000000000000000000000000000 --- a/libs/middlewares/query.js +++ /dev/null @@ -1,16 +0,0 @@ -var libs = process.cwd() + '/libs/' -var conn = require(libs + 'db/monet') -var log = require(libs + 'log')(module) - -function query(req, res, next) { - log.debug(req.query) - conn.query( - req.query.text, req.query.values, true - ).then(function(result) { - // log.debug(result) - req.result = result - next() - }) -} - -module.exports = query diff --git a/libs/routes/api.js b/libs/routes/api.js deleted file mode 100644 index aa13166f957aac40cdcbed5e66419e991771b7ce..0000000000000000000000000000000000000000 --- a/libs/routes/api.js +++ /dev/null @@ -1,138 +0,0 @@ -var express = require('express') -var xml = require('js2xmlparser') -var router = express.Router() - -var libs = process.cwd() + '/libs/' - -var log = require(libs + 'log')(module) -var config = require(libs + 'config') - -var conn = require(libs + 'db/monet') - -router.get('/', function (req, res) { - res.json({ - msg: 'SimCAQ API is running' - }) -}) - -/** - * Complete range of the enrollments dataset - * - * Returns a tuple of start and ending years of the complete enrollments dataset. - */ -router.get('/year_range', function(req, res) { - var yearSql = "SELECT MIN(t.ano_censo) AS start_year, MAX(t.ano_censo) AS end_year FROM turmas AS t"; - conn.query(yearSql, true).then(function(result) { - if (req.query.format === 'csv') { - res.csv(result.data); - } else if (req.query.format === 'xml') { - res.send(xml("result", JSON.stringify({year_range: result.data}))) - } - else { - res.json({ - result: result.data - }); - } - }); -}) - -router.get('/data', function(req, res) { - log.debug(req.query) - log.debug(req.query.met) - log.debug(req.query.dim) - conn.query( - 'SELECT * FROM turmas' - ).then(function(result) { - if (req.query.format === 'csv') { - res.csv(result.data); - } else if (req.query.format === 'xml') { - res.send(xml("result", JSON.stringify({data: result.data}))) - } - else { - res.json({ - result: result.data - }); - } - }) -}) - -router.get('/enrollments', function(req, res) { - var params = req.query; - var id = 0; - var location_id = 0; - var adm_dependency_id = 0; - var census_year = 0; - var enrollmentSql = ""; - - if (params.id) - { - id = parseInt(params.id, 10); - } - - if (params.location_id) - { - location_id = parseInt(params.location_id, 10); - } - - if (params.adm_dependency_id) - { - adm_dependency_id = parseInt(params.adm_dependency_id, 10); - } - - if (params.census_year) - { - census_year = parseInt(params.census_year, 10); - } - - /** - * FIXME: parameter substitution in the queries is not safe (vulnerable to - * SQL injection). Substitution from MonetDB module is not working for some - * reason. - */ - switch(params.aggregate) - { - case "city": - if (id) { - enrollmentSql = "SELECT nome AS name, total FROM mat_municipio(" + id + "," + census_year + "," + adm_dependency_id + "," + location_id + ")"; - } else { - enrollmentSql = "SELECT nome AS name, total FROM mat_municipios(" + census_year + "," + adm_dependency_id + "," + location_id + ")"; - } - break; - case "state": - if (id) { - enrollmentSql = "SELECT nome AS name, total FROM mat_estado(" + id + "," + census_year + "," + adm_dependency_id + "," + location_id + ")"; - } else { - enrollmentSql = "SELECT nome AS name, total FROM mat_estados(" + census_year + "," + adm_dependency_id + "," + location_id + ")"; - } - break; - case "region": - if (id) { - enrollmentSql = "SELECT nome AS name, total FROM mat_regiao(" + id + "," + census_year + "," + adm_dependency_id + "," + location_id + ")"; - } else { - enrollmentSql = "SELECT nome AS name, total FROM mat_regioes(" + census_year + "," + adm_dependency_id + "," + location_id + ")"; - } - break; - default: - enrollmentSql = "SELECT nome AS name, total FROM mat_brasil(" + census_year + "," + adm_dependency_id + "," + location_id + ")"; - } - - log.debug(params); - log.debug("Executing query: " + enrollmentSql); - - conn.query(enrollmentSql, true).then(function(result) { - log.debug(result); - if (req.query.format === 'csv') { - res.csv(result.data); - } else if (req.query.format === 'xml') { - res.send(xml("result", JSON.stringify({enrollments: result.data}))) - } - else { - res.json({ - result: result.data - }); - } - log.debug("All resources were released"); - }); -}) - -module.exports = router diff --git a/libs/routes/cities.js b/libs/routes/cities.js deleted file mode 100644 index 10d7ceef4cc7ceebad2577ae12fcea5b8d91019a..0000000000000000000000000000000000000000 --- a/libs/routes/cities.js +++ /dev/null @@ -1,42 +0,0 @@ -var express = require('express') -var xml = require('js2xmlparser') -var router = express.Router() -var squel = require('squel') - -var libs = process.cwd() + '/libs/' - -var log = require(libs + 'log')(module) -var config = require(libs + 'config') - -var conn = require(libs + 'db/monet') -var query = require(libs + 'middlewares/query') - -function response(req, res) { - if (req.query.format === 'csv') { - res.csv(req.result.data) - } else if (req.query.format === 'xml') { - res.send(xml("result", JSON.stringify({city: req.result.data}))) - } - else { - res.json({ - result: req.result.data - }) - } -} - -router.get('/', function(req, res, next) { - req.query = squel.select().from('municipios').toParam() - next() -}, query, response) - -router.get('/:id', function(req, res, next) { - req.query = squel.select().from('municipios').where('pk_municipio_id=?', parseInt(req.params.id, 10)).toParam() - next() -}, query, response) - -router.get('/state/:id', function(req, res, next) { - req.query = squel.select().from('municipios').where('fk_estado_id=?', parseInt(req.params.id, 10)).toParam() - next() -}, query, response) - -module.exports = router diff --git a/libs/routes/regions.js b/libs/routes/regions.js deleted file mode 100644 index cfbbcd946572a8a30ebf00536ce9cbf544226f7f..0000000000000000000000000000000000000000 --- a/libs/routes/regions.js +++ /dev/null @@ -1,38 +0,0 @@ -var express = require('express') -var xml = require('js2xmlparser') -var router = express.Router() -var squel = require('squel') - -var libs = process.cwd() + '/libs/' - -var log = require(libs + 'log')(module) -var config = require(libs + 'config') - -var conn = require(libs + 'db/monet') - -var query = require(libs + 'middlewares/query') - -function response(req, res) { - if (req.query.format === 'csv') { - res.csv(req.result.data) - } else if (req.query.format === 'xml') { - res.send(xml("result", JSON.stringify({state: req.result.data}))) - } - else { - res.json({ - result: req.result.data - }) - } -} - -router.get('/', function(req, res, next) { - req.query = squel.select().from('regioes').toParam() - next() -}, query, response) - -router.get('/:id', function(req, res, next) { - req.query = squel.select().from('regioes').where('pk_regiao_id=?', parseInt(req.params.id, 10)).toParam() - next() -}, query, response) - -module.exports = router diff --git a/libs/routes/states.js b/libs/routes/states.js deleted file mode 100644 index 79959ef8fd4a19c3afc3f51328ec737396cb0bbe..0000000000000000000000000000000000000000 --- a/libs/routes/states.js +++ /dev/null @@ -1,44 +0,0 @@ -var express = require('express') -var xml = require('js2xmlparser') -var router = express.Router() -var squel = require('squel') - -var libs = process.cwd() + '/libs/' - -var log = require(libs + 'log')(module) -var config = require(libs + 'config') - -var conn = require(libs + 'db/monet') - -var query = require(libs + 'middlewares/query') - -function response(req, res) { - console.log('respostas :)') - if (req.query.format === 'csv') { - res.csv(req.result.data) - } else if (req.query.format === 'xml') { - res.send(xml("result", JSON.stringify({state: req.result.data}))) - } - else { - res.json({ - result: req.result.data - }) - } -} - -router.get('/', function(req, res, next) { - req.query = squel.select().from('estados').toParam() - next() -}, query, response) - -router.get('/:id', function(req, res, next) { - req.query = squel.select().from('estados').where('pk_estado_id = ?', parseInt(req.params.id, 10)).toParam() - next() -}, query, response) - -router.get('/region/:id', function(req, res, next) { - req.query = squel.select().from('estados').where('fk_regiao_id = ?', parseInt(req.params.id, 10)).toParam() - next() -}, query, response) - -module.exports = router diff --git a/package.json b/package.json index c8c186c70762c34e88b90578f4f20a76b7317209..06ea4fce17d05c284ee4b464d84c5f35352ca443 100644 --- a/package.json +++ b/package.json @@ -5,30 +5,52 @@ "description": "Simulador custo aluno-qualidade", "private": true, "scripts": { - "start": "forever start server.js || node server.js", - "test": "mocha" + "start": "cd build && forever start server.js || node server.js", + "test": "cd build && mocha" }, "dependencies": { "apicache": "0.0.14", "body-parser": "^1.13.1", + "chai": "^3.5.0", + "chai-http": "^3.0.0", "cookie-parser": "^1.3.5", "cors": "^2.7.1", "csv-express": "^1.1.0", "debug": "~2.0.x", + "dirty-chai": "^1.2.2", "express": "^4.13.0", "faker": "^2.1.5", "forever": "^0.15.2", "js2xmlparser": "^1.0.0", "method-override": "^2.3.3", - "monetdb": "^1.1.2", + "mocha": "^2.5.3", + "monetdb-pool": "0.0.8", "nconf": "^0.6.x", "squel": "^5.4.2", "winston": "^2.2.0" }, "license": "MIT", "devDependencies": { - "chai": "^3.5.0", - "chai-http": "^3.0.0", - "mocha": "^2.5.3" + "babel-cli": "^6.11.4", + "babel-core": "^6.14.0", + "babel-preset-es2015": "^6.13.2", + "babel-register": "^6.14.0", + "babelify": "^7.3.0", + "browserify": "^13.1.0", + "eslint": "^3.3.1", + "eslint-config-airbnb": "^10.0.1", + "eslint-plugin-import": "^1.13.0", + "eslint-plugin-jsx-a11y": "^2.1.0", + "eslint-plugin-react": "^6.1.1", + "gulp": "^3.9.1", + "gulp-babel": "^6.1.2", + "gulp-cli": "^1.2.2", + "gulp-eslint": "^3.0.1", + "gulp-file-cache": "0.0.1", + "gulp-mocha": "^3.0.1", + "gulp-nodemon": "^2.1.0", + "gulp-plumber": "^1.1.0", + "gulp-rename": "^1.2.2", + "gulp-uglify": "^2.0.0" } } diff --git a/server.js b/server.js deleted file mode 100644 index f27cd5c80693c1b31bbd500a5fcbae00a86b957d..0000000000000000000000000000000000000000 --- a/server.js +++ /dev/null @@ -1,13 +0,0 @@ -var debug = require('debug')('node-express-base') - -var libs = process.cwd() + '/libs/' -var config = require(libs + 'config') -var log = require(libs + 'log')(module) -var app = require(libs + 'app') - -app.set('port', process.env.PORT || config.get('port') || 3000) - -var server = app.listen(app.get('port'), function() { - debug('Express server listening on port ' + server.address().port) - log.info('Express server listening on port ' + server.address().port) -}) diff --git a/src/libs/app.js b/src/libs/app.js new file mode 100644 index 0000000000000000000000000000000000000000..07d7d28fcebce25e8aadc5b9b8e47e09ffd0da96 --- /dev/null +++ b/src/libs/app.js @@ -0,0 +1,34 @@ +const express = require('express'); +const cookieParser = require('cookie-parser'); +const bodyParser = require('body-parser'); +const methodOverride = require('method-override'); +const cors = require('cors'); + +const log = require('./log')(module); + +const app = express(); + +const api = require('./routes/api'); + +app.use(bodyParser.json()); +app.use(bodyParser.urlencoded({ extended: false })); +app.use(cookieParser()); +app.use(cors()); +app.use(methodOverride()); +app.use(api); + +// catch 404 and forward to error handler +app.use((req, res, next) => { + res.status(404); + log.debug('%s %d %s', req.method, res.statusCode, req.url); + res.json({ error: 'Not found' }).end(); +}); + +// error handlers +app.use((err, req, res, next) => { + res.status(err.status || 500); + log.error('%s %d %s', req.method, res.statusCode, err.message); + res.json({ error: err.message }).end(); +}); + +module.exports = app; diff --git a/src/libs/config.js b/src/libs/config.js new file mode 100644 index 0000000000000000000000000000000000000000..ef5a26cddbb3946b89ac690e8cf52d28884463aa --- /dev/null +++ b/src/libs/config.js @@ -0,0 +1,7 @@ +const nconf = require('nconf'); + +nconf.argv() + .env() + .file({ file: `${process.cwd()}/config.json` }); + +module.exports = nconf; diff --git a/src/libs/db/monet.js b/src/libs/db/monet.js new file mode 100644 index 0000000000000000000000000000000000000000..1cf874cc9ca5a2ada3e20bdd077c34fbcfbe8226 --- /dev/null +++ b/src/libs/db/monet.js @@ -0,0 +1,22 @@ +const MonetDBPool = require('monetdb-pool'); + +const libs = `${process.cwd()}/libs`; + +const config = require(`${libs}/config`); + +const poolOptions = { + nrConnections: config.get('monetdb:nrConnections'), +}; + +const options = { + host: config.get('monetdb:host'), + port: config.get('monetdb:port'), + dbname: config.get('monetdb:dbname'), + user: config.get('monetdb:user'), + password: config.get('monetdb:password'), +}; + +const conn = new MonetDBPool(poolOptions, options); +conn.connect(); + +module.exports = conn; diff --git a/src/libs/db/query_exec.js b/src/libs/db/query_exec.js new file mode 100644 index 0000000000000000000000000000000000000000..bd921d1d02deec90101260046707b7929434a10a --- /dev/null +++ b/src/libs/db/query_exec.js @@ -0,0 +1,44 @@ +const libs = `${process.cwd()}/libs`; + +const log = require(`${libs}/log`)(module); + +const conn = require(`${libs}/db/monet`); + +/** + * Promise that executes an SQL query with optional parameters + * + * Examples: + * Query with no parameters: + * execSqlQuery('SELECT * FROM people'); + * Query with one parameter: + * execSqlQuery('SELECT name, age FROM people WHERE id = ?', [1]); + * Query with more than one parameter: + * execSqlQuery('SELECT name, age FROM people WHERE city = ? AND age > ?', ['São Paulo', 35]); + * + * @param {string} sqlQuery - SQL query to be executed by the Promise + * @param {array} sqlQueryParams - SQL query parameters + */ +function execSqlQuery(sqlQuery, sqlQueryParams = []) { + log.debug(`Executing SQL query '${sqlQuery}' with params '${sqlQueryParams}'`); + return new Promise((resolve, reject) => { + conn.prepare(sqlQuery, true).then( + (dbQuery) => { + dbQuery.exec(sqlQueryParams).then( + (dbResult) => { + log.debug(`Query result: ${dbResult.data}`); + log.debug(dbResult.data); + resolve(dbResult.data); + }, + (dbError) => { + log.error(`SQL query execution error: ${dbError.message}`); + reject(new Error(dbError.message)); + } + ); + // release resources allocated for prepared statement + conn.release(); + } + ); + }); +} + +module.exports = execSqlQuery; diff --git a/src/libs/log.js b/src/libs/log.js new file mode 100644 index 0000000000000000000000000000000000000000..ecf6df026314105b02628ef0ac2736a23950374a --- /dev/null +++ b/src/libs/log.js @@ -0,0 +1,42 @@ +const config = require('./config'); + +const winston = require('winston'); + +winston.emitErrs = true; + +function getFilePath(module) { + // using filename in log statements + return module.filename.split('/').slice(-2).join('/'); +} + +function logger(module) { + const log = new winston.Logger({ + transports: [ + new winston.transports.File({ + name: 'info-log', + level: 'info', + filename: `${process.cwd()}/logs/all.log`, + handleException: true, + json: false, + maxSize: 5242880, // 5MB + maxFiles: 2, + colorize: false, + }), + new winston.transports.Console({ + name: 'debug-log', + level: 'debug', + label: getFilePath(module), + handleException: true, + json: false, + colorize: true, + }), + ], + exitOnError: false, + }); + if (!config.get('debug')) { + log.remove('debug-log'); + } + return log; +} + +module.exports = logger; diff --git a/src/libs/middlewares/dimensions.js b/src/libs/middlewares/dimensions.js new file mode 100644 index 0000000000000000000000000000000000000000..8532425cf107ee9f19b13009cdbbdc365e3ebbc3 --- /dev/null +++ b/src/libs/middlewares/dimensions.js @@ -0,0 +1,52 @@ +/** +* Dimensions middleware +* +* EXAMPLE: +* Use it with no parameters to get all the dimensions specified +* app.get('/', dimensions(), function(req, res, next){}) +* +* Use it with an array of accepted values +* app.get('/', dimensions(['year', 'location']), function(req, res, next){}) +* +* Use it globally +* app.use(dimensions()) +*/ + +function intersect(a, b) { + let t; + if (b.length > a.length) { + t = b; b = a; a = t; + } + return a.filter((e) => b.indexOf(e) !== -1); +} + +function dimensions(dims) { + return (req, res, next) => { + req.dims = {}; + if (req.query.dims) { + const params = req.query.dims.split(','); + const dimObj = {}; + for (const param of params) { + const kv = param.split(':'); + dimObj[kv[0]] = (typeof kv[1] === 'undefined') ? null : kv[1]; + } + // for(let i=0; i<params.length; ++i) { + // let kv = params[i].split(':'); + // dimObj[kv[0]] = (typeof kv[1] === 'undefined') ? null : kv[1]; + // } + + // If the dims array exists and is not empty + if (typeof dims !== 'undefined' && dims.length > 0) { + const intersection = intersect(dims, Object.keys(dimObj)); + for (let i = 0; i < intersection.length; ++i) { + req.dims[intersection[i]] = dimObj[intersection[i]]; + } + } else { + req.dims = dimObj; + } + } + next(); + }; +} + +module.exports = dimensions; diff --git a/src/libs/middlewares/query.js b/src/libs/middlewares/query.js new file mode 100644 index 0000000000000000000000000000000000000000..6b91fc8f2e86be81f212d4148023f6673c1b6323 --- /dev/null +++ b/src/libs/middlewares/query.js @@ -0,0 +1,16 @@ +const libs = `${process.cwd()}/libs`; +const log = require(`${libs}/log`)(module); +const execQuery = require(`${libs}/db/query_exec`); + +function query(req, res, next) { + log.debug(req.query); + execQuery(req.query.text, req.query.values).then((result) => { + log.debug(result); + req.result = result; + next(); + }, (error) => { + next(error); + }); +} + +module.exports = query; diff --git a/src/libs/middlewares/response.js b/src/libs/middlewares/response.js new file mode 100644 index 0000000000000000000000000000000000000000..8328b4ffb7947d85280f14ee71be7b3afd8b4a13 --- /dev/null +++ b/src/libs/middlewares/response.js @@ -0,0 +1,13 @@ +const xml = require('js2xmlparser'); + +function response(req, res) { + if (req.query.format === 'csv') { + res.csv(req.result); + } else if (req.query.format === 'xml') { + res.send(xml('result', JSON.stringify({ city: req.result }))); + } else { + res.json({ result: req.result }); + } +} + +module.exports = response; diff --git a/src/libs/routes/api.js b/src/libs/routes/api.js new file mode 100644 index 0000000000000000000000000000000000000000..9a23be8c5b12994d12ce5014c9fee82bbb8a966c --- /dev/null +++ b/src/libs/routes/api.js @@ -0,0 +1,23 @@ +const express = require('express'); + +const api = express(); + +const enrollment = require('./enrollment'); + +const state = require('./state'); + +const region = require('./region'); + +const city = require('./city'); + +api.get('/', (req, res) => { + res.json({ msg: 'SimCAQ API is running' }); +}); + +// mount API routes +api.use('/v1/enrollment', enrollment); +api.use('/v1/state', state); +api.use('/v1/region', region); +api.use('/v1/city', city); + +module.exports = api; diff --git a/src/libs/routes/city.js b/src/libs/routes/city.js new file mode 100644 index 0000000000000000000000000000000000000000..aa28900b9f29972aa0d5d8c01c85aba8fd54bd17 --- /dev/null +++ b/src/libs/routes/city.js @@ -0,0 +1,36 @@ +const express = require('express'); + +const cityApp = express(); + +const libs = `${process.cwd()}/libs`; + +const squel = require('squel'); + +const query = require(`${libs}/middlewares/query`); + +const response = require(`${libs}/middlewares/response`); + +cityApp.get('/', (req, res, next) => { + req.query = squel.select().from('municipios').toParam(); + next(); +}, query, response); + +cityApp.get('/:id', (req, res, next) => { + req.query = squel.select().from('municipios').where('pk_municipio_id = ?', + parseInt(req.params.id, 10)).toParam(); + next(); +}, query, response); + +cityApp.get('/ibge/:id', (req, res, next) => { + req.query = squel.select().from('municipios').where('codigo_ibge = ?', + req.params.id).toParam(); + next(); +}, query, response); + +cityApp.get('/state/:id', (req, res, next) => { + req.query = squel.select().from('municipios').where('fk_estado_id = ?', + parseInt(req.params.id, 10)); + next(); +}, query, response); + +module.exports = cityApp; diff --git a/src/libs/routes/enrollment.js b/src/libs/routes/enrollment.js new file mode 100644 index 0000000000000000000000000000000000000000..7ca2be3e020635fed9d51affdaa242b960be93b4 --- /dev/null +++ b/src/libs/routes/enrollment.js @@ -0,0 +1,301 @@ +const express = require('express'); + +const enrollmentApp = express(); + +const libs = `${process.cwd()}/libs`; + +const log = require(`${libs}/log`)(module); + +const dbQuery = require('../db/query_exec'); + +const response = require('../middlewares/response'); + +/** + * Complete range of the enrollments dataset + * + * Returns a tuple of start and ending years of the complete enrollments dataset. + */ +enrollmentApp.get('/year_range', (req, res, next) => { + const yearSql = 'SELECT MIN(t.ano_censo) AS start_year, MAX(t.ano_censo)' + + 'AS end_year FROM turmas AS t'; + + dbQuery(yearSql).then((result) => { + const record = result['0']; + log.debug(record); + req.result = { start_year: record.start_year, end_year: record.end_year }; + return response(req, res); + }, (error) => { + log.error(`[${req.originalUrl}] SQL query error: ${error}`); + next('Internal error, request could not be satisfied at this moment. Please, ' + + 'try again later'); + }); +}); + +enrollmentApp.get('/education_level', (req, res, next) => { + const edLevelSql = 'SELECT pk_etapa_ensino_id AS id, desc_etapa AS ' + + 'education_level FROM etapa_ensino'; + + dbQuery(edLevelSql).then((result) => { + req.result = result; + return response(req, res); + }, (error) => { + log.error(`[${req.originalUrl}] SQL query error: ${error}`); + next('Internal error, request could not be satisfied at this moment. Please, ' + + 'try again later'); + }); +}); + +enrollmentApp.get('/data', (req, res, next) => { + const schoolClassSql = 'SELECT * FROM turmas'; + dbQuery(schoolClassSql).then((result) => { + req.result = result; + return response(req, res); + }, (error) => { + log.error(`[${req.originalUrl}] SQL query error: ${error}`); + next('Internal error, request could not be satisfied at this moment. Please, ' + + 'try again later'); + }); +}); + +enrollmentApp.use('/', (req, res, next) => { + const params = req.query; + req.paramCnt = 0; + + if (typeof params.id !== 'undefined') { + req.id = parseInt(params.id, 10); + req.paramCnt += 1; + } + + if (typeof params.location_id !== 'undefined') { + req.location_id = parseInt(params.location_id, 10); + req.paramCnt += 1; + } + + if (typeof params.adm_dependency_id !== 'undefined') { + req.adm_dependency_id = parseInt(params.adm_dependency_id, 10); + req.paramCnt += 1; + } + + if (typeof params.census_year !== 'undefined') { + req.census_year = parseInt(params.census_year, 10); + req.paramCnt += 1; + } + + if (typeof params.education_level_id !== 'undefined') { + req.education_level_id = parseInt(params.education_level_id, 10); + req.paramCnt += 1; + } + + next(); +}); + +enrollmentApp.use('/', (req, res, next) => { + const params = req.query; + if (typeof params.aggregate !== 'undefined' && params.aggregate === 'region') { + log.debug('Using enrollments query for regions'); + req.sqlQuery = 'SELECT r.nome AS name, COALESCE(SUM(t.num_matriculas), 0) AS total ' + + 'FROM regioes AS r ' + + 'INNER JOIN estados AS e ON r.pk_regiao_id = e.fk_regiao_id ' + + 'INNER JOIN municipios AS m ON e.pk_estado_id = m.fk_estado_id ' + + 'LEFT OUTER JOIN turmas AS t ON ( ' + + 'm.pk_municipio_id = t.fk_municipio_id '; + req.sqlQueryParams = []; + + if (typeof req.census_year !== 'undefined') { + req.sqlQuery += ' AND '; + req.sqlQuery += 't.ano_censo = ?'; + req.sqlQueryParams.push(req.census_year); + } + + if (typeof req.adm_dependency_id !== 'undefined') { + req.sqlQuery += ' AND '; + req.sqlQuery += 't.fk_dependencia_adm_id = ?'; + req.sqlQueryParams.push(req.adm_dependency_id); + } + + if (typeof req.location_id !== 'undefined') { + req.sqlQuery += ' AND '; + req.sqlQuery += 't.id_localizacao = ?'; + req.sqlQueryParams.push(req.location_id); + } + + if (typeof req.education_level_id !== 'undefined') { + req.sqlQuery += ' AND '; + req.sqlQuery += 't.fk_etapa_ensino_id = ?'; + req.sqlQueryParams.push(req.education_level_id); + } + + req.sqlQuery += ')'; + if (typeof req.id !== 'undefined') { + req.sqlQuery += ' WHERE '; + req.sqlQuery += 'r.pk_regiao_id = ?'; + req.sqlQueryParams.push(req.id); + } + req.sqlQuery += ' GROUP BY r.nome'; + } + next(); +}); + +enrollmentApp.use('/', (req, res, next) => { + const params = req.query; + if (typeof params.aggregate !== 'undefined' && params.aggregate === 'state') { + log.debug('Using enrollments query for states'); + req.sqlQuery = 'SELECT e.nome AS name, COALESCE(SUM(t.num_matriculas), 0) as total ' + + 'FROM estados AS e ' + + 'INNER JOIN municipios AS m ON m.fk_estado_id = e.pk_estado_id ' + + 'LEFT OUTER JOIN turmas AS t ON (' + + 'm.pk_municipio_id = t.fk_municipio_id '; + req.sqlQueryParams = []; + + if (typeof req.census_year !== 'undefined') { + req.sqlQuery += ' AND '; + req.sqlQuery += 't.ano_censo = ?'; + req.sqlQueryParams.push(req.census_year); + } + + if (typeof req.adm_dependency_id !== 'undefined') { + req.sqlQuery += ' AND '; + req.sqlQuery += 't.fk_dependencia_adm_id = ?'; + req.sqlQueryParams.push(req.adm_dependency_id); + } + + if (typeof req.location_id !== 'undefined') { + req.sqlQuery += ' AND '; + req.sqlQuery += 't.id_localizacao = ?'; + req.sqlQueryParams.push(req.location_id); + } + + if (typeof req.education_level_id !== 'undefined') { + req.sqlQuery += ' AND '; + req.sqlQuery += 't.fk_etapa_ensino_id = ?'; + req.sqlQueryParams.push(req.education_level_id); + } + + req.sqlQuery += ')'; + + if (typeof req.id !== 'undefined') { + req.sqlQuery += ' WHERE '; + req.sqlQuery += 'e.pk_estado_id = ?'; + req.sqlQueryParams.push(req.id); + } + + req.sqlQuery += ' GROUP BY e.nome'; + } + next(); +}); + +enrollmentApp.use('/', (req, res, next) => { + const params = req.query; + if (typeof params.aggregate !== 'undefined' && params.aggregate === 'city') { + log.debug('Using enrollments query for cities'); + req.sqlQuery = 'SELECT m.nome AS name, COALESCE(SUM(t.num_matriculas), 0) as total ' + + 'FROM municipios AS m ' + + 'LEFT OUTER JOIN turmas AS t ON ( ' + + 'm.pk_municipio_id = t.fk_municipio_id'; + req.sqlQueryParams = []; + + if (typeof req.census_year !== 'undefined') { + req.sqlQuery += ' AND '; + req.sqlQuery += 't.ano_censo = ?'; + req.sqlQueryParams.push(req.census_year); + } + + if (typeof req.adm_dependency_id !== 'undefined') { + req.sqlQuery += ' AND '; + req.sqlQuery += 't.fk_dependencia_adm_id = ?'; + req.sqlQueryParams.push(req.adm_dependency_id); + } + + if (typeof req.location_id !== 'undefined') { + req.sqlQuery += ' AND '; + req.sqlQuery += 't.id_localizacao = ?'; + req.sqlQueryParams.push(req.location_id); + } + + if (typeof req.education_level_id !== 'undefined') { + req.sqlQuery += ' AND '; + req.sqlQuery += 't.fk_etapa_ensino_id = ?'; + req.sqlQueryParams.push(req.education_level_id); + } + + req.sqlQuery += ')'; + + if (typeof req.id !== 'undefined') { + req.sqlQuery += ' WHERE '; + req.sqlQuery += 'm.pk_municipio_id = ?'; + req.sqlQueryParams.push(req.id); + } + + req.sqlQuery += 'GROUP BY m.nome'; + } + next(); +}); + +enrollmentApp.use('/', (req, res, next) => { + const params = req.query; + if (typeof params.aggregate === 'undefined') { + log.debug('Using enrollments query for the whole country'); + req.sqlQuery = 'SELECT \'Brasil\' AS name, COALESCE(SUM(t.num_matriculas),0) AS total ' + + 'FROM turmas AS t'; + req.sqlQueryParams = []; + + if (req.paramCnt > 0) { + req.sqlQuery += ' WHERE '; + } + + if (typeof req.census_year !== 'undefined') { + req.sqlQuery += 't.ano_censo = ?'; + req.sqlQueryParams.push(req.census_year); + } + + if (typeof req.adm_dependency_id !== 'undefined') { + if (req.sqlQueryParams.length > 0) { + req.sqlQuery += ' AND '; + } + req.sqlQuery += 't.fk_dependencia_adm_id = ?'; + req.sqlQueryParams.push(req.adm_dependency_id); + } + + if (typeof req.location_id !== 'undefined') { + if (req.sqlQueryParams.length > 0) { + req.sqlQuery += ' AND '; + } + req.sqlQuery += 't.id_localizacao = ?'; + req.sqlQueryParams.push(req.location_id); + } + + if (typeof req.education_level_id !== 'undefined') { + if (req.sqlQueryParams.length > 0) { + req.sqlQuery += ' AND '; + } + req.sqlQuery += 't.fk_etapa_ensino_id = ?'; + req.sqlQueryParams.push(req.education_level_id); + } + } + next(); +}); + +enrollmentApp.get('/', (req, res, next) => { + log.debug(`Request parameters: ${req}`); + if (typeof req.sqlQuery === 'undefined') { + // Should only happen if there is a bug in the chaining of the + // '/enrollments' route, since when no +aggregate+ parameter is given, + // it defaults to use the query for the whole country. + log.error('BUG -- No SQL query was found to be executed!'); + next('Internal error, request could not be satisfied at this moment. Please, ' + + 'try again later'); + } else { + log.debug('SQL query: ${ req.sqlQuery }?'); + log.debug(req.sqlQuery); + dbQuery(req.sqlQuery).then((result) => { + req.result = result; + return response(req, res); + }, (error) => { + log.error(`[${req.originalUrl}] SQL query error: ${error}`); + next('Internal error, request could not be satisfied at this moment. Please, ' + + 'try again later'); + }); + } +}); + +module.exports = enrollmentApp; diff --git a/src/libs/routes/region.js b/src/libs/routes/region.js new file mode 100644 index 0000000000000000000000000000000000000000..56c4cd2e44640ccd8a166e49afae97b567bb51ec --- /dev/null +++ b/src/libs/routes/region.js @@ -0,0 +1,24 @@ +const express = require('express'); + +const regionApp = express(); + +const libs = `${process.cwd()}/libs`; + +const squel = require('squel'); + +const query = require(`${libs}/middlewares/query`); + +const response = require(`${libs}/middlewares/response`); + +regionApp.get('/', (req, res, next) => { + req.query = squel.select().from('regioes').toParam(); + next(); +}, query, response); + +regionApp.get('/:id', (req, res, next) => { + req.query = squel.select().from('regioes').where('pk_regiao_id = ?', + parseInt(req.params.id, 10)).toParam(); + next(); +}, query, response); + +module.exports = regionApp; diff --git a/src/libs/routes/state.js b/src/libs/routes/state.js new file mode 100644 index 0000000000000000000000000000000000000000..dae0eb713ee44d43b4f9cca82e06ee32fa3b1582 --- /dev/null +++ b/src/libs/routes/state.js @@ -0,0 +1,30 @@ +const express = require('express'); + +const stateApp = express(); + +const libs = `${process.cwd()}/libs`; + +const squel = require('squel'); + +const query = require(`${libs}/middlewares/query`); + +const response = require(`${libs}/middlewares/response`); + +stateApp.get('/', (req, res, next) => { + req.query = squel.select().from('estados').toParam(); + next(); +}, query, response); + +stateApp.get('/:id', (req, res, next) => { + req.query = squel.select().from('estados').where('pk_estado_id = ?', + parseInt(req.params.id, 10)).toParam(); + next(); +}, query, response); + +stateApp.get('/region/:id', (req, res, next) => { + req.query = squel.select().from('estados').where('fk_regiao_id = ?', + parseInt(req.params.id, 10)).toParam(); + next(); +}, query, response); + +module.exports = stateApp; diff --git a/src/server.js b/src/server.js new file mode 100644 index 0000000000000000000000000000000000000000..7b3927e3fc64fe9f0001f01e95b0a2805feb33eb --- /dev/null +++ b/src/server.js @@ -0,0 +1,16 @@ +const debug = require('debug')('node-express-base'); + +const libs = `${process.cwd()}/libs`; + +const config = require(`${libs}/config`); + +const log = require(`${libs}/log`)(module); + +const app = require(`${libs}/app`); + +app.set('port', config.get('port') || 3000); + +const server = app.listen(app.get('port'), () => { + debug(`Express server listening on port ${server.address().port}`); + log.info(`Express server listening on port ${config.get('port')}`); +}); diff --git a/src/test/test.js b/src/test/test.js new file mode 100644 index 0000000000000000000000000000000000000000..dde277d0dec15186290a5a0aa992dd60d8e5c7a4 --- /dev/null +++ b/src/test/test.js @@ -0,0 +1,162 @@ +const chai = require('chai'); + +const dirtyChai = require('dirty-chai'); + +chai.use(dirtyChai); + +const chaiHttp = require('chai-http'); + +const assert = chai.assert; + +const expect = chai.expect; + +const should = chai.should(); // actually call the function + +const server = require('../libs/app'); + +chai.use(chaiHttp); + +describe('request enrollments', () => { + it('should list enrollments', (done) => { + chai.request(server) + .get('/v1/enrollment') + .end((err, res) => { + res.should.have.status(200); + res.should.be.json; + res.body.should.have.property('result'); + res.body.result.should.be.a('array'); + res.body.result[0].should.have.property('name'); + res.body.result[0].should.have.property('total'); + done(); + }); + }); +}); + +describe('request regions', () => { + it('should list all regions', (done) => { + chai.request(server) + .get('/v1/region') + .end((err, res) => { + res.should.have.status(200); + res.should.be.json; + res.body.should.have.property('result'); + res.body.result.should.be.a('array'); + res.body.result[0].should.have.property('pk_regiao_id'); + res.body.result[0].should.have.property('nome'); + done(); + }); + }); + + it('should list region by id', (done) => { + chai.request(server) + .get('/v1/region/1') + .end((err, res) => { + res.should.have.status(200); + res.should.be.json; + res.body.should.have.property('result'); + res.body.result.should.be.a('array'); + res.body.result.should.have.length(1); + res.body.result[0].should.have.property('pk_regiao_id'); + res.body.result[0].should.have.property('nome'); + done(); + }); + }); +}); + +describe('request states', () => { + it('should list all states', (done) => { + chai.request(server) + .get('/v1/state') + .end((err, res) => { + res.should.have.status(200); + res.should.be.json; + res.body.should.have.property('result'); + res.body.result.should.be.a('array'); + res.body.result[0].should.have.property('pk_estado_id'); + res.body.result[0].should.have.property('fk_regiao_id'); + res.body.result[0].should.have.property('nome'); + done(); + }); + }); + + it('should list a state by id', (done) => { + chai.request(server) + .get('/v1/state/11') + .end((err, res) => { + res.should.have.status(200); + res.should.be.json; + res.body.should.have.property('result'); + res.body.result.should.be.a('array'); + res.body.result.should.have.length(1); + res.body.result[0].should.have.property('pk_estado_id'); + res.body.result[0].should.have.property('fk_regiao_id'); + res.body.result[0].should.have.property('nome'); + done(); + }); + }); + + it('should list states by region id', (done) => { + chai.request(server) + .get('/v1/state/region/1') + .end((err, res) => { + res.should.have.status(200); + res.should.be.json; + res.body.should.have.property('result'); + res.body.result.should.be.a('array'); + res.body.result[0].should.have.property('pk_estado_id'); + res.body.result[0].should.have.property('fk_regiao_id'); + res.body.result[0].should.have.property('nome'); + done(); + }); + }); +}); + +describe('request cities', () => { + it('should list all cities', (done) => { + chai.request(server) + .get('/v1/city') + .end((err, res) => { + res.should.have.status(200); + res.should.be.json; + res.body.should.have.property('result'); + res.body.result.should.be.a('array'); + res.body.result[0].should.have.property('pk_municipio_id'); + res.body.result[0].should.have.property('fk_estado_id'); + res.body.result[0].should.have.property('nome'); + res.body.result[0].should.have.property('codigo_ibge'); + done(); + }); + }); + + it('should list a city by id', (done) => { + chai.request(server) + .get('/v1/city/1') + .end((err, res) => { + res.should.have.status(200); + res.should.be.json; + res.body.should.have.property('result'); + res.body.result.should.be.a('array'); + res.body.result[0].should.have.property('pk_municipio_id'); + res.body.result[0].should.have.property('fk_estado_id'); + res.body.result[0].should.have.property('nome'); + res.body.result[0].should.have.property('codigo_ibge'); + done(); + }); + }); + + it('should list a city by codigo_ibge', (done) => { + chai.request(server) + .get('/v1/city/ibge/1200013') + .end((err, res) => { + res.should.have.status(200); + res.should.be.json; + res.body.should.have.property('result'); + res.body.result.should.be.a('array'); + res.body.result[0].should.have.property('pk_municipio_id'); + res.body.result[0].should.have.property('fk_estado_id'); + res.body.result[0].should.have.property('nome'); + res.body.result[0].should.have.property('codigo_ibge'); + done(); + }); + }); +}); diff --git a/test/test.js b/test/test.js deleted file mode 100644 index d6b67ff84daf91c4abcb98e014cd8beee5c5c42f..0000000000000000000000000000000000000000 --- a/test/test.js +++ /dev/null @@ -1,142 +0,0 @@ -var chai = require('chai'); -var chaiHttp = require('chai-http'); -var assert = chai.assert; -var expect = chai.expect; -var should = chai.should(); //actually call the function -var server = require('../libs/app'); - -chai.use(chaiHttp); - -describe('request enrollments', function(){ - - it('should list enrollments', function(done){ - chai.request(server) - .get('/api/v1/enrollments') - .end(function(err, res){ - res.should.have.status(200); - res.should.be.json; - res.body.should.have.property('result'); - res.body.result.should.be.a('array'); - res.body.result[0].should.have.property('name'); - res.body.result[0].should.have.property('total'); - done(); - }) - }); -}); - -describe('request regions', function(){ - - it('should list all regions', function(done){ - chai.request(server) - .get('/api/v1/regions') - .end(function(err, res){ - res.should.have.status(200); - res.should.be.json; - res.body.should.have.property('result'); - res.body.result.should.be.a('array'); - res.body.result[0].should.have.property('pk_regiao_id'); - res.body.result[0].should.have.property('nome'); - done(); - }) - }); - - it('should list region by id', function(done){ - chai.request(server) - .get('/api/v1/regions/1') - .end(function(err, res){ - res.should.have.status(200); - res.should.be.json; - res.body.should.have.property('result'); - res.body.result.should.be.a('array'); - res.body.result.should.have.length(1); - res.body.result[0].should.have.property('pk_regiao_id'); - res.body.result[0].should.have.property('nome'); - done(); - }) - }); -}); - -describe('request states', function(){ - - it('should list all states', function(done){ - chai.request(server) - .get('/api/v1/states') - .end(function(err, res){ - res.should.have.status(200); - res.should.be.json; - res.body.should.have.property('result'); - res.body.result.should.be.a('array'); - res.body.result[0].should.have.property('pk_estado_id'); - res.body.result[0].should.have.property('fk_regiao_id'); - res.body.result[0].should.have.property('nome'); - done(); - }) - }); - - it('should list a state by id', function(done){ - chai.request(server) - .get('/api/v1/states/11') - .end(function(err, res){ - res.should.have.status(200); - res.should.be.json; - res.body.should.have.property('result'); - res.body.result.should.be.a('array'); - res.body.result.should.have.length(1); - res.body.result[0].should.have.property('pk_estado_id'); - res.body.result[0].should.have.property('fk_regiao_id'); - res.body.result[0].should.have.property('nome'); - done(); - }) - }); - - it('should list states by region id', function(done){ - chai.request(server) - .get('/api/v1/states/region/1') - .end(function(err, res){ - res.should.have.status(200); - res.should.be.json; - res.body.should.have.property('result'); - res.body.result.should.be.a('array'); - res.body.result[0].should.have.property('pk_estado_id'); - res.body.result[0].should.have.property('fk_regiao_id'); - res.body.result[0].should.have.property('nome'); - done(); - }) - }); -}); - -describe('request cities', function(){ - - it('should list all cities', function(done){ - chai.request(server) - .get('/api/v1/cities') - .end(function(err, res){ - res.should.have.status(200); - res.should.be.json; - res.body.should.have.property('result'); - res.body.result.should.be.a('array'); - res.body.result[0].should.have.property('pk_municipio_id'); - res.body.result[0].should.have.property('fk_estado_id'); - res.body.result[0].should.have.property('nome'); - res.body.result[0].should.have.property('codigo_ibge'); - done(); - }) - }); - - it('should list a city by id', function(done){ - chai.request(server) - .get('/api/v1/cities/1') - .end(function(err, res){ - res.should.have.status(200); - res.should.be.json; - res.body.should.have.property('result'); - res.body.result.should.be.a('array'); - res.body.result[0].should.have.property('pk_municipio_id'); - res.body.result[0].should.have.property('fk_estado_id'); - res.body.result[0].should.have.property('nome'); - res.body.result[0].should.have.property('codigo_ibge'); - done(); - }) - }); - -});