refactored server

This commit is contained in:
Danny Coates 2018-02-06 14:31:18 -08:00
parent 6d470b8eba
commit 3fd2537311
No known key found for this signature in database
GPG key ID: 4C442633C62E00CB
36 changed files with 2944 additions and 792 deletions

105
test/unit/auth-tests.js Normal file
View 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);
});
});

View file

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

View 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);
});
});

View file

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

View 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
View 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
View 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);
});
});

View 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
View 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
View 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);
});
});
});