From cdf45de8e20e42889301c7df4df521eb28fbcde5 Mon Sep 17 00:00:00 2001 From: Abhinav Adduri Date: Tue, 11 Jul 2017 12:47:40 -0700 Subject: [PATCH] added server tests --- package-lock.json | 76 +++++++++++- package.json | 3 +- server/portal_server.js | 23 +++- server/storage.js | 12 ++ test/server/server.test.js | 170 ++++++++++++++++++++++++++ test/test_upload.txt | 1 + test/{ => unit}/aws.storage.test.js | 2 +- test/{ => unit}/local.storage.test.js | 2 +- 8 files changed, 276 insertions(+), 13 deletions(-) create mode 100644 test/server/server.test.js create mode 100644 test/test_upload.txt rename test/{ => unit}/aws.storage.test.js (98%) rename test/{ => unit}/local.storage.test.js (98%) diff --git a/package-lock.json b/package-lock.json index d4a24e8b..a7b4c926 100644 --- a/package-lock.json +++ b/package-lock.json @@ -191,6 +191,11 @@ "integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0=", "dev": true }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, "autoprefixer": { "version": "6.7.7", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-6.7.7.tgz", @@ -556,11 +561,21 @@ } } }, + "combined-stream": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", + "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=" + }, "commander": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=" }, + "component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=" + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -694,6 +709,11 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" }, + "cookiejar": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.1.tgz", + "integrity": "sha1-Qa1XsbVVlR7BcUEqgZQrHoIA00o=" + }, "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", @@ -846,6 +866,11 @@ "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", "dev": true }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, "depd": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.0.tgz", @@ -1210,6 +1235,11 @@ "resolved": "https://registry.npmjs.org/express-handlebars/-/express-handlebars-3.0.0.tgz", "integrity": "sha1-gKBwu4GbCeSvLKbQeA91zgXnXC8=" }, + "extend": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", + "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=" + }, "external-editor": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.0.4.tgz", @@ -1306,12 +1336,22 @@ "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=" }, + "form-data": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.2.0.tgz", + "integrity": "sha1-ml47kpX5gLJiPPZPojixTOvKcHs=" + }, "formatio": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/formatio/-/formatio-1.2.0.tgz", "integrity": "sha1-87IWfZBoxGmKjVH092CjmlTYGOs=", "dev": true }, + "formidable": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.1.1.tgz", + "integrity": "sha1-lriIb3w8NQi5Mta9cMTTqI818ak=" + }, "forwarded": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.0.tgz", @@ -3381,8 +3421,7 @@ "process-nextick-args": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", - "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", - "dev": true + "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=" }, "progress": { "version": "2.0.0", @@ -3775,8 +3814,7 @@ "safe-buffer": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==", - "dev": true + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" }, "safe-regex": { "version": "1.1.0", @@ -4213,6 +4251,33 @@ "integrity": "sha1-rDQjdWMyfG/4l7ZHQr9q7BkK054=", "dev": true }, + "superagent": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-3.5.2.tgz", + "integrity": "sha1-M2GjlxVnUEw1EGOr6q4PqiPb8/g=", + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "readable-stream": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", + "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==" + }, + "string_decoder": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==" + } + } + }, + "supertest": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/supertest/-/supertest-3.0.0.tgz", + "integrity": "sha1-jUu2j9GDDuBwM7HFpamkAhyWUpY=" + }, "supports-color": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", @@ -4417,8 +4482,7 @@ "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, "utils-merge": { "version": "1.0.0", diff --git a/package.json b/package.json index aa846b15..2b419e71 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "raven": "^2.1.0", "raven-js": "^3.16.0", "redis": "^2.7.1", + "supertest": "^3.0.0", "uglify-es": "3.0.19" }, "devDependencies": { @@ -50,7 +51,7 @@ "lint:css": "stylelint 'public/*.css'", "lint:js": "eslint .", "start": "node server/portal_server", - "test": "mocha", + "test": "mocha test/unit && mocha test/server", "version": "node scripts/version" } } diff --git a/server/portal_server.js b/server/portal_server.js index dae7e602..f92cbe22 100644 --- a/server/portal_server.js +++ b/server/portal_server.js @@ -153,9 +153,19 @@ app.post('/delete/:id', (req, res) => { app.post('/upload', (req, res, next) => { const newId = crypto.randomBytes(5).toString('hex'); - const meta = JSON.parse(req.header('X-File-Metadata')); + let meta; + + try { + meta = JSON.parse(req.header('X-File-Metadata')); + } catch(err) { + res.sendStatus(400); + return; + } - if (!validateIV(meta.id)) { + if (!validateIV(meta.id) || + !meta.hasOwnProperty('aad') || + !meta.hasOwnProperty('id') || + !meta.hasOwnProperty('filename')) { res.sendStatus(404); return; } @@ -191,7 +201,7 @@ app.get('/__version__', (req, res) => { res.sendFile(path.join(STATIC_PATH, 'version.json')); }); -app.listen(conf.listen_port, () => { +const server = app.listen(conf.listen_port, () => { log.info('startServer:', `Portal app listening on port ${conf.listen_port}!`); }); @@ -201,4 +211,9 @@ const validateID = route_id => { const validateIV = route_id => { return route_id.match(/^[0-9a-fA-F]{24}$/) !== null; -}; \ No newline at end of file +}; + +module.exports = { + server: server, + storage: storage +} \ No newline at end of file diff --git a/server/storage.js b/server/storage.js index 38559ba3..b805133a 100644 --- a/server/storage.js +++ b/server/storage.js @@ -31,6 +31,8 @@ if (conf.s3_bucket) { delete: awsDelete, forceDelete: awsForceDelete, ping: awsPing, + flushall: flushall, + quit: quit, metadata }; } else { @@ -45,10 +47,20 @@ if (conf.s3_bucket) { delete: localDelete, forceDelete: localForceDelete, ping: localPing, + flushall: flushall, + quit: quit, metadata }; } +function flushall() { + redis_client.flushdb(); +} + +function quit() { + redis_client.quit(); +} + function metadata(id) { return new Promise((resolve, reject) => { redis_client.hgetall(id, (err, reply) => { diff --git a/test/server/server.test.js b/test/server/server.test.js new file mode 100644 index 00000000..582d0148 --- /dev/null +++ b/test/server/server.test.js @@ -0,0 +1,170 @@ +const assert = require('assert'); +const sinon = require('sinon'); +const proxyquire = require('proxyquire'); +const request = require('supertest'); +const fs = require('fs'); + + +const logStub = {}; +logStub.info = sinon.stub(); +logStub.error = sinon.stub(); + +const storage = proxyquire('../../server/storage', { + './log.js': function() { + return logStub; + } +}); + +storage.flushall(); + +describe('Server integration tests', function() { + let server; + let storage; + let uuid; + let fildId; + + before(function() { + const app = proxyquire('../../server/portal_server', { + './log.js': function() { + return logStub; + } + }); + + server = app.server; + storage = app.storage; + }); + + after(function() { + storage.flushall(); + storage.quit(); + server.close(); + }) + + function upload() { + return request(server).post('/upload') + .field('fname', 'test_upload.txt') + .set('X-File-Metadata', JSON.stringify({ + aad: '11111', + id: '111111111111111111111111', + filename: 'test_upload.txt' + })) + .attach('file', './test/test_upload.txt') + } + + it('Responds with a 200 when the service is up', function() { + return request(server).get('/').expect(200); + }); + + it('Rejects with a 404 when a file id is not valid', function() { + return request(server).post('/upload/123') + .field('fname', 'test_upload.txt') + .set('X-File-Metadata', JSON.stringify({ + 'silly': 'text' + })) + .attach('file', './test/test_upload.txt') + .expect(404) + }) + + it('Accepts a file and stores it when properly uploaded', function(done) { + upload().then(res => { + assert(res.body.hasOwnProperty('delete')); + uuid = res.body.delete; + assert(res.body.hasOwnProperty('url')); + assert(res.body.hasOwnProperty('id')); + fileId = res.body.id; + fs.access('./static/' + fileId, fs.constants.F_OK, err => { + if (err) { + done(new Error('The file does not exist')); + } else { + done(); + } + }) + }) + }) + + it('Responds with a 200 if a file exists', function() { + return request(server).get('/exists/' + fileId) + .expect(200) + }) + + it('Exists in the redis server', function() { + return storage.exists(fileId) + .then(() => assert(1)) + .catch(err => assert.fail()) + }) + + it('Fails delete if the delete token does not match', function() { + return request(server).post('/delete/' + fileId) + .send({ delete_token: 11 }) + .expect(404); + }) + + it('Fails delete if the id is invalid', function() { + return request(server).post('/delete/1') + .expect(404); + }) + + it('Successfully deletes if the id is valid and the delete token matches', function(done) { + request(server).post('/delete/' + fileId) + .send({ delete_token: uuid }) + .expect(200) + .then(() => { + fs.access('./static/' + fileId, fs.constants.F_OK, err => { + if (err) { + done(); + } else { + done(new Error('The file does not exist')); + } + }) + }) + }) + + it('Responds with a 404 if a file does not exist', function() { + return request(server).get('/exists/notfound') + .expect(404) + }) + + it('Uploads properly after a delete', function(done) { + upload().then(res => { + assert(res.body.hasOwnProperty('delete')); + uuid = res.body.delete; + assert(res.body.hasOwnProperty('url')); + assert(res.body.hasOwnProperty('id')); + fileId = res.body.id; + fs.access('./static/' + fileId, fs.constants.F_OK, err => { + if (err) { + done(new Error('The file does not exist')); + } else { + done(); + } + }) + }) + }) + + it('Responds with a 200 for the download page', function() { + return request(server).get('/download/' + fileId) + .expect(200); + }) + + it('Downloads a file properly', function() { + return request(server).get('/assets/download/' + fileId) + .then(res => { + assert(res.header.hasOwnProperty('content-disposition')); + assert(res.header.hasOwnProperty('content-type')) + assert(res.header.hasOwnProperty('content-length')) + assert(res.header.hasOwnProperty('x-file-metadata')) + assert.equal(res.header['content-disposition'], 'attachment; filename=test_upload.txt') + assert.equal(res.header['content-type'], 'application/octet-stream') + }) + }) + + it('The file is deleted after one download', function() { + assert(!fs.existsSync('./static/' + fileId)); + }) + + it('No longer exists in the redis server', function() { + return storage.exists(fileId) + .then(() => assert.fail()) + .catch(err => assert(1)) + }) +}); \ No newline at end of file diff --git a/test/test_upload.txt b/test/test_upload.txt new file mode 100644 index 00000000..273c1a9f --- /dev/null +++ b/test/test_upload.txt @@ -0,0 +1 @@ +This is a test. \ No newline at end of file diff --git a/test/aws.storage.test.js b/test/unit/aws.storage.test.js similarity index 98% rename from test/aws.storage.test.js rename to test/unit/aws.storage.test.js index eef1f2a7..0a1778bf 100644 --- a/test/aws.storage.test.js +++ b/test/unit/aws.storage.test.js @@ -43,7 +43,7 @@ const awsStub = { } }; -const storage = proxyquire('../server/storage', { +const storage = proxyquire('../../server/storage', { redis: redisStub, fs: fsStub, './log.js': function() { diff --git a/test/local.storage.test.js b/test/unit/local.storage.test.js similarity index 98% rename from test/local.storage.test.js rename to test/unit/local.storage.test.js index 3a9993b2..d50fb478 100644 --- a/test/local.storage.test.js +++ b/test/unit/local.storage.test.js @@ -32,7 +32,7 @@ const logStub = {}; logStub.info = sinon.stub(); logStub.error = sinon.stub(); -const storage = proxyquire('../server/storage', { +const storage = proxyquire('../../server/storage', { redis: redisStub, fs: fsStub, './log.js': function() {