How do I do a simple route test with this setup? - node.js

EDIT: After a deal of working towards a solution I am convinced this has to do with the way the package.json file compiles a lot of the site on the fly currently. Webpack, and babble are involved. I think the solution will setting up a test server that works with a fully compiled site.
I am working my way through a node course, and I want to stop before I go any further and add testing to it.
ATM I'd just like to be able to test the home route kicks back a 200. With postman it does no problem, but I can't get mocha to test it.
app.js:
const express = require("express");
const session = require("express-session");
const mongoose = require("mongoose");
const MongoStore = require("connect-mongo")(session);
const path = require("path");
const cookieParser = require("cookie-parser");
const bodyParser = require("body-parser");
const passport = require("passport");
const { promisify } = require("es6-promisify");
const flash = require("connect-flash");
const expressValidator = require("express-validator");
const routes = require("./routes/index");
const helpers = require("./helpers");
const errorHandlers = require("./handlers/errorHandlers");
// create our Express app
const app = express();
// view engine setup
app.set("views", path.join(__dirname, "views")); // this is the folder where we keep our pug files
app.set("view engine", "pug"); // we use the engine pug, mustache or EJS work great too
// serves up static files from the public folder. Anything in public/ will just be served up as the file it is
app.use(express.static(path.join(__dirname, "public")));
// Takes the raw requests and turns them into usable properties on req.body
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
// Exposes a bunch of methods for validating data. Used heavily on userController.validateRegister
app.use(expressValidator());
// populates req.cookies with any cookies that came along with the request
app.use(cookieParser());
// Sessions allow us to store data on visitors from request to request
// This keeps users logged in and allows us to send flash messages
app.use(
session({
secret: process.env.SECRET,
key: process.env.KEY,
resave: false,
saveUninitialized: false,
store: new MongoStore({ mongooseConnection: mongoose.connection })
})
);
// // Passport JS is what we use to handle our logins
app.use(passport.initialize());
app.use(passport.session());
// // The flash middleware let's us use req.flash('error', 'Shit!'), which will then pass that message to the next page the user requests
app.use(flash());
// pass variables to our templates + all requests
app.use((req, res, next) => {
res.locals.h = helpers;
res.locals.flashes = req.flash();
res.locals.user = req.user || null;
res.locals.currentPath = req.path;
next();
});
// promisify some callback based APIs
app.use((req, res, next) => {
req.login = promisify(req.login, req);
next();
});
// After allllll that above middleware, we finally handle our own routes!
app.use("/", routes);
// If that above routes didnt work, we 404 them and forward to error handler
app.use(errorHandlers.notFound);
// One of our error handlers will see if these errors are just validation errors
app.use(errorHandlers.flashValidationErrors);
// Otherwise this was a really bad error we didn't expect! Shoot eh
if (app.get("env") === "development") {
/* Development Error Handler - Prints stack trace */
app.use(errorHandlers.developmentErrors);
}
// production error handler
app.use(errorHandlers.productionErrors);
// done! we export it so we can start the site in start.js
module.exports = app;
The application is set up to run routes through a file at routes/ called index.js. That file then calls up the view file...
My test can't seem to get properly routed though.
const expect = require("expect");
const request = require("supertest");
const app = require("./../../app");
describe("Dummy Test", () => {
it("Should return 5", () => {
const result = 2 + 3;
expect(5);
});
});
describe("Get /home", () => {
it("should get home", done => {
request(app)
.get("/home")
.expect(200)
.end(done);
});
});
It always returns a 500. I can make the repo public if a deeper look might help.

Not sure if I'll solve it, but hoping this at least sparks some new debugging ideas for you. I normally use superagent with Jest to test, but this looks like a more or less similar setup.
I did some code comparison to the docs (https://www.npmjs.com/package/supertest).
In this example, there is some error handling on the .end(). Wondering if adding that might help you diagnose?
describe('POST /users', function() {
it('responds with json', function(done) {
request(app)
.post('/users')
.send({name: 'john'})
.set('Accept', 'application/json')
.expect(200)
.end(function(err, res) {
if (err) return done(err);
done();
});
});
});
Also, this example shows the done being added as a comma separated instead of a .end(done) on it's own line. Your current way is also shown, but it's just another way to try.
describe('GET /user', function() {
it('respond with json', function(done) {
request(app)
.get('/user')
.set('Accept', 'application/json')
.expect('Content-Type', /json/)
.expect(200, done);
});
});
If none of that helps, my last thoughts are what is your '/home' route actually returning? I see the routes import in your app file, but cannot see that actual routes for reference. Have you tried additional console.log's / error handling in your /home route to examine the back-end's perspective on what is being sent?

Related

Why can’t I send specific mongoose.js errors to the client when they enter a username already in use?

I am working on implementing custom error handling in a MongoDB MERN application. I’m also using Mongoose and passport-local-mongoose.
When a user registers for a new account, they should get a specific error message in the chrome console saying, ‘username already exists’. I know it is a bad idea to show all the server error messages to the user, but I would like to show a select few.
Links that I have tried:
https://github.com/saintedlama/passport-local-mongoose
Passport-local-mongoose : Authenticate user right after registration
https://www.geeksforgeeks.org/nodejs-authentication-using-passportjs-and-passport-local-mongoose/
Here is the code:
server/controller/auth.js
const { User } = require('../models');
const register = async function (req, res) {
try {
const user = new User({ username: req.body.username });
await user.setPassword(req.body.password);
await user.save();
}
catch (err) {
console.log(`error inside save ${err}`);
res.status(500).send(err);
}
};
const login = async function (req, res) {
//code block under construction
console.log(`login!!!!!`);
};
const logout = function (req, res) {
req.session.destroy();
res.end();
};
exports.login = login;
exports.register = register;
exports.logout = logout;
server/models/User.js
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const passportLocalMongoose = require('passport-local-mongoose');
const userSchema = new Schema({
username: { type: String, unique: true },
password: { type: String}
});
userSchema.plugin(passportLocalMongoose);
const User = mongoose.model('User', userSchema);
module.exports = User;
server/routes/api/auth/index.js
const router = require('express').Router();
const passport = require('../../../config/passport');
const authController = require('../../../controllers/auth');
router.route('/logout').get(authController.logout);
router.route('/register').post(authController.register);
router.use(passport.authenticate('local', {
session: true
}));
// Matches with '/api/auth'
router.route('/login').post(authController.login);
module.exports = router;
server/server.js
const path = require('path');
const express = require('express');
const passport = require('./config/passport');
const mongoose = require('mongoose');
const cors = require('cors');
const session = require('express-session');
const helmet = require('helmet');
const morgan = require('morgan');
const corsOptions = require('./config/cors.js');
const routes = require('./routes');
const { v1: uuidv1 } = require('uuid');
// console.log(uuidv1());
const PORT = process.env.PORT || 3001;
const app = express();
mongoose.connect(process.env.MONGODB_URI || 'mongodb://localhost/puzzlegallery', {
useNewUrlParser: true,
useUnifiedTopology: true,
useCreateIndex: true,
useFindAndModify: false
});
mongoose.set("useCreateIndex", true);
// Define middleware here
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
app.use(helmet({ contentSecurityPolicy: false }));
app.use(session({ secret: 'sassy', resave: false, saveUninitialized: true }));
app.use(passport.initialize());
app.use(passport.session());
app.use(cors(corsOptions));
app.use(morgan('dev'));
app.use(routes);
// for Reactjs ##################
// Serve up static assets (usually on heroku)
if (process.env.NODE_ENV === 'production') {
app.use(express.static('client/build'));
}
// #################################################
if (process.env.NODE_ENV === 'production') {
app.get('*', (_, res) => {
res.sendFile(path.join(__dirname, '../client/build/index.html'));
});
}
app.listen(PORT, (err) => {
if (err) throw err;
console.log(
`🌎 Server is Ready and Listening on http://localhost:${PORT}`
); // eslint-disable-line no-console
});
Login page:
Server console:
Here is a link to the repo also for more context: https://github.com/BenjDG/puzzle-gallery
Thanks for any help you can offer!!!
If you want the caller to handle errors gracefully, you might consider returning a 200 response, like:
{
"success": true
}
or...
{
"success": false,
"errorCode": "UsernameAlreadyExists",
"message": "This username already exists."
}
It will be the responsibility of the caller to check the success field to make sure the request succeeded. If you want, you can display the friendly message directly in your app, or you can use an "error code" to determine what to display. This is helpful for an API shared across multiple apps, and you want to display different messages, or if you support a multi-lingual UI, and want to translate the message.
Quick aside:
I know it is a bad idea to show all the server error messages to the user, but I would like to show a select few.
I'm sure you've seen articles that warn against this, so just a bit of clarification. You don't want to pass internal error messages and stack traces to your callers, as this exposes more information about your system than most clients should know. Attackers might use this information to learn more about your implementation, and use that to exploit your system.
In general, there is little harm in returning a friendly error message, or a sub-status code, esp for 4xx errors, to help the caller understand how they need to re-submit the request to get a successful response. The important thing is to abstract away all underlying implementation details, so don't just pass an error message directly from Mongoose to your caller, catch the exception, and send an appropriate response.
Because you're dealing with authentication - you also need to be careful about exposing too much to your caller. For example - exposing a "Check Username" endpoint will make it easy for someone to brute force your API to get a handful of valid users in your app.
When you send a status code that is not in the range 200, it is considered as an "exception" in the client code. For Axios specifically, the catch block is executed. In your code, it is
.catch((err) => {
console.error(err);
});
So you have the error message in the console.
Recommend: You can use the status 200. And the better is to check if the email is already in the database before adding a new user.
I think the error 500 is used in case you aren't aware of the error. In this case, we can handle the duplication error by checking before.

Express error middleware not reached by asynchronous error

I'm trying to handle all my errors in my backend in a streamlined fashion. In one of my endpoints I save some documents to my mongoose database and if there are any errors, I catch them and propagate them to my error handler by calling next(err). This works for errors outside the Promise, but not when next() is called inside the catch() clause. My endpoint is defined like this:
router.post("/", (req, res, next) => {
let listToAdd = req.body;
if (!(listToAdd instanceof Array)) {
listToAdd = [listToAdd];
}
let persons = listToAdd.map(p => {
return new Person(p)
});
Promise.all(persons.map(p => p.save()))
.then(saved => {
res.status(201).send(`Successfully saved document(s) with id(s): ${persons.map(p => p._id.toString())}`)
})
.catch(err => {
next(err); // This error never reaches my middleware
});
next(); // errors here be handled by middleware
});
My app.js, where my middleware is registered:
const express = require("express");
const cors = require("cors");
const bodyParser = require("body-parser");
const session = require('express-session');
const mongoose = require('mongoose');
const MongoStore = require('connect-mongo')(session);
// Routes
const personalFileRouter = require("./endpoints/person");
const userRouter = require("./endpoints/user");
// Custom Middleware
const {myRequestLoggingMiddleware} = require("./middleware/express_logging");
const {myErrorLoggingMiddleware} = require("./middleware/express_logging");
// Create a new express app
const app = express();
// Use CORS to allow communication to frontend
app.use(cors());
// use bodyparser to parse url body
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({extended: true}));
// Use Express Sessions to track user logins
app.use(session({
secret: process.env.PRIVATE_KEY,
resave: true,
saveUninitialized: false,
store: new MongoStore({
mongooseConnection: mongoose.connection
})
}));
// Whenever we get a request on the form "/whatever" it should use the routes file to redirect
app.use("/person/", personalFileRouter);
app.use("/user/", userRouter);
app.use(myErrorLoggingMiddleware); // <-- Register my middleware last
module.exports = app;
And finally, my middleware error handler
function myErrorLoggingMiddleware(err, req, res, next) { // Never reached :(
if (res.headersSent) {
return next(err)
}
if (err instanceof mongoose.Error.ValidationError) {
validationErrorHandler(err, req, res);
} else {
defaultErrorHandler(err, req, res);
}
next();
}
exports.myErrorLoggingMiddleware = myErrorLoggingMiddleware;
Well, this is embarrassing. I'm not a 100% sure why, but I reckon that when I call next() below my promise, which is executed before the promise is resolved, the function has run its course and next is "used up". So when the promise later resolves, next() is an empty function that does not reference any middleware. My solution is then to simply remove the last next() and only call next inside my promise.

Reading Cookies from Express Middleware

I'm making an app using the Express and Unblocker modules, along with CookieParser for cookie reading. I have a simple system where an if statement read req.cookies.visits and sees if it is 0 or undefined, then redirects to a password screen. However, a problem arises when the Unblocker module is taken into account. Doing
app.get('/proxy', function(req, res){
if(req.cookies.visits==0||req.cookies.visits==undefined){
res.redirect("/password");
} else {
res.sendFile(__dirname + '/public/index.html');
}
});
doesn't read the cookie for when the request is made (because the route is /proxy/http address). I've tried using express middleware, but doing req.cookie.visits results in an undefined error. Unblocker also has a built in middleware functionality, but the page on npm only shows how to do a response and not a request.
Basically, how could I read a cookie with every request and be compatible with this module?
Check out a full example with middleware :
const express = require('express')
const app = express();
const port = 3000;
var Unblocker = require('unblocker');
const cookieParser = require("cookie-parser");
app.use(cookieParser());
app.use(new Unblocker({prefix: '/proxy/'}));
function checkVisits(req,res,next){
let visits;
try {
visits = req.cookies.visits;
if(isNaN(visits)){
visits = 0;
}
console.log("visits:" + visits);
} catch {
console.log("no visits");
visits = 0;
}finally{
if(visits === 0){
res.redirect("/password");
return;
}
res.visits = parseInt(visits)+1;
res.cookie('visits',res.visits, { maxAge: 900000, httpOnly: true });
return next();
}
}
//add checkVisits(req,res,next) as a middleware
app.get('/index',checkVisits, function (req, res) {
res.send('index visits:'+res.visits);
})
//add checkVisits(req,res,next) as a middleware
app.get('/home',checkVisits, function (req, res) {
res.send('home visits:'+res.visits);
})
app.get('/password', function (req, res) {
res.send('password page');
})
app.listen(port, () => console.log("App listening on port %s!", port))
The checkVisits(req,res,next) middleware will check for visits cookie. If visits cookies is 0 the user will redirected to route /password .

How to prevent routes from being called multiple time in express.js

I am new to node.js and I am trying to make a simple task list app using express.js and express-session. However, for a reason that I don't understand most of the routes are called two or tree times when I make a request and it shouldn't. For instance, if I send a request to /new the new task is sometimes added two (or three) times instead of one and this causes a problem...
I read in other threads that the problem could come from the browser trying to get a favicon, however if I log all incoming request url (console.log(req.url)) on the /new route, the duplicated requests are always /new and not a favicon...
Here is my code :
var express = require('express');
var session = require('express-session');
// Create a new express application instance
var app = express();
// Initialize session
app.use(session({
secret: 'secret',
resave: true,
saveUninitialized: true,
cookie: {}
}));
// Initialize req.session.tasks if needed
app.use(function (req, res, next) {
if (req.session.tasks === undefined) {
req.session.tasks = [];
}
next();
});
app.get('/', function (req, res) {
res.send(req.session.tasks);
});
// Create a Test Task
app.get('/new', function (req, res) {
console.log(req.url);
req.session.tasks.push("Test Task");
res.redirect('/');
});
app.get('/clear', function (req, res) {
req.session.tasks = [];
res.redirect('/');
})
app.listen(3000, function () {
console.log('Task Server is listening on port 3000!');
});
Do you have a idea of what could be the cause of this problem and how to avoid it ??
Thanks a lot !

NodeJS/Express GET exit status of external application

I am pretty new to NodeJS and this is my first time with Express 4, I'm trying to build a simple RESTful front-end around a command-line application. There will ultimately only be one GET and one POST necessary, with the POST handling about 3 or 4 different parameters. The GET should call the command-line application with all default parameters, which is basically just a status check and return the exit status upon completion. The POST will pass along POST parameters on the commandline. I know that this basically calls for an asynchronous call, like child_process.execFile(), but I can't seem to figure out how to actually return the response from within the callback function.
This is the tutorial I used as a starting point, omitting the mongoose dependency, because I have no need for MongoDB, so I basically just followed it up to the point where you start the server. At this point, I'm pretty lost. I always hate writing async code...
var express = require('express'); // call express
var app = express(); // define our app using express
var bodyParser = require('body-parser');
var child_process = require('child_process');
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
var port = process.env.PORT || 8080; // set our port
var router = express.Router(); // get an instance of the express Router
router.get('/', function(req, res) {
res.json({ message: 'hooray! welcome to our api!' });
});
router.get('/myapp/status', function(req, res) {
console.log(req.user);
child_process.execFile(
'casperjs',
['myfile.js', '--cmd="Status"', '--user="myuser"', '--pass="#mypass"'],
null,
function(response) {
// ???
}, res);
});
app.use('/api', router);
app.listen(port);
console.log('Magic happens on port ' + port);
You can try the following:
router.get('/myapp/status', function(req, res) {
console.log(req.user);
child_process.execFile(
'casperjs', //command
["myfile.js --cmd=Status --user=myuser --pass=#mypass"], // args
function(err, stdout, stderr) { //callback
if (err) {
return res.status(500).send(err);
}
res.send(stdout); // to send response to client
});
});

Resources