Open mousetree opened 10 years ago
FYI, manually updated my project structure (including auth etc) to reflect that of https://github.com/DaftMonk/fullstack-demo (was using the previous version of the generator)
Disclaimer: I'm very very new to unit testing.
That being said, this is what I would do to test server/auth/local/index.js
:
local.spec.js
var should = require('should');
var app = require('../../app');
var User = require('../../api/user/user.model');
var jwt = require('jsonwebtoken');
var config = require('../../config/environment');
var request = require('supertest');
describe('Local Auth API:', function() {
// Clear users before testing
before(function() {
return User.remove().exec();
});
// Clear users after testing
after(function() {
return User.remove().exec();
});
describe('POST /auth/local', function() {
var user;
before(function(done) {
user = new User({
name: 'Fake User',
email: 'test@test.com',
password: 'password'
});
user.save(function(err) {
if (err) return done(err);
done();
});
});
it('should respond with JWT when authenticated', function(done) {
request(app)
.post('/auth/local')
.send({
email: 'test@test.com',
password: 'password'
})
.expect(200)
.expect('Content-Type', /json/)
.end(function(err, res) {
if (err) return done(err);
var token = res.body.token;
jwt.verify(token, config.secrets.session, function(err, session) {
if (err) return done(err);
session._id.should.equal(user._id.toString());
done();
});
});
});
it('should respond with 401 and a "message" when not authenticated', function(done) {
request(app)
.post('/auth/local')
.send({
email: 'test@test.com',
password: 'bad-password'
})
.expect(401)
.expect('Content-Type', /json/)
.end(function(err, res) {
res.body.message.should.be.type('string');
done();
});
});
});
});
I know thats kind of heavy on the includes and unit test are supposed to test a very specific piece of an application, but I'm not sure how (without creating mocks) you would be able to test a local auth login strategy without them.
I don't think that the authenticate test is what the OP ask here, but how to test the routes protected by the auth middleware
I see, thanks @Apercu.
Thanks @kingcody, but yeah as @Apercu said, need to test the protected routes. Any suggestions guys?
I've also struggle quite a bit with this a few days ago and really interested in that, you could have a look to some of these links :
http://stackoverflow.com/questions/14001183/how-to-authenticate-supertest-requests-with-passport http://stackoverflow.com/questions/20640774/how-to-authenticate-supertest-requests-with-passport-facebook-strategy http://jaketrent.com/post/authenticated-supertest-tests/
I've try almost everything but I may have miss something
Thanks @Apercu, I had a look at those yesterday but didn't manage to get them to work. I think i'll try again though. Essentially, as far as I can tell, in the 'before' i'll need to create the user, then authenticate it, and then hopefully superagent will remember that session so I can make requests on the protected routes...?
Yeah that's what we should do I think. There's also a possibility of appending the auth result token to each of the query but I can't get that working either
Perhaps this helps? server/api/user/user.spec.js
var app = require('../../app');
var User = require('./user.model');
var request = require('supertest');
describe('User API:', function() {
var user;
// Clear users before testing
before(function(done) {
User.remove(function() {
user = new User({
name: 'Fake User',
email: 'test@test.com',
password: 'password'
});
user.save(function(err) {
if (err) return done(err);
done();
});
});
});
// Clear users after testing
after(function() {
return User.remove().exec();
});
describe('GET /api/users/me', function() {
var token;
before(function(done) {
request(app)
.post('/auth/local')
.send({
email: 'test@test.com',
password: 'password'
})
.expect(200)
.expect('Content-Type', /json/)
.end(function(err, res) {
token = res.body.token;
done();
});
});
it('should respond with a user profile when authenticated', function(done) {
request(app)
.get('/api/users/me')
.set('authorization', 'Bearer ' + token)
.expect(200)
.expect('Content-Type', /json/)
.end(function(err, res) {
res.body._id.should.equal(user._id.toString());
done();
});
});
it('should respond with a 401 when not authenticated', function(done) {
request(app)
.get('/api/users/me')
.expect(401)
.end(done);
});
});
});
@kingcody: thanks, that's excellent. Seems to work perfectly! How would you go about refactoring that so that the user create, remove and authentication can be used in multiple specs? All my tests across the application will likely need to do those steps...
You're a star!
Other than copy that setup to the other tests, I don't have a clean solution off the top of my head. If one comes to me, I'll be sure to fill you in. Glad I could help :)
@kingcody, you should win a Nobel Prize for that!
Nevertheless, mine still doesn't work, but it may be a different problem.
1) PUT /api/users/:id/contactInfo should return success if new password fields match:
Uncaught TypeError: Cannot read property 'length' of undefined
at model.UserSchema.path.validate.self (/home/lsiden/projects/cid/farmersmarket/app/server/api/user/user.model.js:69:17)
at /home/lsiden/projects/cid/farmersmarket/app/node_modules/mongoose/lib/schematype.js:627:28
at Array.forEach (native)
at SchemaString.SchemaType.doValidate (/home/lsiden/projects/cid/farmersmarket/app/node_modules/mongoose/lib/schematype.js:614:19)
at /home/lsiden/projects/cid/farmersmarket/app/node_modules/mongoose/lib/document.js:968:9
at process._tickDomainCallback (node.js:463:13)
And the failing code:
// Validate empty email
UserSchema
.path('email')
.validate(function(email) {
if (authTypes.indexOf(this.provider) !== -1) return true;
return email.length;
}, 'Email cannot be blank');
Hint: email is undefined.
I'm new to the MEAN stack. A friend got me to try out angular-fullstack. nodejs and Angular both make claims about ease of testing, but not in this particular framework!
Hey guys, this is what I've been using:
I have a little auth helper for the tests that creates the users, authorizes them and then returns the users with their auth token.
auth.spec.helper.js
var supertest = require('supertest');
var app = require('../app');
var agent = supertest.agent(app);
var User = require('../api/user/user.model');
var async = require('async');
var _ = require('lodash');
// creates all users in mock and authenticates them
exports.initUsers = function(callback){
var users = require('./auth.mock');
User.remove({}, function(){
async.mapSeries(users, function(user, cb) {
initUser(user, cb);
}, function(err, results){
if (!err) {
var x = {};
_(results).each(function(result){ x[result.role] = result; });
callback(x);
}
});
});
};
// creates a single user and authenticates it
function initUser(fixture, cb){
User.create(fixture, function(err, user){
if (err){ cb(err); }
fixture.id = user._id;
agent
.post('/auth/local')
.send({username: fixture.username, password: fixture.password})
.end(function(err, result){
fixture.token = result.body.token;
cb(null, fixture);
});
});
}
// deletes all users
exports.clearUsers = function(callback){
User.remove({},function(){
if (callback) { callback(); }
});
};
auth.mock.js
module.exports = [{
provider: 'local',
name: 'Test User',
username: 't1',
email: 'test@test.com',
password: 'test',
role: 'user'
},
{
provider: 'local',
role: 'admin',
name: 'Admin',
username: 't2',
email: 'admin@admin.com',
password: 'admin'
}
];
With the above code and the mocks, the helper would return an object containing two users, in the format users['user']
and users['admin']
. Each of which would contain all the usual user attributes from the db as well an attribute token
that contains the authorization token from passport.
Then, in all my tests that require auth, I include the helper as such:
kyc.spec.js
var Auth = require('../../auth/auth.spec.helper');
var KYC = require('./kyc.model');
var request = require('supertest');
var app = require('../../app');
describe ('KYC API',function(){
var users; // stores the authorized users: user and admin
// delete existing objects and initialise users
before(function(done) {
KYC.remove().exec().then(function() {
Auth.initUsers(function(results){
users = results;
return done();
});
});
});
afterEach(function(done) { KYC.remove({}, done); });
after(function(done) { Auth.clearUsers(done); });
describe('GET /api/kyc', function() {
it('should respond with data when authenticated', function(done) {
request(app)
.get('/api/kyc')
.set('authorization', 'Bearer ' + users['user'].token)
.expect(200)
.expect('Content-Type', /json/)
.end(function(err, res) {
if (err) return done(err);
res.body.should.be.instanceof(Array);
return done();
});
});
it('should respond with forbidden when not authenticated', function(done) {
request(app)
.get('/api/kyc')
.expect(401)
.end(function(err, res) {
if (err) return done(err);
return done();
});
});
});
});
It seems to be working quite nicely, just need to include the helper and then call the initUsers method and save the users it returns. You then have users in the db that are authorized and can then use those tokens to make queries etc.
The above is inspired by @kingcody 's solution above, just refactored into its own module so it can be re-used across multiple tests.
I've done this a bit different,
var auth = require('../../auth/auth.service');
describe('/api/roles', function () {
var user;
var role;
beforeEach(function (done) {
clearModels()
.then(function () {
return createModels();
})
.spread(function (newRole, newUser) {
role = newRole;
user = newUser;
done();
})
.catch(done);
});
it('should respond with JSON array', function (done) {
request(app)
.get('/api/roles')
.set('Authorization', 'Bearer ' + auth.signToken(user._id))
.expect(200)
.expect('Content-Type', /json/)
.end(function (err, res) {
if (err) return done(err);
res.body.should.be.an('array');
res.body.length.should.equal(1);
done();
});
});
});
This way, while testing the auth spaerately, I could save on the ,multiple auth requests for each test
FWIW, I wrote a little wrapper https://github.com/lsiden/cid-farmersmarket/blob/master/server/api/helpers.service.js#L30 to help with this. On Tue Dec 23 2014 at 1:18:36 AM noamokman notifications@github.com wrote:
I've done this a bit different,
var auth = require('../../auth/auth.service');
describe('/api/roles', function () { var user; var role;
beforeEach(function (done) { clearModels() .then(function () { return createModels(); }) .spread(function (newRole, newUser) { role = newRole; user = newUser; done(); }) .catch(done); });
it('should respond with JSON array', function (done) { request(app) .get('/api/roles') .set('Authorization', 'Bearer ' + auth.signToken(user._id)) .expect(200) .expect('Content-Type', /json/) .end(function (err, res) { if (err) return done(err); res.body.should.be.an('array'); res.body.length.should.equal(1); done(); }); }); });
This way, while testing the auth spaerately, I could save on the ,multiple auth requests for each test
— Reply to this email directly or view it on GitHub https://github.com/DaftMonk/generator-angular-fullstack/issues/494#issuecomment-67925008 .
What I don't understand is why the server authentication doesn't also read the token from the session cookie? Then you can browse the API in the browser without having to add Authorization
headers. If there's a header, use that, if there is a cookie, use that.
The cookie is being added for most of the OAuth strategies anyway, so why not for all?
As you can see, not doing that results in a headaches and extra code on the API consumer side, without security improvements (AFAIK)?
@chuyik you're correct regarding CSRF attacks and non http only cookies. However the token cookie is not http only as it is read by auth.service.js
to log the user in automatically.
@chuyik Not sure if that is a problem, since if you allow CORS/JSONP you presumably are happy with the token being used by other sites. Anyway, so maybe that should be configurable then...
@chuyik I was under the impression that CORS prevents exactly this? Can you point me to a proof-of-concept implementation or explanation?
I've adapted @chuyik's code to allow the test to specify a role for the user. Useful for testing endpoints which have different permissions
@jeshuamaxey PR?
I'm still ironing out some teething problems to do with tests running before the auth request returns a token. Once I have a fully working solution, I'll set up a PR :)
EDIT: I was running grunt serve
and grunt watch:mochaTest
simultaneously. They appear to have been interfering as they reloaded on save. Lesson learnt!
FYI I've updated my gists with the implementation I've settled on for testing endpoints which require user authentification. They can be viewed here: https://gist.github.com/jeshuamaxey/e88a21f802445bf05e18
Next step is to develop this into a PR. With any luck I'll push something tonight, although will more likely be sometime mid next week.
Thanks to everyone contributing on this issue it was really helpful.
Hi, I'm not sure if this is the right place to post this question but I'm not having any luck elsewhere. I'm new to testing and I'm not having any luck on SO etc. as they lack the context of whats already provided by this generator.
Most of my routes on the API are protected. What I'm trying to do is write test specs that:
What I'm having trouble with, is how to write the tests such that:
Please help, i'm pulling my hair out!