Merge pull request #184 from mozilla/server_tests

Server tests
This commit is contained in:
Danny Coates 2017-07-11 13:01:06 -07:00 committed by GitHub
commit 57c7c475fc
9 changed files with 277 additions and 13 deletions

View File

@ -3,6 +3,7 @@ machine:
version: 8 version: 8
services: services:
- docker - docker
- redis
deployment: deployment:
latest: latest:

76
package-lock.json generated
View File

@ -191,6 +191,11 @@
"integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0=", "integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0=",
"dev": true "dev": true
}, },
"asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
},
"autoprefixer": { "autoprefixer": {
"version": "6.7.7", "version": "6.7.7",
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-6.7.7.tgz", "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": { "commander": {
"version": "2.9.0", "version": "2.9.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz",
"integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=" "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": { "concat-map": {
"version": "0.0.1", "version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "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", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
"integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" "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": { "core-util-is": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
@ -846,6 +866,11 @@
"integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=",
"dev": true "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": { "depd": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.0.tgz", "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", "resolved": "https://registry.npmjs.org/express-handlebars/-/express-handlebars-3.0.0.tgz",
"integrity": "sha1-gKBwu4GbCeSvLKbQeA91zgXnXC8=" "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": { "external-editor": {
"version": "2.0.4", "version": "2.0.4",
"resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.0.4.tgz", "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", "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz",
"integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=" "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": { "formatio": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/formatio/-/formatio-1.2.0.tgz", "resolved": "https://registry.npmjs.org/formatio/-/formatio-1.2.0.tgz",
"integrity": "sha1-87IWfZBoxGmKjVH092CjmlTYGOs=", "integrity": "sha1-87IWfZBoxGmKjVH092CjmlTYGOs=",
"dev": true "dev": true
}, },
"formidable": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/formidable/-/formidable-1.1.1.tgz",
"integrity": "sha1-lriIb3w8NQi5Mta9cMTTqI818ak="
},
"forwarded": { "forwarded": {
"version": "0.1.0", "version": "0.1.0",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.0.tgz", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.0.tgz",
@ -3381,8 +3421,7 @@
"process-nextick-args": { "process-nextick-args": {
"version": "1.0.7", "version": "1.0.7",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz",
"integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M="
"dev": true
}, },
"progress": { "progress": {
"version": "2.0.0", "version": "2.0.0",
@ -3775,8 +3814,7 @@
"safe-buffer": { "safe-buffer": {
"version": "5.1.1", "version": "5.1.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz",
"integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==", "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg=="
"dev": true
}, },
"safe-regex": { "safe-regex": {
"version": "1.1.0", "version": "1.1.0",
@ -4213,6 +4251,33 @@
"integrity": "sha1-rDQjdWMyfG/4l7ZHQr9q7BkK054=", "integrity": "sha1-rDQjdWMyfG/4l7ZHQr9q7BkK054=",
"dev": true "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": { "supports-color": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
@ -4417,8 +4482,7 @@
"util-deprecate": { "util-deprecate": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
"dev": true
}, },
"utils-merge": { "utils-merge": {
"version": "1.0.0", "version": "1.0.0",

View File

@ -18,6 +18,7 @@
"raven": "^2.1.0", "raven": "^2.1.0",
"raven-js": "^3.16.0", "raven-js": "^3.16.0",
"redis": "^2.7.1", "redis": "^2.7.1",
"supertest": "^3.0.0",
"uglify-es": "3.0.19" "uglify-es": "3.0.19"
}, },
"devDependencies": { "devDependencies": {
@ -50,7 +51,7 @@
"lint:css": "stylelint 'public/*.css'", "lint:css": "stylelint 'public/*.css'",
"lint:js": "eslint .", "lint:js": "eslint .",
"start": "node server/portal_server", "start": "node server/portal_server",
"test": "mocha", "test": "mocha test/unit && mocha test/server",
"version": "node scripts/version" "version": "node scripts/version"
} }
} }

View File

@ -153,9 +153,19 @@ app.post('/delete/:id', (req, res) => {
app.post('/upload', (req, res, next) => { app.post('/upload', (req, res, next) => {
const newId = crypto.randomBytes(5).toString('hex'); 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); res.sendStatus(404);
return; return;
} }
@ -191,7 +201,7 @@ app.get('/__version__', (req, res) => {
res.sendFile(path.join(STATIC_PATH, 'version.json')); 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}!`); log.info('startServer:', `Portal app listening on port ${conf.listen_port}!`);
}); });
@ -201,4 +211,9 @@ const validateID = route_id => {
const validateIV = route_id => { const validateIV = route_id => {
return route_id.match(/^[0-9a-fA-F]{24}$/) !== null; return route_id.match(/^[0-9a-fA-F]{24}$/) !== null;
}; };
module.exports = {
server: server,
storage: storage
}

View File

@ -31,6 +31,8 @@ if (conf.s3_bucket) {
delete: awsDelete, delete: awsDelete,
forceDelete: awsForceDelete, forceDelete: awsForceDelete,
ping: awsPing, ping: awsPing,
flushall: flushall,
quit: quit,
metadata metadata
}; };
} else { } else {
@ -45,10 +47,20 @@ if (conf.s3_bucket) {
delete: localDelete, delete: localDelete,
forceDelete: localForceDelete, forceDelete: localForceDelete,
ping: localPing, ping: localPing,
flushall: flushall,
quit: quit,
metadata metadata
}; };
} }
function flushall() {
redis_client.flushdb();
}
function quit() {
redis_client.quit();
}
function metadata(id) { function metadata(id) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
redis_client.hgetall(id, (err, reply) => { redis_client.hgetall(id, (err, reply) => {

170
test/server/server.test.js Normal file
View File

@ -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 fileId;
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))
})
});

1
test/test_upload.txt Normal file
View File

@ -0,0 +1 @@
This is a test.

View File

@ -43,7 +43,7 @@ const awsStub = {
} }
}; };
const storage = proxyquire('../server/storage', { const storage = proxyquire('../../server/storage', {
redis: redisStub, redis: redisStub,
fs: fsStub, fs: fsStub,
'./log.js': function() { './log.js': function() {

View File

@ -32,7 +32,7 @@ const logStub = {};
logStub.info = sinon.stub(); logStub.info = sinon.stub();
logStub.error = sinon.stub(); logStub.error = sinon.stub();
const storage = proxyquire('../server/storage', { const storage = proxyquire('../../server/storage', {
redis: redisStub, redis: redisStub,
fs: fsStub, fs: fsStub,
'./log.js': function() { './log.js': function() {