Skip to content

Commit

Permalink
basic test suite built
Browse files Browse the repository at this point in the history
  • Loading branch information
kbjr committed Jun 2, 2012
1 parent c59164c commit 995b718
Show file tree
Hide file tree
Showing 6 changed files with 383 additions and 67 deletions.
44 changes: 30 additions & 14 deletions lib/index.js
Expand Up @@ -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);
Expand All @@ -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);
Expand Down Expand Up @@ -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) {
Expand All @@ -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) {
Expand Down
2 changes: 2 additions & 0 deletions test/buster.js
@@ -1,4 +1,6 @@

require('./io-setup');

exports['Tests'] = {
env: 'node',
rootPath: '../',
Expand Down
68 changes: 68 additions & 0 deletions test/io-setup.js
@@ -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);
});
};
}

48 changes: 48 additions & 0 deletions test/mock-http.js
@@ -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);
};

127 changes: 127 additions & 0 deletions test/tests/request-password-reset.js
@@ -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();
});
};
}

0 comments on commit 995b718

Please sign in to comment.