commit
3827a27583
@ -8,8 +8,13 @@ var _getVersion = function() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
exports.render = function(req, res) {
|
exports.render = function(req, res) {
|
||||||
|
|
||||||
if (config.publicPath) {
|
if (config.publicPath) {
|
||||||
return res.sendfile(config.publicPath + '/index.html');
|
return res.sendfile(config.publicPath + '/index.html', {}, function(err) {
|
||||||
|
res.status(404).json({
|
||||||
|
error: err
|
||||||
|
}).end();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
var version = _getVersion();
|
var version = _getVersion();
|
||||||
@ -18,6 +23,7 @@ exports.render = function(req, res) {
|
|||||||
|
|
||||||
exports.version = function(req, res) {
|
exports.version = function(req, res) {
|
||||||
var version = _getVersion();
|
var version = _getVersion();
|
||||||
res.json({ version: version });
|
res.json({
|
||||||
|
version: version
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -8,6 +8,7 @@ module.exports = function(app) {
|
|||||||
res.setHeader('Access-Control-Allow-Origin', '*');
|
res.setHeader('Access-Control-Allow-Origin', '*');
|
||||||
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, DELETE');
|
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, DELETE');
|
||||||
res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,Content-Type,Authorization');
|
res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,Content-Type,Authorization');
|
||||||
|
res.setHeader('Access-Control-Expose-Headers', 'X-Email-Needs-Validation,X-Quota-Per-Item,X-Quota-Items-Limit,X-RateLimit-Limit,X-RateLimit-Remaining');
|
||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@ -67,7 +67,7 @@ module.exports = function(app) {
|
|||||||
app.get(apiPrefix + '/email/retrieve/:email', emailPlugin.oldRetrieve);
|
app.get(apiPrefix + '/email/retrieve/:email', emailPlugin.oldRetrieve);
|
||||||
|
|
||||||
app.post(apiPrefix + '/email/delete/profile', emailPlugin.eraseProfile);
|
app.post(apiPrefix + '/email/delete/profile', emailPlugin.eraseProfile);
|
||||||
app.post(apiPrefix + '/email/delete/item/:key', emailPlugin.erase);
|
app.get(apiPrefix + '/email/delete/item', emailPlugin.erase);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Currency rates plugin
|
// Currency rates plugin
|
||||||
|
|||||||
@ -46,21 +46,38 @@
|
|||||||
INVALID_CODE: {
|
INVALID_CODE: {
|
||||||
code: 403,
|
code: 403,
|
||||||
message: 'The provided code is invalid'
|
message: 'The provided code is invalid'
|
||||||
|
},
|
||||||
|
OVER_QUOTA: {
|
||||||
|
code: 406,
|
||||||
|
message: 'User quota exceeded',
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var EMAIL_TO_PASSPHRASE = 'email-to-passphrase-';
|
var EMAIL_TO_PASSPHRASE = 'email-to-passphrase-';
|
||||||
var STORED_VALUE = 'emailstore-';
|
var STORED_VALUE = 'emailstore-';
|
||||||
|
var ITEMS_COUNT = 'itemscount-';
|
||||||
var PENDING = 'pending-';
|
var PENDING = 'pending-';
|
||||||
var VALIDATED = 'validated-';
|
var VALIDATED = 'validated-';
|
||||||
|
|
||||||
var SEPARATOR = '#';
|
var SEPARATOR = '#';
|
||||||
var MAX_ALLOWED_STORAGE = 1024 * 100 /* no more than 100 kb */ ;
|
|
||||||
|
var UNCONFIRMED_PER_ITEM_QUOTA = 1024 * 50; /* 50 kb */
|
||||||
|
var CONFIRMED_PER_ITEM_QUOTA = 1024 * 250; /* 250 kb */
|
||||||
|
|
||||||
|
var UNCONFIRMED_ITEMS_LIMIT = 5;
|
||||||
|
var CONFIRMED_ITEMS_LIMIT = 20;
|
||||||
|
|
||||||
|
var POST_LIMIT = 1024 * 250 /* Max POST 250 kb */ ;
|
||||||
|
|
||||||
var valueKey = function(email, key) {
|
var valueKey = function(email, key) {
|
||||||
return STORED_VALUE + bitcore.util.twoSha256(email + SEPARATOR + key).toString('hex');
|
return STORED_VALUE + bitcore.util.twoSha256(email + SEPARATOR + key).toString('hex');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
var countKey = function(email) {
|
||||||
|
return ITEMS_COUNT + bitcore.util.twoSha256(email).toString('hex');
|
||||||
|
};
|
||||||
|
|
||||||
var pendingKey = function(email) {
|
var pendingKey = function(email) {
|
||||||
return PENDING + email;
|
return PENDING + email;
|
||||||
};
|
};
|
||||||
@ -234,6 +251,79 @@
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* checkSizeQuota
|
||||||
|
*
|
||||||
|
* @param email
|
||||||
|
* @param size
|
||||||
|
* @param isConfirmed
|
||||||
|
* @param callback
|
||||||
|
*/
|
||||||
|
emailPlugin.checkSizeQuota = function(email, size, isConfirmed, callback) {
|
||||||
|
var err;
|
||||||
|
|
||||||
|
if (size > (isConfirmed ? CONFIRMED_PER_ITEM_QUOTA : UNCONFIRMED_PER_ITEM_QUOTA))
|
||||||
|
err = emailPlugin.errors.OVER_QUOTA;
|
||||||
|
|
||||||
|
logger.info('Storage size:', size);
|
||||||
|
return callback(err);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
emailPlugin.checkAndUpdateItemCounter = function(email, isConfirmed, isAdd, callback) {
|
||||||
|
// this is a new item... Check User's Items quota.
|
||||||
|
emailPlugin.db.get(countKey(email), function(err, counter) {
|
||||||
|
if (err && !err.notFound) {
|
||||||
|
return callback(emailPlugin.errors.INTERNAL_ERROR);
|
||||||
|
}
|
||||||
|
counter = (parseInt(counter) || 0)
|
||||||
|
|
||||||
|
if (isAdd) {
|
||||||
|
counter++;
|
||||||
|
logger.info('User counter quota:', counter);
|
||||||
|
if (counter > (isConfirmed ? CONFIRMED_ITEMS_LIMIT : UNCONFIRMED_ITEMS_LIMIT)) {
|
||||||
|
return callback(emailPlugin.errors.OVER_QUOTA);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (counter > 0) counter--;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
emailPlugin.db.put(countKey(email), counter, function(err) {
|
||||||
|
if (err) {
|
||||||
|
logger.error('error saving counter');
|
||||||
|
return callback(emailPlugin.errors.INTERNAL_ERROR);
|
||||||
|
}
|
||||||
|
return callback();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} email
|
||||||
|
* @param {string} key
|
||||||
|
* @param {Function(err)} callback
|
||||||
|
*/
|
||||||
|
emailPlugin.checkAndUpdateItemQuota = function(email, key, isConfirmed, callback) {
|
||||||
|
|
||||||
|
emailPlugin.db.get(valueKey(email, key), function(err) {
|
||||||
|
|
||||||
|
//existing item?
|
||||||
|
if (!err)
|
||||||
|
return callback();
|
||||||
|
|
||||||
|
if (err.notFound) {
|
||||||
|
//new item
|
||||||
|
return emailPlugin.checkAndUpdateItemCounter(email, isConfirmed, 1, callback);
|
||||||
|
} else {
|
||||||
|
return callback(emailPlugin.errors.INTERNAL_ERROR);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} email
|
* @param {string} email
|
||||||
* @param {string} key
|
* @param {string} key
|
||||||
@ -306,9 +396,12 @@
|
|||||||
emailPlugin.db.del(valueKey(email, key), function(error) {
|
emailPlugin.db.del(valueKey(email, key), function(error) {
|
||||||
if (error) {
|
if (error) {
|
||||||
logger.error(error);
|
logger.error(error);
|
||||||
|
if (error.notFound) {
|
||||||
|
return callback(emailPlugin.errors.NOT_FOUND);
|
||||||
|
}
|
||||||
return callback(emailPlugin.errors.INTERNAL_ERROR);
|
return callback(emailPlugin.errors.INTERNAL_ERROR);
|
||||||
}
|
}
|
||||||
return callback();
|
return emailPlugin.checkAndUpdateItemCounter(email, null, null, callback);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -325,7 +418,7 @@
|
|||||||
emailPlugin.db.del(validatedKey(email), cb);
|
emailPlugin.db.del(validatedKey(email), cb);
|
||||||
}
|
}
|
||||||
], function(err) {
|
], function(err) {
|
||||||
if (err) {
|
if (err && !err.notFound) {
|
||||||
logger.error(err);
|
logger.error(err);
|
||||||
return callback(emailPlugin.errors.INTERNAL_ERROR);
|
return callback(emailPlugin.errors.INTERNAL_ERROR);
|
||||||
}
|
}
|
||||||
@ -356,7 +449,7 @@
|
|||||||
|
|
||||||
request.on('data', function(data) {
|
request.on('data', function(data) {
|
||||||
queryData += data;
|
queryData += data;
|
||||||
if (queryData.length > MAX_ALLOWED_STORAGE) {
|
if (queryData.length > POST_LIMIT) {
|
||||||
queryData = '';
|
queryData = '';
|
||||||
response.writeHead(413, {
|
response.writeHead(413, {
|
||||||
'Content-Type': 'text/plain'
|
'Content-Type': 'text/plain'
|
||||||
@ -377,70 +470,87 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
emailPlugin.processPost = function(request, response, email, key, passphrase, record) {
|
emailPlugin.processPost = function(request, response, email, key, passphrase, record) {
|
||||||
|
var isConfirmed = false;
|
||||||
|
|
||||||
async.series([
|
async.series([
|
||||||
/**
|
/**
|
||||||
* Try to fetch this user's email. If it exists, check the secret is the same.
|
* Try to fetch this user's email. If it exists, check the secret is the same.
|
||||||
*/
|
*/
|
||||||
function(callback) {
|
function(callback) {
|
||||||
emailPlugin.exists(email, function(err, exists) {
|
emailPlugin.exists(email, function(err, exists) {
|
||||||
if (err) {
|
if (err) {
|
||||||
return callback(err);
|
return callback(err);
|
||||||
} else if (exists) {
|
} else if (exists) {
|
||||||
emailPlugin.checkPassphrase(email, passphrase, function(err, match) {
|
emailPlugin.checkPassphrase(email, passphrase, function(err, match) {
|
||||||
if (err) {
|
if (err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
if (match) {
|
||||||
|
return callback();
|
||||||
|
} else {
|
||||||
|
return callback(emailPlugin.errors.EMAIL_TAKEN);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
emailPlugin.savePassphrase(email, passphrase, function(err) {
|
||||||
return callback(err);
|
return callback(err);
|
||||||
}
|
});
|
||||||
if (match) {
|
}
|
||||||
return callback();
|
});
|
||||||
} else {
|
},
|
||||||
return callback(emailPlugin.errors.EMAIL_TAKEN);
|
function(callback) {
|
||||||
}
|
emailPlugin.isConfirmed(email, function(err, inIsConfirmed) {
|
||||||
});
|
if (err) return callback(err);
|
||||||
} else {
|
isConfirmed = inIsConfirmed;
|
||||||
emailPlugin.savePassphrase(email, passphrase, function(err) {
|
return callback();
|
||||||
if (err) {
|
});
|
||||||
return callback(err);
|
},
|
||||||
}
|
function(callback) {
|
||||||
return callback();
|
emailPlugin.checkSizeQuota(email, record.length, isConfirmed, function(err) {
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Save the encrypted private key in the storage.
|
|
||||||
*/
|
|
||||||
function(callback) {
|
|
||||||
emailPlugin.saveEncryptedData(email, key, record, function(err) {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
return callback(err);
|
||||||
}
|
});
|
||||||
return callback();
|
},
|
||||||
});
|
function(callback) {
|
||||||
},
|
emailPlugin.checkAndUpdateItemQuota(email, key, isConfirmed, function(err) {
|
||||||
/**
|
return callback(err);
|
||||||
* Create and store the verification secret. If successful, send a verification email.
|
});
|
||||||
*/
|
},
|
||||||
function(callback) {
|
/**
|
||||||
emailPlugin.createVerificationSecretAndSendEmail(email, function(err) {
|
* Save the encrypted private key in the storage.
|
||||||
if (err) {
|
*/
|
||||||
callback({
|
function(callback) {
|
||||||
code: 500,
|
emailPlugin.saveEncryptedData(email, key, record, function(err) {
|
||||||
message: err
|
if (err) {
|
||||||
});
|
return callback(err);
|
||||||
} else {
|
}
|
||||||
callback();
|
return callback();
|
||||||
}
|
});
|
||||||
});
|
},
|
||||||
}
|
/**
|
||||||
], function(err) {
|
* Create and store the verification secret. If successful, send a verification email.
|
||||||
if (err) {
|
*/
|
||||||
emailPlugin.returnError(err, response);
|
function(callback) {
|
||||||
} else {
|
emailPlugin.createVerificationSecretAndSendEmail(email, function(err) {
|
||||||
response.json({
|
if (err) {
|
||||||
success: true
|
callback({
|
||||||
}).end();
|
code: 500,
|
||||||
}
|
message: err
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
],
|
||||||
|
function(err) {
|
||||||
|
if (err) {
|
||||||
|
emailPlugin.returnError(err, response);
|
||||||
|
} else {
|
||||||
|
response.json({
|
||||||
|
success: true
|
||||||
|
}).end();
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
emailPlugin.getCredentialsFromRequest = function(request) {
|
emailPlugin.getCredentialsFromRequest = function(request) {
|
||||||
@ -462,18 +572,41 @@
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
emailPlugin.addValidationHeader = function(response, email, callback) {
|
|
||||||
emailPlugin.db.get(validatedKey(email), function(err, value) {
|
/**
|
||||||
if (err && !err.notFound) {
|
* @param {string} email
|
||||||
return callback(err);
|
* @param {Function(err, boolean)} callback
|
||||||
|
*/
|
||||||
|
emailPlugin.isConfirmed = function(email, callback) {
|
||||||
|
emailPlugin.db.get(validatedKey(email), function(err, isConfirmed) {
|
||||||
|
if (err && err.notFound) {
|
||||||
|
return callback(null, false);
|
||||||
|
} else if (err) {
|
||||||
|
return callback(emailPlugin.errors.INTERNAL_ERROR);
|
||||||
|
}
|
||||||
|
return callback(null, !!isConfirmed);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* addValidationAndQuotaHeader
|
||||||
|
*
|
||||||
|
* @param response
|
||||||
|
* @param email
|
||||||
|
* @param {Function(err, boolean)} callback
|
||||||
|
*/
|
||||||
|
emailPlugin.addValidationAndQuotaHeader = function(response, email, callback) {
|
||||||
|
emailPlugin.isConfirmed(email, function(err, isConfirmed) {
|
||||||
|
if (err) return callback(err);
|
||||||
|
|
||||||
|
if (!isConfirmed) {
|
||||||
|
response.set('X-Email-Needs-Validation', 'true');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (value) {
|
response.set('X-Quota-Per-Item', isConfirmed ? CONFIRMED_PER_ITEM_QUOTA : UNCONFIRMED_PER_ITEM_QUOTA);
|
||||||
return callback();
|
response.set('X-Quota-Items-Limit', isConfirmed ? CONFIRMED_ITEMS_LIMIT : UNCONFIRMED_ITEMS_LIMIT);
|
||||||
}
|
return callback();
|
||||||
|
|
||||||
response.set('X-Email-Needs-Validation', 'true');
|
|
||||||
return callback(null, value);
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -520,19 +653,17 @@
|
|||||||
*/
|
*/
|
||||||
emailPlugin.retrieve = function(request, response) {
|
emailPlugin.retrieve = function(request, response) {
|
||||||
emailPlugin.authorizeRequestWithKey(request, function(err, email, key) {
|
emailPlugin.authorizeRequestWithKey(request, function(err, email, key) {
|
||||||
if (err) {
|
if (err)
|
||||||
return emailPlugin.returnError(err, response);
|
return emailPlugin.returnError(err, response);
|
||||||
}
|
|
||||||
|
|
||||||
emailPlugin.retrieveByEmailAndKey(email, key, function(err, value) {
|
emailPlugin.retrieveByEmailAndKey(email, key, function(err, value) {
|
||||||
if (err) {
|
if (err)
|
||||||
return emailPlugin.returnError(err, response);
|
return emailPlugin.returnError(err, response);
|
||||||
}
|
|
||||||
|
|
||||||
emailPlugin.addValidationHeader(response, email, function(err) {
|
|
||||||
if (err) {
|
emailPlugin.addValidationAndQuotaHeader(response, email, function(err) {
|
||||||
|
if (err)
|
||||||
return emailPlugin.returnError(err, response);
|
return emailPlugin.returnError(err, response);
|
||||||
}
|
|
||||||
|
|
||||||
response.send(value).end();
|
response.send(value).end();
|
||||||
});
|
});
|
||||||
@ -653,7 +784,7 @@
|
|||||||
var queryData = '';
|
var queryData = '';
|
||||||
request.on('data', function(data) {
|
request.on('data', function(data) {
|
||||||
queryData += data;
|
queryData += data;
|
||||||
if (queryData.length > MAX_ALLOWED_STORAGE) {
|
if (queryData.length > POST_LIMIT) {
|
||||||
queryData = '';
|
queryData = '';
|
||||||
response.writeHead(413, {
|
response.writeHead(413, {
|
||||||
'Content-Type': 'text/plain'
|
'Content-Type': 'text/plain'
|
||||||
@ -718,7 +849,7 @@
|
|||||||
|
|
||||||
request.on('data', function(data) {
|
request.on('data', function(data) {
|
||||||
queryData += data;
|
queryData += data;
|
||||||
if (queryData.length > MAX_ALLOWED_STORAGE) {
|
if (queryData.length > UNCONFIRMED_PER_ITEM_QUOTA) {
|
||||||
queryData = '';
|
queryData = '';
|
||||||
response.writeHead(413, {
|
response.writeHead(413, {
|
||||||
'Content-Type': 'text/plain'
|
'Content-Type': 'text/plain'
|
||||||
|
|||||||
@ -288,6 +288,12 @@ describe('emailstore test', function() {
|
|||||||
plugin.exists.onFirstCall().callsArgWith(1, null, false);
|
plugin.exists.onFirstCall().callsArgWith(1, null, false);
|
||||||
plugin.savePassphrase = sinon.stub();
|
plugin.savePassphrase = sinon.stub();
|
||||||
plugin.savePassphrase.onFirstCall().callsArg(2);
|
plugin.savePassphrase.onFirstCall().callsArg(2);
|
||||||
|
plugin.isConfirmed = sinon.stub();
|
||||||
|
plugin.isConfirmed.onFirstCall().callsArgWith(1, null, false);
|
||||||
|
plugin.checkSizeQuota = sinon.stub();
|
||||||
|
plugin.checkSizeQuota.onFirstCall().callsArgWith(3, null);
|
||||||
|
plugin.checkAndUpdateItemQuota = sinon.stub();
|
||||||
|
plugin.checkAndUpdateItemQuota.onFirstCall().callsArgWith(3, null);
|
||||||
plugin.saveEncryptedData = sinon.stub();
|
plugin.saveEncryptedData = sinon.stub();
|
||||||
plugin.saveEncryptedData.onFirstCall().callsArg(3);
|
plugin.saveEncryptedData.onFirstCall().callsArg(3);
|
||||||
plugin.createVerificationSecretAndSendEmail = sinon.stub();
|
plugin.createVerificationSecretAndSendEmail = sinon.stub();
|
||||||
@ -317,6 +323,12 @@ describe('emailstore test', function() {
|
|||||||
plugin.exists.onFirstCall().callsArgWith(1, null, true);
|
plugin.exists.onFirstCall().callsArgWith(1, null, true);
|
||||||
plugin.checkPassphrase = sinon.stub();
|
plugin.checkPassphrase = sinon.stub();
|
||||||
plugin.checkPassphrase.onFirstCall().callsArgWith(2, null, true);
|
plugin.checkPassphrase.onFirstCall().callsArgWith(2, null, true);
|
||||||
|
plugin.isConfirmed = sinon.stub();
|
||||||
|
plugin.isConfirmed.onFirstCall().callsArgWith(1, null, false);
|
||||||
|
plugin.checkSizeQuota = sinon.stub();
|
||||||
|
plugin.checkSizeQuota.onFirstCall().callsArgWith(3, null);
|
||||||
|
plugin.checkAndUpdateItemQuota = sinon.stub();
|
||||||
|
plugin.checkAndUpdateItemQuota.onFirstCall().callsArgWith(3, null);
|
||||||
plugin.saveEncryptedData = sinon.stub();
|
plugin.saveEncryptedData = sinon.stub();
|
||||||
plugin.saveEncryptedData.onFirstCall().callsArg(3);
|
plugin.saveEncryptedData.onFirstCall().callsArg(3);
|
||||||
plugin.createVerificationSecretAndSendEmail = sinon.stub();
|
plugin.createVerificationSecretAndSendEmail = sinon.stub();
|
||||||
@ -385,6 +397,10 @@ describe('emailstore test', function() {
|
|||||||
});
|
});
|
||||||
it('deletes a stored element (key)', function(done) {
|
it('deletes a stored element (key)', function(done) {
|
||||||
leveldb_stub.del.onFirstCall().callsArg(1);
|
leveldb_stub.del.onFirstCall().callsArg(1);
|
||||||
|
|
||||||
|
plugin.checkAndUpdateItemCounter = sinon.stub();
|
||||||
|
plugin.checkAndUpdateItemCounter.onFirstCall().callsArg(3);
|
||||||
|
|
||||||
plugin.deleteByEmailAndKey(fakeEmail, fakeKey, function(err) {
|
plugin.deleteByEmailAndKey(fakeEmail, fakeKey, function(err) {
|
||||||
expect(err).to.be.undefined;
|
expect(err).to.be.undefined;
|
||||||
done();
|
done();
|
||||||
@ -440,6 +456,7 @@ describe('emailstore test', function() {
|
|||||||
|
|
||||||
response.send.onFirstCall().returnsThis();
|
response.send.onFirstCall().returnsThis();
|
||||||
plugin.addValidationHeader = sinon.stub().callsArg(2);
|
plugin.addValidationHeader = sinon.stub().callsArg(2);
|
||||||
|
plugin.addValidationAndQuotaHeader = sinon.stub().callsArg(2);
|
||||||
|
|
||||||
plugin.retrieve(request, response);
|
plugin.retrieve(request, response);
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user