Search code examples
node.jsexpressjasminepassport.jsgulp-jasmine

Testing Express / Passport middleware using Jasmine — passport.authenticate never completes


I'm trying to unit test a simple piece of Express middleware, a cascading athenticator that checks first for a JWT token using a passport-jwt-strategy, and then if that fails, using a passport-openid-strategy. Each of the strategies is already well tested so what I am trying to test is their integration.

The module I am testing looks like this:

"use strict";

let passport = require('passport');
let Strategies = require('./strategies');
let setupDone = false;

// set up passport
let setup = function (app) {
    passport.serializeUser(function (user, done) {
        done(null, user);
    });

    passport.deserializeUser(function (obj, done) {
        done(null, obj);
    });

    passport.use('jwt', Strategies.jwt);
    passport.use('openid', Strategies.openId);
    app.use(passport.initialize());
    app.use(passport.session());
    setupDone = true;
};

let authenticate = function (req, res, next) {
    if (!setupDone) throw new Error('You must have run setup(app) before you can use the middleware');
    console.log(' cascadingAuthentication');
    // first try the token option
    passport.authenticate('jwt', function (jwterr, user, info) {
        console.log(' jwt auth', jwterr, user, info);
        if (jwterr || !user) {
            passport.authenticate('openid', function (oautherr, user, info) {
                if (oautherr || !user) {
                    return next(oautherr);
                } else {
                    next();
                }
            });
        } else {
            req.user = user;
            next();
        }
    });
};

module.exports = {
    setup: setup,
    authenticate: authenticate
}

My Jasmine test looks like this

"use strict";

let CascadingAuthentication = require('../../lib/middleware/cascadingAuthentication');
let TokenUtils = require('../support/tokenUtils');
let email = '[email protected]';

describe('cascadingAuthentication', function () {

    describe('when there is a token in the header', function () {
        let req;
        let res = {};
        let app = {
            use: function (used) { console.log('app.use called with', typeof used); }
        };

        beforeEach(function (done) {
            let token = TokenUtils.makeJWT(email);
            req = {
                app: app,
                header: {
                    Authorization: `Bearer ${token}`
                }
            }
            CascadingAuthentication.setup(app);
            CascadingAuthentication.authenticate(req, res, function () {
                done();
            });
        });

        it('populates req.user', function () {
            expect(req.user).toEqual(jasmine.any(Object));
        });
    });

});

The issue I have is that, when I run the test, I see the first console.log(' cascadingAuthentication') but I never see the second console.log('jwt auth', err, user, info). The code just dies inside passport.authenticate without ever calling the callback, without raising an error, or without providing any kind of feedback at all.

I'm running my tests via gulp using Jasmine.

My questions are: in order,

  1. Can you see anything obvious that I have done that I might have just missed?
  2. Is there anything else I ought to mock out in my req, res, or app that might make this test work?
  3. Is there any way to debug this interactively; stepping through the code under test as it runs, rather than just adding console.log statements (which seems a little 1980s to me).

Solution

  • Digging through passport's source I have worked out there were two problems with my code.

    The first is that passport.authenticate returns a middleware function, it doesn't actually execute that function. So the solution was simply to call the returned function.

    So my authenticate method now looks like:

    let authenticate = function(req, res, next) {
      if (!setupDone) throw new Error('You must have run setup(app) before you can use the middleware');
      // first try the token option
      passport.authenticate('jwt', function(jwterr, user, info) {
        if (jwterr || !user) {
          passport.authenticate('openid', function(autherr, user, info) {
            if (autherr || !user) {
              return next(autherr);
            } else {
              next();
            }
          })(req, res, next);
        } else {
          req.user = user;
          next();
        }
      })(req, res, next);
    };
    

    (The above example is trimmed for use in the question)

    The other issue was in my test I used header instead of headers in my mock req object, and also authorization ought to have had a lower case a.

    With those two fixes the test now passes.