diff --git a/package.json b/package.json index 5763fdc..6e7f289 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "start": "tsc && node dist/index.js || exit 1", "dev": "nodemon -e ts,yaml --exec \"npm run start\"", "loadDev": "node dist/test/loadDev.js", - "coverage": "nyc --reporter=html --reporter=tex mocha dist/**/**.spec.js" + "coverage": "tsc && nyc --reporter=html --reporter=text mocha dist/**/**.spec.js --timeout 5000" }, "keywords": [], "author": "", diff --git a/src/db.ts b/src/db.ts index c1d1fbb..fb5d424 100644 --- a/src/db.ts +++ b/src/db.ts @@ -42,15 +42,18 @@ export default class db { }); mongoose.connection.on('error', console.error.bind(console, 'connection error:')); mongoose.connection.on('disconnected', () => { // reset state on disconnect - console.info('Database disconnected'); - this.state.db = 0; - done(); + if (process.env.NODE_ENV !== 'test') { // Do not interfere with testing + console.info('Database disconnected'); + this.state.db = 0; + } }); process.on('SIGINT', () => { // close connection when app is terminated - mongoose.connection.close(() => { - console.info('Mongoose default connection disconnected through app termination'); - process.exit(0); - }); + if (!this.state.db) { // database still connected + mongoose.connection.close(() => { + console.info('Mongoose default connection disconnected through app termination'); + process.exit(0); + }); + } }); mongoose.connection.once('open', () => { mongoose.set('useFindAndModify', false); @@ -60,6 +63,14 @@ export default class db { }); } + static disconnect (done) { + mongoose.connection.close(() => { + console.info(process.env.NODE_ENV === 'test' ? '' : `Disconnected from database`); + this.state.db = 0; + done(); + }); + } + static getState () { return this.state; } diff --git a/src/index.ts b/src/index.ts index 55ca5ff..c007ca9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -13,6 +13,7 @@ import db from './db'; // TODO: add multiple samples at once // TODO: coverage // TODO: think about the display of deleted/new samples and validation in data and UI +// TODO: improve error coverage // tell if server is running in debug or production environment console.info(process.env.NODE_ENV === 'production' ? '===== PRODUCTION =====' : process.env.NODE_ENV === 'test' ? '' :'===== DEVELOPMENT ====='); diff --git a/src/routes/material.spec.ts b/src/routes/material.spec.ts index 344642d..ae8d305 100644 --- a/src/routes/material.spec.ts +++ b/src/routes/material.spec.ts @@ -12,6 +12,7 @@ describe('/material', () => { before(done => TestHelper.before(done)); beforeEach(done => server = TestHelper.beforeEach(server, done)); afterEach(done => TestHelper.afterEach(server, done)); + after(done => TestHelper.after(done)); describe('GET /materials', () => { it('returns all materials', done => { @@ -146,7 +147,6 @@ describe('/material', () => { } }); }); - done(); }); }); it('rejects requests from a write user', done => { diff --git a/src/routes/measurement.spec.ts b/src/routes/measurement.spec.ts index 6e58290..e1f36a4 100644 --- a/src/routes/measurement.spec.ts +++ b/src/routes/measurement.spec.ts @@ -5,14 +5,17 @@ import globals from '../globals'; // TODO: restore measurements for m/a +// TODO: coverage!!! + describe('/measurement', () => { let server; before(done => TestHelper.before(done)); beforeEach(done => server = TestHelper.beforeEach(server, done)); afterEach(done => TestHelper.afterEach(server, done)); + after(done => TestHelper.after(done)); - describe('GET /mesurement/{id}', () => { + describe('GET /measurement/{id}', () => { it('returns the right measurement', done => { TestHelper.request(server, done, { method: 'get', diff --git a/src/routes/measurement.ts b/src/routes/measurement.ts index 78b7ec1..0d0f0f6 100644 --- a/src/routes/measurement.ts +++ b/src/routes/measurement.ts @@ -36,7 +36,7 @@ router.put('/measurement/' + IdValidate.parameter(), async (req, res, next) => { const data = await MeasurementModel.findById(req.params.id).lean().exec().catch(err => {next(err);}) as any; if (data instanceof Error) return; if (!data) { - res.status(404).json({status: 'Not found'}); + return res.status(404).json({status: 'Not found'}); } // add properties needed for sampleIdCheck @@ -55,7 +55,6 @@ router.put('/measurement/' + IdValidate.parameter(), async (req, res, next) => { if (!await templateCheck(measurement, 'change', res, next)) return; await MeasurementModel.findByIdAndUpdate(req.params.id, measurement, {new: true}).lean().exec((err, data) => { if (err) return next(err); - console.log(data); res.json(MeasurementValidate.output(data)); }); }); @@ -66,12 +65,12 @@ router.delete('/measurement/' + IdValidate.parameter(), (req, res, next) => { MeasurementModel.findById(req.params.id).lean().exec(async (err, data) => { if (err) return next(err); if (!data) { - res.status(404).json({status: 'Not found'}); + return res.status(404).json({status: 'Not found'}); } if (!await sampleIdCheck(data, req, res, next)) return; await MeasurementModel.findByIdAndUpdate(req.params.id, {status:globals.status.deleted}).lean().exec(err => { if (err) return next(err); - res.json({status: 'OK'}); + return res.json({status: 'OK'}); }); }); }); @@ -89,7 +88,6 @@ router.post('/measurement/new', async (req, res, next) => { measurement.status = 0; await new MeasurementModel(measurement).save((err, data) => { if (err) return next(err); - console.log(data); res.json(MeasurementValidate.output(data.toObject())); }); }); diff --git a/src/routes/root.spec.ts b/src/routes/root.spec.ts index f8a803f..a5f8f8b 100644 --- a/src/routes/root.spec.ts +++ b/src/routes/root.spec.ts @@ -1,4 +1,5 @@ import TestHelper from "../test/helper"; +import db from '../db'; describe('/', () => { @@ -6,6 +7,7 @@ describe('/', () => { before(done => TestHelper.before(done)); beforeEach(done => server = TestHelper.beforeEach(server, done)); afterEach(done => TestHelper.afterEach(server, done)); + after(done => TestHelper.after(done)); describe('GET /', () => { it('returns the root message', done => { @@ -40,7 +42,15 @@ describe('/', () => { TestHelper.request(server, done, { method: 'get', url: '/authorized', - auth: {name: 'admin', pass: 'Abc123!!'}, + auth: {basic: {name: 'admin', pass: 'Abc123!!'}}, + httpStatus: 401 + }); + }); + it('does not work with incorrect username', done => { + TestHelper.request(server, done, { + method: 'get', + url: '/authorized', + auth: {basic: {name: 'adminxx', pass: 'Abc123!!'}}, httpStatus: 401 }); }); @@ -66,4 +76,32 @@ describe('/', () => { }); }); }); + + describe('An invalid JSON body', () => { + it('is rejected', done => { + TestHelper.request(server, done, { + method: 'post', + url: '/', + httpStatus: 400, + reqType: 'json', + req: '{"xxx"}', + res: {status: 'Invalid JSON body'} + }); + + }); + }); + + describe('A not connected database', () => { + it('resolves to an 500 error', done => { + db.disconnect(() => { + TestHelper.request(server, done, { + method: 'get', + url: '/', + httpStatus: 500 + }); + }); + }); + }); + + // describe('API') // TODO not in production }); \ No newline at end of file diff --git a/src/routes/sample.spec.ts b/src/routes/sample.spec.ts index f54f5b4..cfeeb7c 100644 --- a/src/routes/sample.spec.ts +++ b/src/routes/sample.spec.ts @@ -19,6 +19,7 @@ describe('/sample', () => { before(done => TestHelper.before(done)); beforeEach(done => server = TestHelper.beforeEach(server, done)); afterEach(done => TestHelper.afterEach(server, done)); + after(done => TestHelper.after(done)); describe('GET /samples', () => { it('returns all samples', done => { @@ -197,7 +198,7 @@ describe('/sample', () => { }); }); - it('returns a deleted sample for a maintain/admin user', done => { // TODO: make tests work + it('returns a deleted sample for a maintain/admin user', done => { TestHelper.request(server, done, { method: 'get', url: '/sample/400000000000000000000005', diff --git a/src/routes/template.spec.ts b/src/routes/template.spec.ts index 2ac09ae..54adfcb 100644 --- a/src/routes/template.spec.ts +++ b/src/routes/template.spec.ts @@ -13,6 +13,7 @@ describe('/template', () => { before(done => TestHelper.before(done)); beforeEach(done => server = TestHelper.beforeEach(server, done)); afterEach(done => TestHelper.afterEach(server, done)); + after(done => TestHelper.after(done)); describe('/template/condition', () => { describe('GET /template/conditions', () => { diff --git a/src/routes/template.ts b/src/routes/template.ts index f4054c1..849cf59 100644 --- a/src/routes/template.ts +++ b/src/routes/template.ts @@ -44,7 +44,7 @@ router.put('/template/:collection(measurement|condition)/' + IdValidate.paramete const templateData = await model(req).findById(req.params.id).lean().exec().catch(err => {next(err);}) as any; if (templateData instanceof Error) return; if (!templateData) { - res.status(404).json({status: 'Not found'}); + return res.status(404).json({status: 'Not found'}); } if (!_.isEqual(_.pick(templateData, _.keys(template)), template)) { // data was changed diff --git a/src/routes/user.spec.ts b/src/routes/user.spec.ts index a7a2ddb..a0d67a5 100644 --- a/src/routes/user.spec.ts +++ b/src/routes/user.spec.ts @@ -9,6 +9,7 @@ describe('/user', () => { before(done => TestHelper.before(done)); beforeEach(done => server = TestHelper.beforeEach(server, done)); afterEach(done => TestHelper.afterEach(server, done)); + after(done => TestHelper.after(done)); describe('GET /users', () => { it('returns all users', done => { diff --git a/src/test/helper.ts b/src/test/helper.ts index c724d14..539eba3 100644 --- a/src/test/helper.ts +++ b/src/test/helper.ts @@ -39,6 +39,10 @@ export default class TestHelper { server.close(done); } + static after(done) { + db.disconnect(done); + } + static request (server, done, options) { // options in form: {method, url, auth: {key/basic: 'name' or 'key'/{name, pass}}, httpStatus, req, res} let st = supertest(server); if (options.hasOwnProperty('auth') && options.auth.hasOwnProperty('key')) { // resolve API key @@ -58,6 +62,9 @@ export default class TestHelper { st = st.delete(options.url) break; } + if (options.hasOwnProperty('reqType')) { // request body + st = st.type(options.reqType); + } if (options.hasOwnProperty('req')) { // request body st = st.send(options.req); }