2

This is my first project using passport and I am new to Node in general. I am having trouble getting an endpoint recognized as authorized when running integration tests using Chai. The endpoint is protected by token authentication using the passport and jsonwebtoken libraries. Here are the other files imported to the tests;

const chai = require('chai');
const chaiHttp = require('chai-http');
const mongoose = require('mongoose');
const should = chai.should();
const { Session } = require('../models/practiceSession' );
const {User} = require('../models/user');
const { app, runServer, closeServer } = require('../app');
const { TEST_DATABASE_URL } = require('../config/mainConfig');
const {secret} = require('../config/mainConfig');
const jwt = require('jsonwebtoken');
chai.use( chaiHttp );

Here is the test for the endpoint;

//test the session endpoints
       it( 'should return a session with proper request', function(){
          const user = new User({
              name: 'random2',
              email: 'random2@random.com',
              password: 'Password2'
            });


          user.save( (err) => {
            if (err){
              console.log( err.message);
              }
          });

          const token = jwt.sign({
            id: user._id,
            userName: user.name,
            level: 0
            }, secret, {
            expiresIn: 60 * 60
            });

          console.log( "user id: ", user._id);
          console.log( 'token is: ', token );

          const practiceRequest = {
            operation: "+",
            number: "10",  ///this will possible need to be sent as a number
            min: "1",
            max: "200"
          };

          return chai.request(app)
            .post('/api/session')
            .set('Authorization', 'Bearer ' + token)
            .send( practiceRequest )
            .then( (res) => {
              res.should.have.status(201);
            })
          });

    });

The two console.log statements confirm that the user is being created and has an _id property, and that a valid token is being created (this was confirmed on jwt.io).

This is the code for the endpoint;

router.route("/session")
    .post(passport.authenticate('jwt', { session: false }), jsonParser, ( req, res ) => {
        console.log( req );
        let practiceSession = [];  
        for ( let i = 0; i < req.body.number; i++ ){
            let firstTerm = generateTerm( req.body.min, req.body.max );
            let secondTerm = generateTerm( req.body.min, req.body.max );
            const problem = {  
                operator: req.body.operation,
                firstTerm,
                secondTerm,
                problem: `${ firstTerm } ${ req.body.operation } ${ secondTerm }`,
                correctResponse: generateCorrectResponse( firstTerm, secondTerm, req.body.operation )
            }; 
            practiceSession.push( problem );
        }

    // save session into db sessions collection for use in the training-logic.js
        Session
        .create( {
            userId: req.user._id, 
            problems: practiceSession  
        } )
        .then(
            session => res.status( 201 ).json( session) )
        .catch( err => {
            console.error( err );
            res.status( 500 ).json( { message: 'Internal Server Error' } );
        });
    } );

The test of this endpoint fails with a 401 response and a simple Error: Unauthorized That is not a lot of information and I don't know where to continue trouble shooting. My understanding is that this means the initial passport.authenticate at the endpoint is not recognizing the token as valid, even though it has been confirmed as valid on the jwt.io site. Is there additional information I should be including when generating the token? If so how?

I have looked at several other examples using passport; one, two, three but I did not see how to apply any of these insights to my implementation.

Any help with this would be greatly appreciated. Thank you for your time.

UPDATE:

Based on the fact that a valid token is going up to the endpoint but it is still not being accepted I tried building a promise chain with an initial call to the user authentication endpoint and then used the token derived from there to test the session generation endpoint. This was the effort;

     it( 'should return a session with proper request', function(){

       let token; 
       return chai.request(app)
          .post('/api/user/authenticate')
          .send( {
           email: 'random@random.com',
           password: 'password2'
           } )
          .then( (res) => {
            res.should.have.status(200);
            res.should.be.json;
            console.log( res.body );
            token = res.body.token; 
            return token; 
          })
          .catch( (err) => {
            console.log( 'failed at user authentication' );
          })
          .then( ( err, data ) => {
            console.log( 'token value is ', token );
            chai.request( app )
            .post( '/api/session' )
            .set( 'Authorization', `${ token }` )
            .send( {
                operation: "+",
                number: "10",  
                min: "1",
                max: "200"
             })
            .then( res => { 
              console.log( 'second response is ', res );
              res.should.have.status(201);
             } )
            .catch( err => {
              console.log( 'second promise ', err.message );
            })
          })
        });

It is still rejected as Unauthorized. Something is missing from the request being sent to the session endpoint from the test. The console.log( req ) in the session endpoint confirms that it is receiving an object that has a great deal of information added to it, compared to what is being sent from the promise chain in the test. I had to use the token variable in the outer scope to pass the value between the chai.request(app) commands. This is not the same data flow present in the working app. I am not sure how to test this; my understanding is that what I have tried so far does not give the endpoint enough data to validate the request.

Any help would be greatly appreciated. Thank you for your time.

Cameron
  • 135
  • 1
  • 13
  • [Debug](https://medium.com/the-node-js-collection/debugging-node-js-with-google-chrome-4965b5f910f4) the authenticate call. – James Oct 27 '17 at 17:48
  • James, thanks for this reference, I had never used the Chrome Debugger with the server side of Node. In the normal functioning of the app I set a break point in the sessionRouter endpoint (above) and examined the request object. In the Header there was a property for ```authorization: "Bearer eyJhbGciOiJIUzI1NiIsIn..."``` My understanding is that this is what is being sent in the test. This tool provided additional information, however I think it confirmed what I had been doing. I am still not sure why the test fails as being "Unauthorized". Thanks again for your time. – Cameron Oct 27 '17 at 20:04
  • So sounds like token header is going up (which is good). Narrows your problem down to your JWT being invalid :) – James Oct 27 '17 at 21:03

1 Answers1

0

SOLUTION

To follow up, a friend helped with this and gave me a solution that passed additional information back to the endpoint when building the token. The final working model had a beforeEach() function that adds a dummy user to the test database, and then assigns this user to a testUser variable in the outer scope, so it is accessible in the later test. In the actual test of the endpoint this testUser variable is mapped to a full user schema and is used to build the token and test the endpoint. It works well since the endpoint has all the properties needed. Here is the code;

describe( 'End-point for practice session resources', function() {

    let testUser;
    let modelSession; 

    before( function() {
      return runServer( TEST_DATABASE_URL );
    });

    beforeEach( function(done){
      addUser()
      .then( user => {
        testUser = user;
        done(); 
      });
    });

and the test;

 it( 'should generate a session with the proper request (POST)', function(){

          const token = jwt.sign({
            id: testUser._id
          }, secret, { expiresIn: 60 * 60 }); 

          return chai.request( app )
              .post( '/api/session' )
              .set( 'Authorization', `Bearer ${ token }` )
              .send( {
                  operation: "+",
                  number: "10",  
                  min: "1",
                  max: "200"
              })
              .then( res => { 
                res.should.have.status(201);
              } )
            })
          });

Hope this helps someone else down the road.

Cameron
  • 135
  • 1
  • 13
  • Think just to clarify, and it's actually pretty apparent by looking back at your code, your issue was you had a race condition in your test. You create your user and then proceed to send the request *before* you can be sure the user has been saved. I wrote a library that I think would really help simplify your testing, have a look at [fluentify](https://npmjs.com/fluentifyjs), it was designed specifically to solve problems like this. Let me know if you have any questions about it :) – James Nov 02 '17 at 23:50
  • Awesome! Thanks James! – Cameron Nov 03 '17 at 19:53
  • no worries, in my old job we found ourselves with lots of integration tests that required heavy setup and as much as Mocha is great, it's very easy to fall into callback hell or get tripped up with scenarios like you hit, it's now an integral part of the automated testing process and used to run thousands of tests every day. There are simple examples in the docs but if you're struggling with it I'd be happy to "fluentify" the code in your question in a gist as a guide. – James Nov 03 '17 at 20:05