Hello, I am writing my test for the auth side of my application and have run into a bit of pickle on how to handle testing the following case in which the user's JWT is expired:
_generateToken()_
// CREATE JWT TOKEN FOR USER
export const generateToken = user => jwt.sign({
sub: user.id }, secret, { expiresIn: '1h' }
);
_checkToken()_
// CHECK JWT TOKEN STATUS.
export const checkToken = (req, res) => {
const { token } = req.body;
// Does the user possess a JWT?
if (!token) {
return res.status(401).json({ success: false, message: 'Must have JWT.' });
}
// Verify the status of user's JWT.
jwt.verify(token.replace(/^JWT\s/, ''), secret, (err, decoded) => {
if (err) {
// If status = expired, prompt user to login again.
if (err.name === 'TokenExpiredError') {
return res.status(422).json({
success: false,
expireTime: true,
message: 'JWT has expired. Please login again, this is for your security!'
});
}
// If this can't be done return error message.
return res.status(422).json({
success: false,
message: 'JWT Verification Issue.'
});
}
// Find user in db and generate a new JWT.
User.findById(decoded.sub)
.then(user => res.status(201).json({
success: true,
message: 'JWT Refreshed.',
token: `JWT ${generateToken(user)}`,
user: setUserInfo(user)
}))
// If this can't be done return error message.
.catch(error => res.status(401).json({
success: false,
message: 'JWT Refresh Issue.',
error
}));
});
};
I am using supertest, mocha, & chai as my testing packages and I just work from the first test with the same user's data (clearing the database after all testing has finished).
_test_
it('expects a JWT token to return on signup', (done) => {
request(server)
.post('/api/v1/signup')
.send({ email: '[email protected]', password: '123' })
.then(res => {
const { message, success, user } = res.body;
token = res.body.token;
expect(res.statusCode).to.equal(201);
expect(success).to.equal(true);
expect(message).to.equal('Successfully Registerd');
expect(token).to.be.a('string');
expect(token).to.contain('JWT');
expect(user).to.be.a('object');
done();
})
.catch(err => done(err));
});
it('expects a 422 if JWT is expired', (done) => {
request(server)
.post('/api/v1/checkToken')
.send({ token })
.then(res => {
const { message, success } = res.body;
expect(res.statusCode).to.equal(422);
expect(success).to.equal(false);
expect(message).to.equal('JWT has expired. Please login again, this is for your security!');
done();
})
.catch((err) => done(err));
});
At the moment I've got all my tests working except the case in which I would return a 422 for a expired token. I'm trying to figure out how to set this up. Do I need to seed a new user and manipulate _generateToken()_ to have the JWT expire instantly? Just kind of tripped up on how to achieve something like this. Any pointers or example code would be greatly appreciated, and Happy New Years!
Hi @rockchalkwushock ! Happy New Year! I can think in two options:
a) Extract your '1h' to a configuration variable, then you can modify it in that test to some shorter value and use setTimeout to run your asserts.
b) If you can use sinon, it has useFakeTimers(). You can check some examples in our repo.
@ziluvatar thanks hope you had a great New Years as well!
I never would have considered setting up and env var for the time. That is a very nice trick 馃憤
I have never worked with sinon yet and I'm almost finished with this project so switching up testing suites at the moment is not on my radar of things to-do.
I set up an env var for the production _expiration time_ value and then just ran a simple ternary checking the NODE_ENV:
import jwt from 'jsonwebtoken';
import '../config/envConfig';
const secret = process.env.JWT_SECRET;
const time = process.env.NODE_ENV === 'production' ?
process.env.JWT_TOKEN_EXPIRE_TIME :
'1s';
// CREATE JWT TOKEN FOR USER
export const generateToken = user => jwt.sign({
sub: user.id }, secret, { expiresIn: time }
);
Where I am getting lost however is using setTimeout() in my tests. Seems like no matter what I'm trying to do in the test the method keeps breaking the test or not carrying out the desired effect. I set the time to 3000 and the expiration time for a NODE_ENV !== production is 1000.
My thought process is that I would want to apply setTimeout() prior to sending the token for _checking_ it's validity; but I'm not understanding how to accomplish this without the test breaking.
it('expects a 401 if decode does not return a valid user', (done) => {
// either need to apply setTimeout() before the server connection which makes no sense to do.
request(server)
.post('/api/v1/checkToken')
// or before sending the token so it becomes invalidated.
// however placing the method here breaks the test.
.send({ token })
.then(res => {
const { message, success } = res.body;
expect(res.statusCode).to.equal(401);
expect(success).to.equal(false);
expect(message).to.equal('JWT Refresh Issue.');
done();
})
.catch((err) => done(err));
});
I've been looking for resources on how to integrate this into mocha/chai testing; but not having much luck at all. Any pointers to get me headed I the right direction?
The use of the fake timers of sinon is pretty straightfoward, you can check this repo, it is only used for that.
Anyway for the other option you are having problems with: In you it part you already have the token which was generated, let's say 1ms ago. You need to test if your server verify correctly when the token has expired, so you need to send an expired token.
it('expects a 401 if decode does not return a valid user', (done) => {
// you could add here this.timeout(2000); if needed
setTimeout(function() {
request(server)
.post('/api/v1/checkToken')
// or before sending the token so it becomes invalidated.
// however placing the method here breaks the test.
.send({ token })
.then(res => {
const { message, success } = res.body;
expect(res.statusCode).to.equal(401);
expect(success).to.equal(false);
expect(message).to.equal('JWT Refresh Issue.');
done();
})
.catch((err) => done(err));
}, 1500);
});
With this basically you are delaying the whole test, that should work.
Worked out perfectly thanks @ziluvatar
Most helpful comment
The use of the fake timers of
sinonis pretty straightfoward, you can check this repo, it is only used for that.Anyway for the other option you are having problems with: In you
itpart you already have the token which was generated, let's say 1ms ago. You need to test if your server verify correctly when the token has expired, so you need to send an expired token.With this basically you are delaying the whole test, that should work.