refactored server
This commit is contained in:
parent
6d470b8eba
commit
3fd2537311
36 changed files with 2944 additions and 792 deletions
105
test/unit/auth-tests.js
Normal file
105
test/unit/auth-tests.js
Normal file
|
@ -0,0 +1,105 @@
|
|||
const assert = require('assert');
|
||||
const sinon = require('sinon');
|
||||
const proxyquire = require('proxyquire').noCallThru();
|
||||
|
||||
const storage = {
|
||||
metadata: sinon.stub(),
|
||||
setField: sinon.stub()
|
||||
};
|
||||
|
||||
function request(id, auth) {
|
||||
return {
|
||||
params: { id },
|
||||
header: sinon.stub().returns(auth)
|
||||
};
|
||||
}
|
||||
|
||||
function response() {
|
||||
return {
|
||||
sendStatus: sinon.stub(),
|
||||
set: sinon.stub()
|
||||
};
|
||||
}
|
||||
|
||||
const next = sinon.stub();
|
||||
|
||||
const storedMeta = {
|
||||
auth:
|
||||
'r9uFxEs9GEVaQR9CJJ0uTKFGhFSOTRjOY2FCLFlCIZ0Cr-VGTVpMGlXDbNR8RMT55trMpSrzWtBVKq1LffOT2g',
|
||||
nonce: 'FL4oxA7IE1PW8shwFN9qZw=='
|
||||
};
|
||||
|
||||
const authMiddleware = proxyquire('../../server/middleware/auth', {
|
||||
'../storage': storage
|
||||
});
|
||||
|
||||
describe('Owner Middleware', function() {
|
||||
afterEach(function() {
|
||||
storage.metadata.reset();
|
||||
storage.setField.reset();
|
||||
next.reset();
|
||||
});
|
||||
|
||||
it('sends a 401 when no auth header is set', async function() {
|
||||
const req = request('x');
|
||||
const res = response();
|
||||
await authMiddleware(req, res, next);
|
||||
sinon.assert.calledWith(res.sendStatus, 401);
|
||||
sinon.assert.notCalled(next);
|
||||
});
|
||||
|
||||
it('sends a 404 when metadata is not found', async function() {
|
||||
const req = request('x', 'y');
|
||||
const res = response();
|
||||
await authMiddleware(req, res, next);
|
||||
sinon.assert.calledWith(res.sendStatus, 404);
|
||||
sinon.assert.notCalled(next);
|
||||
});
|
||||
|
||||
it('sends a 401 when the auth header is invalid base64', async function() {
|
||||
storage.metadata.returns(Promise.resolve(storedMeta));
|
||||
const req = request('x', '1');
|
||||
const res = response();
|
||||
await authMiddleware(req, res, next);
|
||||
sinon.assert.calledWith(res.sendStatus, 401);
|
||||
sinon.assert.notCalled(next);
|
||||
});
|
||||
|
||||
it('authenticates when the hashes match', async function() {
|
||||
storage.metadata.returns(Promise.resolve(storedMeta));
|
||||
const req = request(
|
||||
'x',
|
||||
'send-v1 R7nZk14qJqZXtxpnAtw2uDIRQTRnO1qSO1Q0PiwcNA8'
|
||||
);
|
||||
const res = response();
|
||||
await authMiddleware(req, res, next);
|
||||
sinon.assert.calledOnce(next);
|
||||
sinon.assert.calledWith(storage.setField, 'x', 'nonce', req.nonce);
|
||||
sinon.assert.calledWith(
|
||||
res.set,
|
||||
'WWW-Authenticate',
|
||||
`send-v1 ${req.nonce}`
|
||||
);
|
||||
sinon.assert.notCalled(res.sendStatus);
|
||||
assert.equal(req.authorized, true);
|
||||
assert.equal(req.meta, storedMeta);
|
||||
assert.notEqual(req.nonce, storedMeta.nonce);
|
||||
});
|
||||
|
||||
it('sends a 401 when the hashes do not match', async function() {
|
||||
storage.metadata.returns(Promise.resolve(storedMeta));
|
||||
const req = request(
|
||||
'x',
|
||||
'send-v1 R8nZk14qJqZXtxpnAtw2uDIRQTRnO1qSO1Q0PiwcNA8'
|
||||
);
|
||||
const res = response();
|
||||
await authMiddleware(req, res, next);
|
||||
sinon.assert.calledWith(res.sendStatus, 401);
|
||||
sinon.assert.calledWith(
|
||||
res.set,
|
||||
'WWW-Authenticate',
|
||||
`send-v1 ${storedMeta.nonce}`
|
||||
);
|
||||
sinon.assert.notCalled(next);
|
||||
});
|
||||
});
|
|
@ -1,173 +0,0 @@
|
|||
const assert = require('assert');
|
||||
const sinon = require('sinon');
|
||||
const proxyquire = require('proxyquire');
|
||||
const crypto = require('crypto');
|
||||
|
||||
const redisStub = {};
|
||||
const exists = sinon.stub();
|
||||
const hget = sinon.stub();
|
||||
const hmset = sinon.stub();
|
||||
const expire = sinon.spy();
|
||||
const del = sinon.stub();
|
||||
|
||||
redisStub.createClient = function() {
|
||||
return {
|
||||
on: sinon.spy(),
|
||||
exists: exists,
|
||||
hget: hget,
|
||||
hmset: hmset,
|
||||
expire: expire,
|
||||
del: del
|
||||
};
|
||||
};
|
||||
|
||||
const fsStub = {};
|
||||
fsStub.statSync = sinon.stub();
|
||||
fsStub.createReadStream = sinon.stub();
|
||||
fsStub.createWriteStream = sinon.stub();
|
||||
fsStub.unlinkSync = sinon.stub();
|
||||
|
||||
const logStub = {};
|
||||
logStub.info = sinon.stub();
|
||||
logStub.error = sinon.stub();
|
||||
|
||||
const s3Stub = {};
|
||||
s3Stub.headObject = sinon.stub();
|
||||
s3Stub.getObject = sinon.stub();
|
||||
s3Stub.upload = sinon.stub();
|
||||
s3Stub.deleteObject = sinon.stub();
|
||||
|
||||
const awsStub = {
|
||||
S3: function() {
|
||||
return s3Stub;
|
||||
}
|
||||
};
|
||||
|
||||
const storage = proxyquire('../../server/storage', {
|
||||
redis: redisStub,
|
||||
'redis-mock': redisStub,
|
||||
fs: fsStub,
|
||||
'./log': function() {
|
||||
return logStub;
|
||||
},
|
||||
'aws-sdk': awsStub,
|
||||
'./config': {
|
||||
s3_bucket: 'test'
|
||||
}
|
||||
});
|
||||
|
||||
describe('Testing Length using aws', function() {
|
||||
it('Filesize returns properly if id exists', function() {
|
||||
s3Stub.headObject.callsArgWith(1, null, { ContentLength: 1 });
|
||||
return storage
|
||||
.length('123')
|
||||
.then(reply => assert.equal(reply, 1))
|
||||
.catch(err => assert.fail());
|
||||
});
|
||||
|
||||
it('Filesize fails if the id does not exist', function() {
|
||||
s3Stub.headObject.callsArgWith(1, new Error(), null);
|
||||
return storage
|
||||
.length('123')
|
||||
.then(_reply => assert.fail())
|
||||
.catch(err => assert(1));
|
||||
});
|
||||
});
|
||||
|
||||
describe('Testing Get using aws', function() {
|
||||
it('Should not error out when the file exists', function() {
|
||||
const spy = sinon.spy();
|
||||
s3Stub.getObject.returns({
|
||||
createReadStream: spy
|
||||
});
|
||||
|
||||
storage.get('123');
|
||||
assert(spy.calledOnce);
|
||||
});
|
||||
|
||||
it('Should error when the file does not exist', function() {
|
||||
const err = function() {
|
||||
throw new Error();
|
||||
};
|
||||
const spy = sinon.spy(err);
|
||||
s3Stub.getObject.returns({
|
||||
createReadStream: spy
|
||||
});
|
||||
|
||||
assert.equal(storage.get('123'), null);
|
||||
assert(spy.threw());
|
||||
});
|
||||
});
|
||||
|
||||
describe('Testing Set using aws', function() {
|
||||
beforeEach(function() {
|
||||
expire.resetHistory();
|
||||
});
|
||||
|
||||
after(function() {
|
||||
crypto.randomBytes.restore();
|
||||
});
|
||||
|
||||
it('Should pass when the file is successfully uploaded', function() {
|
||||
const buf = Buffer.alloc(10);
|
||||
sinon.stub(crypto, 'randomBytes').returns(buf);
|
||||
s3Stub.upload.returns({ promise: () => Promise.resolve() });
|
||||
return storage
|
||||
.set('123', { on: sinon.stub() }, 'Filename.moz', {})
|
||||
.then(() => {
|
||||
assert(expire.calledOnce);
|
||||
assert(expire.calledWith('123', 86400));
|
||||
})
|
||||
.catch(err => assert.fail());
|
||||
});
|
||||
|
||||
it('Should fail if there was an error during uploading', function() {
|
||||
s3Stub.upload.returns({ promise: () => Promise.reject() });
|
||||
return storage
|
||||
.set('123', { on: sinon.stub() }, 'Filename.moz', 'url.com')
|
||||
.then(_reply => assert.fail())
|
||||
.catch(err => assert(1));
|
||||
});
|
||||
});
|
||||
|
||||
describe('Testing Delete from aws', function() {
|
||||
it('Returns successfully if the id is deleted off aws', function() {
|
||||
hget.callsArgWith(2, null, 'delete_token');
|
||||
s3Stub.deleteObject.callsArgWith(1, null, {});
|
||||
return storage
|
||||
.delete('file_id', 'delete_token')
|
||||
.then(_reply => assert(1), err => assert.fail());
|
||||
});
|
||||
|
||||
it('Delete fails if id exists locally but does not in aws', function() {
|
||||
hget.callsArgWith(2, null, 'delete_token');
|
||||
s3Stub.deleteObject.callsArgWith(1, new Error(), {});
|
||||
return storage
|
||||
.delete('file_id', 'delete_token')
|
||||
.then(_reply => assert.fail(), err => assert(1));
|
||||
});
|
||||
|
||||
it('Delete fails if the delete token does not match', function() {
|
||||
hget.callsArgWith(2, null, {});
|
||||
return storage
|
||||
.delete('Filename.moz', 'delete_token')
|
||||
.then(_reply => assert.fail())
|
||||
.catch(err => assert(1));
|
||||
});
|
||||
});
|
||||
|
||||
describe('Testing Forced Delete from aws', function() {
|
||||
it('Deletes properly if id exists', function() {
|
||||
s3Stub.deleteObject.callsArgWith(1, null, {});
|
||||
return storage
|
||||
.forceDelete('file_id', 'delete_token')
|
||||
.then(_reply => assert(1), err => assert.fail());
|
||||
});
|
||||
|
||||
it('Deletes fails if id does not exist', function() {
|
||||
s3Stub.deleteObject.callsArgWith(1, new Error(), {});
|
||||
return storage
|
||||
.forceDelete('file_id')
|
||||
.then(_reply => assert.fail(), err => assert(1));
|
||||
});
|
||||
});
|
43
test/unit/delete-tests.js
Normal file
43
test/unit/delete-tests.js
Normal file
|
@ -0,0 +1,43 @@
|
|||
const sinon = require('sinon');
|
||||
const proxyquire = require('proxyquire').noCallThru();
|
||||
|
||||
const storage = {
|
||||
del: sinon.stub()
|
||||
};
|
||||
|
||||
function request(id) {
|
||||
return {
|
||||
params: { id }
|
||||
};
|
||||
}
|
||||
|
||||
function response() {
|
||||
return {
|
||||
sendStatus: sinon.stub()
|
||||
};
|
||||
}
|
||||
|
||||
const delRoute = proxyquire('../../server/routes/delete', {
|
||||
'../storage': storage
|
||||
});
|
||||
|
||||
describe('/api/delete', function() {
|
||||
afterEach(function() {
|
||||
storage.del.reset();
|
||||
});
|
||||
|
||||
it('calls storage.del with the id parameter', async function() {
|
||||
const req = request('x');
|
||||
const res = response();
|
||||
await delRoute(req, res);
|
||||
sinon.assert.calledWith(storage.del, 'x');
|
||||
sinon.assert.calledWith(res.sendStatus, 200);
|
||||
});
|
||||
|
||||
it('sends a 404 on failure', async function() {
|
||||
storage.del.returns(Promise.reject(new Error()));
|
||||
const res = response();
|
||||
await delRoute(request('x'), res);
|
||||
sinon.assert.calledWith(res.sendStatus, 404);
|
||||
});
|
||||
});
|
59
test/unit/info-tests.js
Normal file
59
test/unit/info-tests.js
Normal file
|
@ -0,0 +1,59 @@
|
|||
const sinon = require('sinon');
|
||||
const proxyquire = require('proxyquire').noCallThru();
|
||||
|
||||
const storage = {
|
||||
ttl: sinon.stub()
|
||||
};
|
||||
|
||||
function request(id, meta) {
|
||||
return {
|
||||
params: { id },
|
||||
meta
|
||||
};
|
||||
}
|
||||
|
||||
function response() {
|
||||
return {
|
||||
sendStatus: sinon.stub(),
|
||||
send: sinon.stub()
|
||||
};
|
||||
}
|
||||
|
||||
const infoRoute = proxyquire('../../server/routes/info', {
|
||||
'../storage': storage
|
||||
});
|
||||
|
||||
describe('/api/info', function() {
|
||||
afterEach(function() {
|
||||
storage.ttl.reset();
|
||||
});
|
||||
|
||||
it('calls storage.ttl with the id parameter', async function() {
|
||||
const req = request('x');
|
||||
const res = response();
|
||||
await infoRoute(req, res);
|
||||
sinon.assert.calledWith(storage.ttl, 'x');
|
||||
});
|
||||
|
||||
it('sends a 404 on failure', async function() {
|
||||
storage.ttl.returns(Promise.reject(new Error()));
|
||||
const res = response();
|
||||
await infoRoute(request('x'), res);
|
||||
sinon.assert.calledWith(res.sendStatus, 404);
|
||||
});
|
||||
|
||||
it('returns a json object', async function() {
|
||||
storage.ttl.returns(Promise.resolve(123));
|
||||
const meta = {
|
||||
dlimit: '1',
|
||||
dl: '0'
|
||||
};
|
||||
const res = response();
|
||||
await infoRoute(request('x', meta), res);
|
||||
sinon.assert.calledWithMatch(res.send, {
|
||||
dlimit: 1,
|
||||
dtotal: 0,
|
||||
ttl: 123
|
||||
});
|
||||
});
|
||||
});
|
67
test/unit/language-tests.js
Normal file
67
test/unit/language-tests.js
Normal file
|
@ -0,0 +1,67 @@
|
|||
const assert = require('assert');
|
||||
const sinon = require('sinon');
|
||||
const proxyquire = require('proxyquire').noCallThru();
|
||||
|
||||
const config = {
|
||||
l10n_dev: false // prod configuration
|
||||
};
|
||||
const pkg = {
|
||||
availableLanguages: ['en-US', 'fr', 'it', 'es-ES']
|
||||
};
|
||||
|
||||
function request(acceptLang) {
|
||||
return {
|
||||
headers: {
|
||||
'accept-language': acceptLang
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const langMiddleware = proxyquire('../../server/middleware/language', {
|
||||
'../config': config,
|
||||
'../../package.json': pkg
|
||||
});
|
||||
|
||||
describe('Language Middleware', function() {
|
||||
it('defaults to en-US when no header is present', function() {
|
||||
const req = request();
|
||||
const next = sinon.stub();
|
||||
langMiddleware(req, null, next);
|
||||
assert.equal(req.language, 'en-US');
|
||||
sinon.assert.calledOnce(next);
|
||||
});
|
||||
|
||||
it('sets req.language to en-US when accept-language > 255 chars', function() {
|
||||
const accept = Array(257).join('a');
|
||||
assert.equal(accept.length, 256);
|
||||
const req = request(accept);
|
||||
const next = sinon.stub();
|
||||
langMiddleware(req, null, next);
|
||||
assert.equal(req.language, 'en-US');
|
||||
sinon.assert.calledOnce(next);
|
||||
});
|
||||
|
||||
it('defaults to en-US when no accept-language is available', function() {
|
||||
const req = request('fa,cs,ja');
|
||||
const next = sinon.stub();
|
||||
langMiddleware(req, null, next);
|
||||
assert.equal(req.language, 'en-US');
|
||||
sinon.assert.calledOnce(next);
|
||||
});
|
||||
|
||||
it('prefers higher q values', function() {
|
||||
const req = request('fa;q=0.5, it;q=0.9');
|
||||
const next = sinon.stub();
|
||||
langMiddleware(req, null, next);
|
||||
assert.equal(req.language, 'it');
|
||||
sinon.assert.calledOnce(next);
|
||||
});
|
||||
|
||||
it('uses likely subtags', function() {
|
||||
const req = request('es-MX');
|
||||
const next = sinon.stub();
|
||||
langMiddleware(req, null, next);
|
||||
assert.equal(req.language, 'es-ES');
|
||||
sinon.assert.calledOn(next);
|
||||
});
|
||||
});
|
|
@ -1,163 +0,0 @@
|
|||
const assert = require('assert');
|
||||
const sinon = require('sinon');
|
||||
const proxyquire = require('proxyquire');
|
||||
|
||||
const redisStub = {};
|
||||
const exists = sinon.stub();
|
||||
const hget = sinon.stub();
|
||||
const hmset = sinon.stub();
|
||||
const expire = sinon.stub();
|
||||
const del = sinon.stub();
|
||||
|
||||
redisStub.createClient = function() {
|
||||
return {
|
||||
on: sinon.spy(),
|
||||
exists: exists,
|
||||
hget: hget,
|
||||
hmset: hmset,
|
||||
expire: expire,
|
||||
del: del
|
||||
};
|
||||
};
|
||||
|
||||
const fsStub = {};
|
||||
fsStub.statSync = sinon.stub();
|
||||
fsStub.createReadStream = sinon.stub();
|
||||
fsStub.createWriteStream = sinon.stub();
|
||||
fsStub.unlinkSync = sinon.stub();
|
||||
|
||||
const logStub = {};
|
||||
logStub.info = sinon.stub();
|
||||
logStub.error = sinon.stub();
|
||||
|
||||
const storage = proxyquire('../../server/storage', {
|
||||
redis: redisStub,
|
||||
'redis-mock': redisStub,
|
||||
fs: fsStub,
|
||||
'./log': function() {
|
||||
return logStub;
|
||||
}
|
||||
});
|
||||
|
||||
describe('Testing Exists from local filesystem', function() {
|
||||
it('Exists returns true when file exists', function() {
|
||||
exists.callsArgWith(1, null, 1);
|
||||
return storage
|
||||
.exists('test')
|
||||
.then(() => assert(1))
|
||||
.catch(err => assert.fail());
|
||||
});
|
||||
|
||||
it('Exists returns false when file does not exist', function() {
|
||||
exists.callsArgWith(1, null, 0);
|
||||
return storage
|
||||
.exists('test')
|
||||
.then(() => assert.fail())
|
||||
.catch(err => assert(1));
|
||||
});
|
||||
});
|
||||
|
||||
describe('Testing Length from local filesystem', function() {
|
||||
it('Filesize returns properly if id exists', function() {
|
||||
fsStub.statSync.returns({ size: 10 });
|
||||
return storage
|
||||
.length('Filename.moz')
|
||||
.then(_reply => assert(1))
|
||||
.catch(err => assert.fail());
|
||||
});
|
||||
|
||||
it('Filesize fails if the id does not exist', function() {
|
||||
fsStub.statSync.returns(null);
|
||||
return storage
|
||||
.length('Filename.moz')
|
||||
.then(_reply => assert.fail())
|
||||
.catch(err => assert(1));
|
||||
});
|
||||
});
|
||||
|
||||
describe('Testing Get from local filesystem', function() {
|
||||
it('Get returns properly if id exists', function() {
|
||||
fsStub.createReadStream.returns(1);
|
||||
if (storage.get('Filename.moz')) {
|
||||
assert(1);
|
||||
} else {
|
||||
assert.fail();
|
||||
}
|
||||
});
|
||||
|
||||
it('Get fails if the id does not exist', function() {
|
||||
fsStub.createReadStream.returns(null);
|
||||
if (storage.get('Filename.moz')) {
|
||||
assert.fail();
|
||||
} else {
|
||||
assert(1);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('Testing Set to local filesystem', function() {
|
||||
it('Successfully writes the file to the local filesystem', function() {
|
||||
const stub = sinon.stub();
|
||||
stub.withArgs('finish', sinon.match.any).callsArgWithAsync(1);
|
||||
stub.withArgs('error', sinon.match.any).returns(1);
|
||||
fsStub.createWriteStream.returns({ on: stub });
|
||||
|
||||
return storage
|
||||
.set('test', { pipe: sinon.stub(), on: sinon.stub() }, 'Filename.moz', {})
|
||||
.then(() => {
|
||||
assert(1);
|
||||
})
|
||||
.catch(err => assert.fail());
|
||||
});
|
||||
|
||||
it('Fails when the file is not properly written to the local filesystem', function() {
|
||||
const stub = sinon.stub();
|
||||
stub.withArgs('error', sinon.match.any).callsArgWithAsync(1);
|
||||
stub.withArgs('close', sinon.match.any).returns(1);
|
||||
fsStub.createWriteStream.returns({ on: stub });
|
||||
|
||||
return storage
|
||||
.set('test', { pipe: sinon.stub() }, 'Filename.moz', 'moz.la')
|
||||
.then(_reply => assert.fail())
|
||||
.catch(err => assert(1));
|
||||
});
|
||||
});
|
||||
|
||||
describe('Testing Delete from local filesystem', function() {
|
||||
it('Deletes properly if id exists', function() {
|
||||
hget.callsArgWith(2, null, '123');
|
||||
fsStub.unlinkSync.returns(1);
|
||||
return storage
|
||||
.delete('Filename.moz', '123')
|
||||
.then(reply => assert(reply))
|
||||
.catch(err => assert.fail());
|
||||
});
|
||||
|
||||
it('Delete fails if id does not exist', function() {
|
||||
hget.callsArgWith(2, null, null);
|
||||
return storage
|
||||
.delete('Filename.moz', '123')
|
||||
.then(_reply => assert.fail())
|
||||
.catch(err => assert(1));
|
||||
});
|
||||
|
||||
it('Delete fails if the delete token does not match', function() {
|
||||
hget.callsArgWith(2, null, null);
|
||||
return storage
|
||||
.delete('Filename.moz', '123')
|
||||
.then(_reply => assert.fail())
|
||||
.catch(err => assert(1));
|
||||
});
|
||||
});
|
||||
|
||||
describe('Testing Forced Delete from local filesystem', function() {
|
||||
it('Deletes properly if id exists', function() {
|
||||
fsStub.unlinkSync.returns(1);
|
||||
return storage.forceDelete('Filename.moz').then(reply => assert(reply));
|
||||
});
|
||||
|
||||
it('Deletes fails if id does not exist, but no reject is called', function() {
|
||||
fsStub.unlinkSync.returns(0);
|
||||
return storage.forceDelete('Filename.moz').then(reply => assert(!reply));
|
||||
});
|
||||
});
|
65
test/unit/metadata-tests.js
Normal file
65
test/unit/metadata-tests.js
Normal file
|
@ -0,0 +1,65 @@
|
|||
const sinon = require('sinon');
|
||||
const proxyquire = require('proxyquire').noCallThru();
|
||||
|
||||
const storage = {
|
||||
ttl: sinon.stub(),
|
||||
length: sinon.stub()
|
||||
};
|
||||
|
||||
function request(id, meta) {
|
||||
return {
|
||||
params: { id },
|
||||
meta
|
||||
};
|
||||
}
|
||||
|
||||
function response() {
|
||||
return {
|
||||
sendStatus: sinon.stub(),
|
||||
send: sinon.stub()
|
||||
};
|
||||
}
|
||||
|
||||
const metadataRoute = proxyquire('../../server/routes/metadata', {
|
||||
'../storage': storage
|
||||
});
|
||||
|
||||
describe('/api/metadata', function() {
|
||||
afterEach(function() {
|
||||
storage.ttl.reset();
|
||||
storage.length.reset();
|
||||
});
|
||||
|
||||
it('calls storage.[ttl|length] with the id parameter', async function() {
|
||||
const req = request('x');
|
||||
const res = response();
|
||||
await metadataRoute(req, res);
|
||||
sinon.assert.calledWith(storage.ttl, 'x');
|
||||
sinon.assert.calledWith(storage.length, 'x');
|
||||
});
|
||||
|
||||
it('sends a 404 on failure', async function() {
|
||||
storage.length.returns(Promise.reject(new Error()));
|
||||
const res = response();
|
||||
await metadataRoute(request('x'), res);
|
||||
sinon.assert.calledWith(res.sendStatus, 404);
|
||||
});
|
||||
|
||||
it('returns a json object', async function() {
|
||||
storage.ttl.returns(Promise.resolve(123));
|
||||
storage.length.returns(Promise.resolve(987));
|
||||
const meta = {
|
||||
dlimit: 1,
|
||||
dl: 0,
|
||||
metadata: 'foo'
|
||||
};
|
||||
const res = response();
|
||||
await metadataRoute(request('x', meta), res);
|
||||
sinon.assert.calledWithMatch(res.send, {
|
||||
metadata: 'foo',
|
||||
finalDownload: true,
|
||||
size: 987,
|
||||
ttl: 123
|
||||
});
|
||||
});
|
||||
});
|
81
test/unit/owner-tests.js
Normal file
81
test/unit/owner-tests.js
Normal file
|
@ -0,0 +1,81 @@
|
|||
const assert = require('assert');
|
||||
const sinon = require('sinon');
|
||||
const proxyquire = require('proxyquire').noCallThru();
|
||||
|
||||
const storage = {
|
||||
metadata: sinon.stub()
|
||||
};
|
||||
|
||||
function request(id, owner_token) {
|
||||
return {
|
||||
params: { id },
|
||||
body: { owner_token }
|
||||
};
|
||||
}
|
||||
|
||||
function response() {
|
||||
return {
|
||||
sendStatus: sinon.stub()
|
||||
};
|
||||
}
|
||||
|
||||
const ownerMiddleware = proxyquire('../../server/middleware/owner', {
|
||||
'../storage': storage
|
||||
});
|
||||
|
||||
describe('Owner Middleware', function() {
|
||||
afterEach(function() {
|
||||
storage.metadata.reset();
|
||||
});
|
||||
|
||||
it('sends a 404 when the id is not found', async function() {
|
||||
const next = sinon.stub();
|
||||
storage.metadata.returns(Promise.resolve(null));
|
||||
const res = response();
|
||||
await ownerMiddleware(request('x', 'y'), res);
|
||||
sinon.assert.notCalled(next);
|
||||
sinon.assert.calledWith(res.sendStatus, 404);
|
||||
});
|
||||
|
||||
it('sends a 401 when the owner_token is missing', async function() {
|
||||
const next = sinon.stub();
|
||||
const meta = { owner: 'y' };
|
||||
storage.metadata.returns(Promise.resolve(meta));
|
||||
const res = response();
|
||||
await ownerMiddleware(request('x', null), res);
|
||||
sinon.assert.notCalled(next);
|
||||
sinon.assert.calledWith(res.sendStatus, 401);
|
||||
});
|
||||
|
||||
it('sends a 401 when the owner_token does not match', async function() {
|
||||
const next = sinon.stub();
|
||||
const meta = { owner: 'y' };
|
||||
storage.metadata.returns(Promise.resolve(meta));
|
||||
const res = response();
|
||||
await ownerMiddleware(request('x', 'z'), res);
|
||||
sinon.assert.notCalled(next);
|
||||
sinon.assert.calledWith(res.sendStatus, 401);
|
||||
});
|
||||
|
||||
it('sends a 401 if the metadata call fails', async function() {
|
||||
const next = sinon.stub();
|
||||
storage.metadata.returns(Promise.reject(new Error()));
|
||||
const res = response();
|
||||
await ownerMiddleware(request('x', 'y'), res);
|
||||
sinon.assert.notCalled(next);
|
||||
sinon.assert.calledWith(res.sendStatus, 401);
|
||||
});
|
||||
|
||||
it('sets req.meta and req.authorized on successful auth', async function() {
|
||||
const next = sinon.stub();
|
||||
const meta = { owner: 'y' };
|
||||
storage.metadata.returns(Promise.resolve(meta));
|
||||
const req = request('x', 'y');
|
||||
const res = response();
|
||||
await ownerMiddleware(req, res, next);
|
||||
assert.equal(req.meta, meta);
|
||||
assert.equal(req.authorized, true);
|
||||
sinon.assert.notCalled(res.sendStatus);
|
||||
sinon.assert.calledOnce(next);
|
||||
});
|
||||
});
|
56
test/unit/params-tests.js
Normal file
56
test/unit/params-tests.js
Normal file
|
@ -0,0 +1,56 @@
|
|||
const sinon = require('sinon');
|
||||
const proxyquire = require('proxyquire').noCallThru();
|
||||
|
||||
const storage = {
|
||||
setField: sinon.stub()
|
||||
};
|
||||
|
||||
function request(id) {
|
||||
return {
|
||||
params: { id },
|
||||
body: {}
|
||||
};
|
||||
}
|
||||
|
||||
function response() {
|
||||
return {
|
||||
sendStatus: sinon.stub()
|
||||
};
|
||||
}
|
||||
|
||||
const paramsRoute = proxyquire('../../server/routes/params', {
|
||||
'../storage': storage
|
||||
});
|
||||
|
||||
describe('/api/params', function() {
|
||||
afterEach(function() {
|
||||
storage.setField.reset();
|
||||
});
|
||||
|
||||
it('calls storage.setField with the correct parameter', function() {
|
||||
const req = request('x');
|
||||
const dlimit = 2;
|
||||
req.body.dlimit = dlimit;
|
||||
const res = response();
|
||||
paramsRoute(req, res);
|
||||
sinon.assert.calledWith(storage.setField, 'x', 'dlimit', dlimit);
|
||||
sinon.assert.calledWith(res.sendStatus, 200);
|
||||
});
|
||||
|
||||
it('sends a 400 if dlimit is too large', function() {
|
||||
const req = request('x');
|
||||
const res = response();
|
||||
req.body.dlimit = 21;
|
||||
paramsRoute(req, res);
|
||||
sinon.assert.calledWith(res.sendStatus, 400);
|
||||
});
|
||||
|
||||
it('sends a 404 on failure', function() {
|
||||
storage.setField.throws(new Error());
|
||||
const req = request('x');
|
||||
const res = response();
|
||||
req.body.dlimit = 2;
|
||||
paramsRoute(req, res);
|
||||
sinon.assert.calledWith(res.sendStatus, 404);
|
||||
});
|
||||
});
|
53
test/unit/password-tests.js
Normal file
53
test/unit/password-tests.js
Normal file
|
@ -0,0 +1,53 @@
|
|||
const sinon = require('sinon');
|
||||
const proxyquire = require('proxyquire').noCallThru();
|
||||
|
||||
const storage = {
|
||||
setField: sinon.stub()
|
||||
};
|
||||
|
||||
function request(id, body) {
|
||||
return {
|
||||
params: { id },
|
||||
body
|
||||
};
|
||||
}
|
||||
|
||||
function response() {
|
||||
return {
|
||||
sendStatus: sinon.stub()
|
||||
};
|
||||
}
|
||||
|
||||
const passwordRoute = proxyquire('../../server/routes/password', {
|
||||
'../storage': storage
|
||||
});
|
||||
|
||||
describe('/api/password', function() {
|
||||
afterEach(function() {
|
||||
storage.setField.reset();
|
||||
});
|
||||
|
||||
it('calls storage.setField with the correct parameter', function() {
|
||||
const req = request('x', { auth: 'z' });
|
||||
const res = response();
|
||||
passwordRoute(req, res);
|
||||
sinon.assert.calledWith(storage.setField, 'x', 'auth', 'z');
|
||||
sinon.assert.calledWith(storage.setField, 'x', 'pwd', true);
|
||||
sinon.assert.calledWith(res.sendStatus, 200);
|
||||
});
|
||||
|
||||
it('sends a 400 if auth is missing', function() {
|
||||
const req = request('x', {});
|
||||
const res = response();
|
||||
passwordRoute(req, res);
|
||||
sinon.assert.calledWith(res.sendStatus, 400);
|
||||
});
|
||||
|
||||
it('sends a 404 on failure', function() {
|
||||
storage.setField.throws(new Error());
|
||||
const req = request('x', { auth: 'z' });
|
||||
const res = response();
|
||||
passwordRoute(req, res);
|
||||
sinon.assert.calledWith(res.sendStatus, 404);
|
||||
});
|
||||
});
|
154
test/unit/s3-tests.js
Normal file
154
test/unit/s3-tests.js
Normal file
|
@ -0,0 +1,154 @@
|
|||
const assert = require('assert');
|
||||
const sinon = require('sinon');
|
||||
const proxyquire = require('proxyquire').noCallThru();
|
||||
|
||||
function resolvedPromise(val) {
|
||||
return {
|
||||
promise: () => Promise.resolve(val)
|
||||
};
|
||||
}
|
||||
|
||||
function rejectedPromise(err) {
|
||||
return {
|
||||
promise: () => Promise.reject(err)
|
||||
};
|
||||
}
|
||||
|
||||
const s3Stub = {
|
||||
headObject: sinon.stub(),
|
||||
getObject: sinon.stub(),
|
||||
upload: sinon.stub(),
|
||||
deleteObject: sinon.stub()
|
||||
};
|
||||
|
||||
const awsStub = {
|
||||
S3: function() {
|
||||
return s3Stub;
|
||||
}
|
||||
};
|
||||
|
||||
const S3Storage = proxyquire('../../server/storage/s3', {
|
||||
'aws-sdk': awsStub
|
||||
});
|
||||
|
||||
describe('S3Storage', function() {
|
||||
it('uses config.s3_bucket', function() {
|
||||
const s = new S3Storage({ s3_bucket: 'foo' });
|
||||
assert.equal(s.bucket, 'foo');
|
||||
});
|
||||
|
||||
describe('length', function() {
|
||||
it('returns the ContentLength', async function() {
|
||||
s3Stub.headObject = sinon
|
||||
.stub()
|
||||
.returns(resolvedPromise({ ContentLength: 123 }));
|
||||
const s = new S3Storage({ s3_bucket: 'foo' });
|
||||
const len = await s.length('x');
|
||||
assert.equal(len, 123);
|
||||
sinon.assert.calledWithMatch(s3Stub.headObject, {
|
||||
Bucket: 'foo',
|
||||
Key: 'x'
|
||||
});
|
||||
});
|
||||
|
||||
it('throws when id not found', async function() {
|
||||
const err = new Error();
|
||||
s3Stub.headObject = sinon.stub().returns(rejectedPromise(err));
|
||||
const s = new S3Storage({ s3_bucket: 'foo' });
|
||||
try {
|
||||
await s.length('x');
|
||||
assert.fail();
|
||||
} catch (e) {
|
||||
assert.equal(e, err);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('getStream', function() {
|
||||
it('returns a Stream object', function() {
|
||||
const stream = {};
|
||||
s3Stub.getObject = sinon
|
||||
.stub()
|
||||
.returns({ createReadStream: () => stream });
|
||||
const s = new S3Storage({ s3_bucket: 'foo' });
|
||||
const result = s.getStream('x');
|
||||
assert.equal(result, stream);
|
||||
sinon.assert.calledWithMatch(s3Stub.getObject, {
|
||||
Bucket: 'foo',
|
||||
Key: 'x'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('set', function() {
|
||||
it('calls s3.upload', async function() {
|
||||
const file = { on: sinon.stub() };
|
||||
s3Stub.upload = sinon.stub().returns(resolvedPromise());
|
||||
const s = new S3Storage({ s3_bucket: 'foo' });
|
||||
await s.set('x', file);
|
||||
sinon.assert.calledWithMatch(s3Stub.upload, {
|
||||
Bucket: 'foo',
|
||||
Key: 'x',
|
||||
Body: file
|
||||
});
|
||||
});
|
||||
|
||||
it('aborts upload if limit is hit', async function() {
|
||||
const file = {
|
||||
on: (ev, fn) => fn()
|
||||
};
|
||||
const abort = sinon.stub();
|
||||
const err = new Error();
|
||||
s3Stub.upload = sinon.stub().returns({
|
||||
promise: () => Promise.reject(err),
|
||||
abort
|
||||
});
|
||||
const s = new S3Storage({ s3_bucket: 'foo' });
|
||||
try {
|
||||
await s.set('x', file);
|
||||
assert.fail();
|
||||
} catch (e) {
|
||||
assert.equal(e.message, 'limit');
|
||||
sinon.assert.calledOnce(abort);
|
||||
}
|
||||
});
|
||||
|
||||
it('throws when s3.upload fails', async function() {
|
||||
const file = {
|
||||
on: sinon.stub()
|
||||
};
|
||||
const err = new Error();
|
||||
s3Stub.upload = sinon.stub().returns(rejectedPromise(err));
|
||||
const s = new S3Storage({ s3_bucket: 'foo' });
|
||||
try {
|
||||
await s.set('x', file);
|
||||
assert.fail();
|
||||
} catch (e) {
|
||||
assert.equal(e, err);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('del', function() {
|
||||
it('calls s3.deleteObject', async function() {
|
||||
s3Stub.deleteObject = sinon.stub().returns(resolvedPromise(true));
|
||||
const s = new S3Storage({ s3_bucket: 'foo' });
|
||||
const result = await s.del('x');
|
||||
assert.equal(result, true);
|
||||
sinon.assert.calledWithMatch(s3Stub.deleteObject, {
|
||||
Bucket: 'foo',
|
||||
Key: 'x'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('ping', function() {
|
||||
it('calls s3.headBucket', async function() {
|
||||
s3Stub.headBucket = sinon.stub().returns(resolvedPromise(true));
|
||||
const s = new S3Storage({ s3_bucket: 'foo' });
|
||||
const result = await s.ping();
|
||||
assert.equal(result, true);
|
||||
sinon.assert.calledWithMatch(s3Stub.headBucket, { Bucket: 'foo' });
|
||||
});
|
||||
});
|
||||
});
|
118
test/unit/storage-tests.js
Normal file
118
test/unit/storage-tests.js
Normal file
|
@ -0,0 +1,118 @@
|
|||
const assert = require('assert');
|
||||
const proxyquire = require('proxyquire').noCallThru();
|
||||
|
||||
const stream = {};
|
||||
class MockStorage {
|
||||
length() {
|
||||
return Promise.resolve(12);
|
||||
}
|
||||
getStream() {
|
||||
return stream;
|
||||
}
|
||||
set() {
|
||||
return Promise.resolve();
|
||||
}
|
||||
del() {
|
||||
return Promise.resolve();
|
||||
}
|
||||
ping() {
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
const expire_seconds = 10;
|
||||
const storage = proxyquire('../../server/storage', {
|
||||
'../config': {
|
||||
expire_seconds,
|
||||
s3_bucket: 'foo',
|
||||
env: 'development',
|
||||
redis_host: 'localhost'
|
||||
},
|
||||
'../log': () => {},
|
||||
'./s3': MockStorage
|
||||
});
|
||||
|
||||
describe('Storage', function() {
|
||||
describe('ttl', function() {
|
||||
it('returns milliseconds remaining', async function() {
|
||||
await storage.set('x', null, { foo: 'bar' });
|
||||
const ms = await storage.ttl('x');
|
||||
await storage.del('x');
|
||||
assert.equal(ms, expire_seconds * 1000);
|
||||
});
|
||||
});
|
||||
|
||||
describe('length', function() {
|
||||
it('returns the file size', async function() {
|
||||
const len = await storage.length('x');
|
||||
assert.equal(len, 12);
|
||||
});
|
||||
});
|
||||
|
||||
describe('get', function() {
|
||||
it('returns a stream', function() {
|
||||
const s = storage.get('x');
|
||||
assert.equal(s, stream);
|
||||
});
|
||||
});
|
||||
|
||||
describe('set', function() {
|
||||
it('sets expiration to config.expire_seconds', async function() {
|
||||
await storage.set('x', null, { foo: 'bar' });
|
||||
const s = await storage.redis.ttlAsync('x');
|
||||
await storage.del('x');
|
||||
assert.equal(Math.ceil(s), expire_seconds);
|
||||
});
|
||||
|
||||
it('sets metadata', async function() {
|
||||
const m = { foo: 'bar' };
|
||||
await storage.set('x', null, m);
|
||||
const meta = await storage.redis.hgetallAsync('x');
|
||||
await storage.del('x');
|
||||
assert.deepEqual(meta, m);
|
||||
});
|
||||
|
||||
//it('throws when storage fails');
|
||||
});
|
||||
|
||||
describe('setField', function() {
|
||||
it('works', async function() {
|
||||
storage.setField('x', 'y', 'z');
|
||||
const z = await storage.redis.hgetAsync('x', 'y');
|
||||
assert.equal(z, 'z');
|
||||
await storage.del('x');
|
||||
});
|
||||
});
|
||||
|
||||
describe('del', function() {
|
||||
it('works', async function() {
|
||||
await storage.set('x', null, { foo: 'bar' });
|
||||
await storage.del('x');
|
||||
const meta = await storage.metadata('x');
|
||||
assert.equal(meta, null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ping', function() {
|
||||
it('works', async function() {
|
||||
await storage.ping();
|
||||
});
|
||||
});
|
||||
|
||||
describe('metadata', function() {
|
||||
it('returns all metadata fields', async function() {
|
||||
const m = {
|
||||
pwd: true,
|
||||
dl: 1,
|
||||
dlimit: 1,
|
||||
auth: 'foo',
|
||||
metadata: 'bar',
|
||||
nonce: 'baz',
|
||||
owner: 'bmo'
|
||||
};
|
||||
await storage.set('x', null, m);
|
||||
const meta = await storage.metadata('x');
|
||||
assert.deepEqual(meta, m);
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue