I'm new to passport and express and trying to get a better idea of how everything works together by creating unit tests for all of my code. I have been having good success but today ran into an issue I don't quite understand...
I'm attempting to unit test the following exported function on session.js:
'use strict';
var mongoose = require('mongoose'),
passport = require('passport');
exports.login = function (req, res, next) {
passport.authenticate('local', function(err, user, info) {
var error = err || info;
if (error) return res.json(401, error);
req.logIn(user, function(err) {
if (err) return res.send(err);
res.json(req.user.userInfo);
});
})(req, res, next);
};
I seem to be having trouble with the second to last line (req, res, next) and how/when it gets called. I also have never seen this type of call in Javascript where the last line is just a set of parameters.
In order to test I'm using rewire, sinon, and mocha. In my test I'm using rewire to change the passport dependency to a mock object that I control.
My test seems to be working as expected as evidenced by where it is failing and what instanbul is telling me is covered but it alwasy throws: TypeError: undefined is not a function.
If I comment out the (req, res, next) line in session.js then my test actually runs successfully.
My test session.unit.js:
'use strict';
var rewire = require('rewire'),
should = require('should'),
sinon = require('sinon'),
sessionController = rewire('../../../../lib/controllers/session'),
passportMock = {};
describe('Session Controller', function() {
describe('#login', function(){
beforeEach(function(){
sessionController.__set__({
'passport': passportMock
});
});
it('should return 401 status and error if passport authentication returns error', function(){
//given
var res = {
json: sinon.spy(),
send: sinon.spy()
},
req = {
logIn: sinon.spy()
},
next = sinon.stub();
passportMock.authenticate = sinon.stub().callsArgWith(1, 'ERROR', null, null);
//when
sessionController.login(req, res, next);
//then
res.json.calledWith(401,'ERROR').should.be.true;
});
});
});
My questions are:
What is the last line (req, res, next) used for and when is it called? (I suspect middleware)
Is there a way I can change my test to get it to pass without the undefined error?
Thanks very much for reading and any help you can provide!!
** EDIT: **
I think I left out a critical piece of information... This session.login function is called from my routes.js file so I believe the last line is needed for middleware to continue. I'm still confused as to how to allow this to continue processing in the unit testing though.
routes.js snippet:
app.route('/api/session')
.post(session.login)
.delete(session.logout);
** EDIT2: **
Added the top part of session.js for clarity on how passport is initially declared with require. In the unit test I am replacing passport via rewire with my mock (stub) object so that I can control what passport returns.
I'm not sure if this will help, but this is generally how I stub passport.authenticate when used in the same way you have.
var stub = sinon.stub(passport, 'authenticate').returns(function() {});
stub.yields(new Error('fails here'));
// add your tests here
stub.restore();
The })(req, res, next); towards the end of your session.js is unnecessary because of how scoping rules work in JavaScript and because passport.authenticate does not return a function (it probably returns nothing which by default returns undefined), so that is the reason for the error.
Just change the })(req, res, next); from the end of session.js to just });.
Related
I have short question.
router.get('/', function(req, res, next) {
throw new Error('123')
res.send('respond with a resource');
});
When I call this snippet on express project.
It throw error what I expected.
But
router.get('/', function(req, res, next) {
setTimeout(() => {
throw new Error('123')
}, 1000)
res.send('respond with a resource');
});
With this code express process killed with
Command failed with exit code 1.
this message.
What keywords should I study is?
What am I missing?
Thank you.
1.The default error handler
http://expressjs.com/en/guide/error-handling.html
2.callback error handler
try/catch issues with nested functons in javascript
This question already has answers here:
NodeJS Express = Catch the sent status code in the response
(2 answers)
Closed 2 years ago.
I have a fairly straight forward logging middleware function in an Express JS app:
app.use(function (req, res, next) {
const line = `${req.method} ${req.originalUrl} ${res.statusCode}`
console.log(line)
next()
})
And I have this route:
this.app.use('/404', function (req, res) {
res.sendStatus(404)
})
Which logs the following:
GET /404 200
With other routes it seems to always return 200.
How can I fix this so that it accurately logs status codes without changing routes?
Edit
My goal here was something quick to tell me if a route was hit and whether or not it succeeded. I don't want to have to edit all my routes to be compatible with it since I'll eventually replace it with an actual logging solution.
Depending of : https://expressjs.com/guide/error-handling.html
app.use('/404', function (req, res) {
console.error(err.stack);
res.status(404).send('not found!');
});
Also you can use http-errors module:
var createError = require('http-errors');
app.use(function(req, res, next) {
next(createError(404));
});
This still doesn't work for all routes but works for more of them:
this.app.use(async function (req, res, next) {
await next()
const line = `${req.method} ${req.originalUrl} ${res.statusCode}`
console.log(line)
})
Don't know your entire code, but this.app is not necessary. Directly use app.use.
Debug, while hitting, you request do not come within app.use('/404'), It might be serving path mentioned above it like app.use('/:variable'), so may be variable == 404.
Mention this type of static path topmost.
I have an express sub app using http-errors module. When I pass new Forbidden() to the next() callback it disappears into the ether and doesn't callback. If I pass new Error() or { message: 'Forbidden' } it triggers the sub app error handler. Is this expected behaviour?
I've created my own Error objects and they all work. I see http-errors uses the inherits module, which works for me. Does the error handler check for anything on the error parameter?
I've used http-errors for years and not noticed this problem before.
const { Forbidden } = require('http-errors')
const express = require('express')
const app = express()
const subapp = express()
subapp.get((req, res, next) => {
next(new Forbidden()) // doesn't work
next(new Error()) // works
next({ message: 'Forbidden' }) // works
})
subapp.use((err, req, res, next) => {
// only called if new Error() or { message: 'Forbidden' }
})
app.use('/somepath', subapp)
app.use((err, req, res, next) => {
// not called
})
Edit:
I omitted that I was using swagger in the question above. Swagger was catching the error but not handling it appropriately; it was setting the correct headers but not sending a complete response. It was therefore missing my error middleware and passing on to the next non-error middleware.
// return response for http-errors error
subapp.use((req, res, next) => {
if (res.headersSent) {
return res.end()
}
next()
})
To answer the direct question, not it doesn't. We can test this with a simpler case shown in this code (tested).
const app = require('express')();
app.get('/test', (req, res, next) => {
const nonErrObj = {
hello: 'world'
};
return next(nonErrObj);
});
app.use((err, req, res, next) => {
console.log('Got an error');
console.table(err);
res.sendStatus(500);
});
app.listen(3000, () => {
console.log('listening on 3000');
});
By then running curl localhost:3000/test in another terminal we get the output:
listening on 3000
Got an error
┌─────────┬─────────┐
│ (index) │ Values │
├─────────┼─────────┤
│ hello │ 'world' │
└─────────┴─────────┘
This console.table is being output by our error handler, and the object we're passing to next is just a standard JS object. So the object passed to "next" can be anything and it will trigger the error handling code.
Now lets try and solve your issue. I have a hunch it's to do with your nested application which is good use of express but can get a bit confusing sometimes. I've created another test app using your code which shows the following. This code has only one global error handler.
const express = require('express');
const app = express();
const subapp = express();
// Create a top level route to test
app.get('/test', (req, res, next) => {
const nonErrObj = {
hello: 'world'
};
return next(nonErrObj);
});
// Create a sub express app to test
subapp.use('/test2', (req, res, next) => {
const nonErrObj = {
hello: 'world'
};
return next(nonErrObj);
});
// Mount the app, so we can now hit /subapp/test2
app.use('/subapp', subapp);
// A single global error handler for now
app.use((err, req, res, next) => {
console.log('Got an error');
console.table(err);
res.sendStatus(500);
});
app.listen(3000, () => {
console.log('listening on 3000');
});
If we now curl localhost:3000/subapp/test2 and curl localhost:3000/test we get the same response. Our global error handler is called with no problems. Now lets try adding an error handler to the sub app to see what happens.
In this case I just added the following under the /test2 route (not adding the full file for brevity.
subapp.use((err, req, res, next) => {
console.log('Sub app got error');
console.table(err);
res.sendStatus(500);
});
In this instance by doing the same thing, we can see that a request to localhost:3000/subapp/test2 only calls the the sub app error handler. This shows that the errors are being handled properly.
From the above we can see that there aren't any issues with random JS objects being passed through (you can dig through the Express Router code and see this as well). The only reason I can see that the http-errors wouldn't be working properly would be if they are causing a conflict with the error handling code.
Looking at the express router code we can see that it's picking up a few properties from the error object and acting based on that. I would check that your http-errors Forbidden error object isn't accidentally conflicting with one of these use cases. If that's the case, then I'd suggest finding a different error library.
I'm assuming you're also using the npm http-errors library. If that's the case, it looks like you should be providing a message on error creation. You could be getting yourself into a situation where your program is hanging or erroring in some other way because you're not providing a message.
I'm using a module called consign to include several different modules in a directory at once (instead of having a bunch of require statements). Within these modules, I've been setting the mount path for each endpoint at the top of the router file so as not to repeat it several times throughout the file. However, consign passes the same router to each of these (which should normally be fine) and the mount path is actually being overwritten via the use() method if the path is the same in any of the files. I'll try and show this the best way I can...
/routes/api.js
var express = require('express');
var router = express.Router();
var consign = require('consign');
// get all routes inside the api directory and attach them to the api router
// all of these routes should be behind authorization
consign({
cwd: 'routes'
})
.include('api')
.into(router);
module.exports = router;
/routes/api/player.js
module.exports = function (router) {
router.use('/player', router);
router.get('/getInfo', function (req, res, next) {
res.error = false;
res.data = {};
res.message = "Player getInfo API Call - IN DEVELOPMENT";
next();
});
};
/routes/api/profile.js
module.exports = function (router) {
router.use('/profile', router);
router.get('/getInfo', function (req, res, next) {
res.error = false;
res.data = {};
res.message = "Profile getInfo API Call - IN DEVELOPMENT";
next();
});
}
Consign is loading in the modules just fine, but the router.use() method seems to be overwriting the callbacks when the paths are the same (disregarding the base path that is). For instance, both "/player/getInfo" and "/profile/getInfo" work as a call, but are both responding with "/profile/getInfo" data.
BTW - in case you're wondering and in case it's pertinent, I have a small piece of middleware called "formatResponse" that will take the data and format all of the calls in the same way, which is why I have a "next()" instead responding from the function itself. The code for that is below as well.
/middleware/formateResponse.js
module.exports = function(req, res, next) {
res.json({
error: res.error,
data: res.data,
message: res.message
});
}
The way you're doing it right now, there's no scope. The fact that you mounted the router on '/profile' and then added a get statement to the '/getInfo' path doesn't really change the scope the way you think it does. They're both stored to match on '/getInfo', with the last one in winning (regardless of prefix. I bet navigating to http://your-server/getInfo will work too).
You either need to use a different router for each module (and then mount that one on the path root you want) or else be more explicit in the rest of the routes (e.g. call router.get('/profile/getInfo', ...).
I want to pass the environment for Express to a routing module for Express. I want to key off of whether Express is running in development or production mode. To do so, I'm guessing I need to pass app.settings.env somehow to a routing module.
My routing module exports a function for each route. So:
app.get('/search', web.search);
Based upon a previous stackoverflow post, i have tried this:
var web = require('./web')({'mode': app.settings.env});
But node throws an type error (object is not a function).
I'm new to Node and Express. Can I pass a value to an express route and if so, how?
If you web.js looks like this:
module.exports.search = function(req, res, next) {
// ...
};
module.exports.somethingOther = function(req, res, next) {
// ...
};
then by calling
var web = require('./web')({'mode': app.settings.env});
you try to use object (module.exports) as function. Type error here.
You need to convert module.exports to function to pass parameters to it. Like this:
module.exports = function (env) {
return {
// env available here
search: function(req, res, next) {
// ...
},
somethingOther: function(req, res, next) {
// ...
};
};
};