Permalink
Browse files

basic test suite built

  • Loading branch information...
1 parent c59164c commit 995b718e49ce26ccc95f2f3dda9679d08be2e815 @kbjr kbjr committed Jun 2, 2012
Showing with 383 additions and 67 deletions.
  1. +30 −14 lib/index.js
  2. +2 −0 test/buster.js
  3. +68 −0 test/io-setup.js
  4. +48 −0 test/mock-http.js
  5. +127 −0 test/tests/request-password-reset.js
  6. +108 −53 test/tests/request-reset-token.js
View
@@ -124,16 +124,16 @@ exports.requestResetToken = function(opts) {
callbackURL: '/password/reset/{token}'
}, opts);
var func = function(req, res, next) {
- var login = req.body[opts.loginParam];
+ var login = req.body ? req.body[opts.loginParam] : null;
if (! login) {
- return res.json('No login given', 400);
+ return res.json(jsonError('No login given'), 400);
}
lookupUsers(login, function(err, users) {
if (err) {
- return res.json(err, 500);
+ return res.json(jsonError(err), 500);
}
if (! users) {
- return res.json('No such user', 404);
+ return res.json(jsonError('No such user'), 404);
}
users.users = users.users.map(function(user) {
var token = storage.create(user.id);
@@ -145,7 +145,7 @@ exports.requestResetToken = function(opts) {
});
sendEmail(users.email, users.users, function(err, sent) {
if (err) {
- return res.json(err, 500);
+ return res.json(jsonError(err), 500);
}
if (! opts.next) {
return res.send(200);
@@ -177,28 +177,28 @@ exports.resetPassword = function(opts) {
confirmParam: 'confirm'
}, opts);
var func = function(req, res, next) {
- var params = {
+ var params = req.body ? {
token: req.body[opts.tokenParam],
password: req.body[opts.passwordParam],
confirm: req.body[opts.confirmParam]
- };
- var id = storage.lookup(params.token);
+ } : { };
if (! params.token || ! params.password || ! params.confirm) {
- return res.json('Cannot attempt reset with missing params', 400);
+ return res.json(jsonError('Cannot attempt reset with missing params'), 400);
}
+ var id = storage.lookup(params.token);
if (! id) {
- return res.json('Request token is invalid', 401);
+ return res.json(jsonError('Request token is invalid'), 401);
}
if (params.password !== params.confirm) {
- return res.json('Password and confirmation do not match', 400);
+ return res.json(jsonError('Password and confirmation do not match'), 400);
}
setPassword(id, params.password,
function(err, success, validationError) {
if (err) {
- return res.json(err, 500);
+ return res.json(jsonError(err), 500);
}
if (! success) {
- return res.json(validationError, 400);
+ return res.json(jsonError(validationError), 400);
}
storage.destroy(params.token);
if (! opts.next) {
@@ -215,12 +215,28 @@ exports.resetPassword = function(opts) {
);
};
func._opts = opts;
- return opts;
+ return func;
};
// ------------------------------------------------------------------
// Utilities
+function jsonError(msg) {
+ if (msg instanceof Error) {
+ msg = {
+ type: msg.type,
+ message: msg.message,
+ stack: msg.stack,
+ stackArray: msg.stack.split('\n').slice(1).map(function(str) {
+ return str.trim();
+ })
+ };
+ } else {
+ msg = {message: msg};
+ }
+ return {error: msg};
+}
+
function merge(host) {
host = isMutable(host) ? host : { };
Array.prototype.slice.call(arguments, 1).forEach(function(arg) {
View
@@ -1,4 +1,6 @@
+require('./io-setup');
+
exports['Tests'] = {
env: 'node',
rootPath: '../',
View
@@ -0,0 +1,68 @@
+
+var events = require('events');
+var passreset = require('../lib');
+
+module.exports = exports = new events.EventEmitter();
+
+passreset.lookupUsers(function(login, callback) {
+ callback = async(callback);
+ switch (login) {
+ case 'valid':
+ callback(null, {
+ email: 'foo1@example.com',
+ users: [{
+ id: 123,
+ name: 'foo'
+ }]
+ });
+ break;
+ case 'multivalid':
+ callback(null, {
+ email: 'foo2@example.com',
+ users: [{
+ id: 234,
+ name: 'bar'
+ }, {
+ id: 345,
+ name: 'baz'
+ }]
+ });
+ break;
+ case 'invalid':
+ callback(null, false);
+ break;
+ default:
+ callback('Invalid test login given');
+ break;
+ }
+});
+
+passreset.sendEmail(function(email, resets, callback) {
+ exports.emit('sendEmail', email, resets);
+ async(callback)(null, true);
+});
+
+passreset.setPassword(function(id, password, callback) {
+ callback = async(callback);
+ switch (password) {
+ case 'short':
+ callback(null, false, 'Password is too short');
+ break;
+ case 'goodPassword':
+ callback(null, true);
+ break;
+ default:
+ callback('Invalid test password given');
+ break;
+ }
+});
+
+function async(func) {
+ return function() {
+ var args = arguments;
+ process.nextTick(function() {
+ func.apply(this, args);
+ });
+ };
+}
+
View
@@ -0,0 +1,48 @@
+
+var events = require('events');
+
+// ------------------------------------------------------------------
+
+var Request = exports.Request = function(body) {
+ if (typeof body === 'string') {
+ body = JSON.parse(body);
+ }
+ this.body = body || { };
+};
+
+// ------------------------------------------------------------------
+
+var Response = exports.Response = function() {
+ this.status = 200;
+ this.response = '';
+ this.redirectedTo = null;
+};
+
+Response.prototype = new events.EventEmitter();
+
+Response.prototype.json = function(response, status) {
+ if (typeof response === 'number' && ! status) {
+ this.status = response;
+ } else {
+ this.response = JSON.stringify(response);
+ this.status = status || 200;
+ }
+ this.emit('respond', this);
+};
+
+Response.prototype.send = function(response, status) {
+ if (typeof response === 'number' && ! status) {
+ this.status = response;
+ } else {
+ this.response = String(response);
+ this.status = status || 200;
+ }
+ this.emit('respond', this);
+};
+
+Response.prototype.redirect = function(redirectTo, status) {
+ this.redirectedTo = redirectTo;
+ this.status = status || 302;
+ this.emit('respond', this);
+};
+
@@ -0,0 +1,127 @@
+
+var buster = require('buster');
+var uuid = require('uuid-v4');
+var passreset = require('../../lib');
+var io = require('../io-setup');
+var mockhttp = require('../mock-http');
+
+buster.testCase('Request Password Reset', {
+ 'resetPassword() should return a function': function() {
+ buster.assert.equals(typeof passreset.resetPassword(), 'function', 'without config');
+ buster.assert.equals(typeof passreset.resetPassword({ }), 'function', 'with empty config');
+ buster.assert.equals(typeof passreset.resetPassword({ foo: 'bar' }), 'function', 'with invalid config');
+ buster.assert.equals(typeof passreset.resetPassword({ next: true }), 'function', 'with valid config');
+ },
+
+ 'simple resetPassword() responses': {
+ setUp: function() {
+ this.routeFunction = passreset.resetPassword();
+ this.response = new mockhttp.Response();
+ },
+
+ 'no request body should result in 400 error': testMissingParams(),
+
+ 'missing token should result in 400 error': testMissingParams(
+ { password: 'goodPassword', confirm: 'goodPassword' }),
+
+ 'missing password should result in 400 error': testMissingParams(
+ { token: uuid(), confirm: 'goodPassword' }),
+
+ 'missing confirm should result in 400 error': testMissingParams(
+ { token: uuid(), password: 'goodPassword' }),
+
+ 'invalid token should result in 401 error': function(done) {
+ var req = new mockhttp.Request({
+ token: uuid(),
+ password: 'goodPassword',
+ confirm: 'goodPassword'
+ });
+ this.response.on('respond', function(res) {
+ buster.assert.equals(res.status, 401);
+ var response = JSON.parse(res.response);
+ buster.assert.equals(response.error.message, 'Request token is invalid');
+ done();
+ });
+ this.routeFunction(req, this.response, function() {
+ buster.assert(false, '`next` should not be called in the case of an error');
+ done();
+ });
+ },
+
+ 'tests with valid request token': {
+ setUp: function() {
+ this.token = passreset._storage.create(567);
+ },
+
+ 'unmatching passwords should result in a 400 error': function(done) {
+ var req = new mockhttp.Request({
+ token: this.token,
+ password: 'goodPassword1',
+ confirm: 'goodPassword2'
+ });
+ this.response.on('respond', function(res) {
+ buster.assert.equals(res.status, 400);
+ var response = JSON.parse(res.response);
+ buster.assert.equals(response.error.message, 'Password and confirmation do not match');
+ done();
+ });
+ this.routeFunction(req, this.response, function() {
+ buster.assert(false, '`next` should not be called in the case of an error');
+ done();
+ });
+ },
+
+ 'invalid password should result in a 400 error': function(done) {
+ var req = new mockhttp.Request({
+ token: this.token,
+ password: 'short',
+ confirm: 'short'
+ });
+ this.response.on('respond', function(res) {
+ buster.assert.equals(res.status, 400);
+ var response = JSON.parse(res.response);
+ buster.assert.equals(response.error.message, 'Password is too short');
+ done();
+ });
+ this.routeFunction(req, this.response, function() {
+ buster.assert(false, '`next` should not be called in the case of an error');
+ done();
+ });
+ },
+
+ 'valid password and token should result in a 200 OK': function(done) {
+ var req = new mockhttp.Request({
+ token: this.token,
+ password: 'goodPassword',
+ confirm: 'goodPassword'
+ });
+ this.response.on('respond', function(res) {
+ buster.assert.equals(res.status, 200);
+ buster.assert.equals(res.response, '');
+ done();
+ });
+ this.routeFunction(req, this.response, function() {
+ buster.assert(false, '`next` should not be called if opts.next is false/null');
+ done();
+ });
+ }
+ }
+ }
+});
+
+function testMissingParams(body) {
+ return function(done) {
+ var req = new mockhttp.Request(body);
+ this.response.on('respond', function(res) {
+ buster.assert.equals(res.status, 400);
+ var response = JSON.parse(res.response);
+ buster.assert.equals(response.error.message, 'Cannot attempt reset with missing params');
+ done();
+ });
+ this.routeFunction(req, this.response, function() {
+ buster.assert(false, '`next` should not be called in the case of an error');
+ done();
+ });
+ };
+}
+
Oops, something went wrong.

0 comments on commit 995b718

Please sign in to comment.