Supertest and Mongoose Middleware (post remove) - express

I have been fiddling with this for days, and I cannot figure out why the Mongoose middleware is not being invoked.
So I have an API in node.js and I have a website using Angular.js. The Mongoose middleware is this:
schema.post('remove', function (doc) {
console.log('doctors - post - remove');
});
So this hook is called perfectly fine when invoked from the Angular front end. However, when I run a test with supertest, chai, and mocha the hook is not invoked. Here is my code for the testing:
it('/doctors - POST - (create doctor)', function(done){
request(app)
.post('/doctors')
.send(doctor)
.end(function (err, res){
if (res.body['error']) {
expect(S(res.body['error']).startsWith('doctor already exists')).to.be.true;
}
else
expect(res.body['email']).to.equal(doctor['email']);
done();
});
});
....
it('/doctors/remove - DELETE', function(done){
request(app)
.del('/doctors/remove')
.auth(new_doctor_creds["email"], new_doctor_creds["pass"])
.end(function (err, res){
expect(Object.keys(res.body).length).to.not.equal(0);
done();
});
});
And here is my route for the express app:
app.delete('/doctors/remove', authController.isAuthenticated, function (req, res, next) {
var email = req.user['email'];
Doctors.findOne({email:email}).remove(function (err, removed) {
if (err) return next(err);
return res.status(200).send(removed);
});
});
Again, this Mongoose middleware works perfectly fine when invoked from an API call from the Angular app. However, it does not work when tested with supertest. Any ideas on what to do here?
EDIT: I tried to recreate this example with a simplified version that way you can see all of the code. So here is a two file version that is STILL not working. Here is the app.js:
var mongoose = require('mongoose');
var app = require('express')();
var http = require('http');
var fs = require('fs');
var Doctors = require('./schema');
mongoose.connect('mongodb://localhost/m4', function(err) {
if (err) throw err;
console.log('connected');
app.get('/post', function (req, res, next) {
console.log('create');
Doctors.create({email:"hello"}, function (err, inserted) {
if (err) console.log(err);
res.end();
});
});
app.get('/delete', function (req, res, next) {
console.log('removed');
Doctors.remove({email:"hello"}, function (err, removed) {
if (err) console.log(err);
res.end();
});
});
http.createServer(app).listen('6000', function () {
console.log('now listen on localhost:6000');
});
});
and the schema:
var mongoose = require('mongoose');
var schema = mongoose.Schema({
email: { type: String }
});
schema.pre('save', function (next) {
console.log('doctors - post - save');
next();
});
schema.post('remove', function (doc) {
console.log('doctors - post - remove');
});
module.exports = mongoose.model('Doctors', schema);

Here's what I suggest. Let's perform the #remove on the doc found by #findOne. If I remember correctly, remove post hooks only works on Doc#remove and not on Model#remove.
schema.post('remove', function (doc) {
console.log('doctors - post - remove'); // <-- now runs
});
app.delete('/doctors/remove', authController.isAuthenticated, function (req, res, next) {
var email = req.user['email'];
Doctors.findOne({email: email}, function(err, doc) {
if (err) {
return next(err);
}
doc.remove().then(function(removed) {
return res.status(200).send(removed);
}, function(err) {
next(err);
});
});
});

Mongoose post hooks run AFTER the operation is completed, concurrently with operation callbacks. See the comments below:
Doctors.findOne({email:email}).remove(function (err, removed) {
// All this code and the post hook are executed at the same time
if (err) return next(err);
// Here you send the response so supertest#end() will be triggered
// It's not guaranteed that post remove was executed completely at this point
return res.status(200).send(removed);
});
Post hooks were made to run processes independent of the server response. When you run tests, the server shuts down right after the tests are completed, and maybe it had no time enough to finish the post hooks. In the other hand, when you call the API from a client, normally you keep the server running, so the post jobs can be completed.
Now, there comes a problem: how can we test post hooks consistently? I got up this question because I was looking for a solution to that. If you already have an answer, please post here.

Related

How to use different middlewares for get and post methods in Next js api?

With express we can use different middlewares for get and post requests,
eg.
// GET method route
app.get('/users', function (req, res) {
// handle get request
})
// POST method route
app.post('/users', auth, function (req, res) {
// handle post request
})
How do I do the same in next js.
I am totally new to next js. I might be simply missing something.
To handle different HTTP methods in an API route, you can use req.method in your request handler.
export default function handler(req, res) {
if (req.method === 'POST') {
// Process a POST request
} else {
// Handle any other HTTP method
}
}
Or you can use a package like next-connect which enables expressjs like API.
In your api file:
import nc from "next-connect";
const handler = nc()
.use(someMiddleware())
.get((req, res) => {
res.send("Hello world");
})
.post((req, res) => {
res.json({ hello: "world" });
})
.put(async (req, res) => {
res.end("async/await is also supported!");
})
.patch(async (req, res) => {
throw new Error("Throws me around! Error can be caught and handled.");
});
export default handler

Cookie blocked by Chrome

I am trying to set up a simple register/login form with vuejs in the front and a server with express js using the passport library to setup a local and social media startegy.
But I can't seem to pass cookies to the front end when I login with the local strategy.
Also when I sign in with google I get the cookie on the front end but it is not sent with the next API call but this is a subject for another question.
I was confused by this so I made a simple project just to receive and send cookies and it works. Here is the back end:
//headers in app.js
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', 'http://localhost:8080');
res.header('Access-Control-Allow-Credentials', true);
res.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, DELETE');
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
next();
});
// index file
router.get('/cookie', function (req, res, next) {
res.cookie("token", "mytoken");
res.send("cookie sent");
});
router.get('/info', function (req, res, next) {
cookies = req.cookies;
console.log(cookies);
res.cookie("token", "mytoken");
res.send("cookie sent");
});
And here is my front end methods that call the API:
methods: {
async getCookie() {
await axios.get("http://localhost:3000/cookie",{withCredentials:true}).then((response) => {
console.log(response);
}).catch((e) => {
console.log(e);
});
},
async sendCookie() {
await axios.get("http://localhost:3000/info",{withCredentials:true}).then((response) => {
console.log(response);
}).catch((e) => {
console.log(e);
});
}
}
With that I have no problem passing the cookie in the requests and receiving it.
Now on my real project I have this on the back end
//Headers just like the other project
router.post('/users/login', function (req, res, next) {
passport.authenticate('local', { session: false }, function (err, user, info) {
if (err) { return next(err); }
if (user) {
res.cookie('token', 'mytoken');
return res.json({ user: user.toAuthJSON() });
} else {
return res.status(401).json(info);
}
})(req, res, next);
});
Front end call:
// Service file to call the api
axios.defaults.baseURL = "http://127.0.0.1:3000/api/";
axios.defaults.withCredentials = true;
const ApiService = {
get(resource, slug = "") {
return axios.get(`${resource}/${slug}`).catch(error => {
throw new Error(`ApiService ${error}`);
});
},
...
}
export default ApiService;
//actual call in authetification module file
[LOGIN](context, credentials) {
return new Promise(resolve => {
ApiService.post("users/login", { email: credentials.email, password: credentials.password })
.then(({ data }) => {
context.commit(SET_AUTH, data.user);
resolve(data);
})
.catch(({ response }) => {
context.commit(SET_ERROR, response.data.errors);
});
});
},
//
The request works but the cookie is blocked by Chrome:
I don't see what is the difference in my two projects that would trigger this warning on Chrome for the last one.
EDIT: In my original post axios.defaults.baseURL was not set to my actual value.
After last update of chrome browser I also started to get this error. A solution to solve this go to chrome://flags and disable SameSite by default cookies and disable Enable removing SameSite=None cookies. This will solve your problem. I think another way to solve this changing your cookie settings(samesite attribute) when you are creating your session.
Find the solution thank to #skirtle. I set axios.defaults.baseURL to "http://127.0.0.1:3000/api/" but localhost and 127.0.0.1 are not interchangeable and considered two different domains. I switched to axios.defaults.baseURL = "http://localhost:3000/api" and it fixed the problem.

Problem with interrogation my API with mongoose

I have a problem with the result of my query at the database mongoDB.
When I exec my query the result is always empty: [].
I'm new user, please help me.
I use SO Mac OS Catalina.
This code is in the access of my API (I obscured my password)
//connection on mongoDB
let URI =
"mongodb+srv://marco:<xxxxxxxxx>#persona-iydyz.gcp.mongodb.net/test?retryWrites=true&w=majority";
mongoose.connect(URI,
{ useNewUrlParser: true, useUnifiedTopology: true });
mongoose.connection.on('connected', () => {console.log('mongodb connected')})
and this code is in my route who exec the query
var express = require('express');
var router = express.Router();
const persona = require('./../classes/persona')
/* GET home page. */
router.get('/', function(req, res, next) {
let result = persona.find().exec();
res.send(result);
});
module.exports = router;
I don't understand why my result is always empty.
please help me. Thanks
Any mongoose model method should be called with a callback, otherwise it just builds a query, not executes it. exec() is there to execute this built query. You can use callback directly if you do not have to do something with built query.
Then try with the below code:
router.get('/', function(req, res, next) {
persona.find({}, (err, result) => {
if(err)
return res.send(err.message);
return res.send(result)
})
});
If you are using promisified mongoose, then it will be
router.get('/', function(req, res, next) {
try {
let result = await persona.find({});
return res.send(result);
} catch(err) {
return res.send(err.message);
}
});
P.S. Check is the mongoDb is connected successfully or not!

Using Node.js / Express is it possible to next() an error from inside an IIFE and advance to the error handling middleware?

Question: Using Express is it possible to return an error from inside an IIFE and advance to my error handling middleware?
Background: The IIFE is used to create an async container to wrap await statements. I can't see a way out of this and I wonder if I'm using the wrong basic, pattern altogether.
Simplified Example:
app.get('/', function(req, res, next) {
(async function() {
try {
let example = await someLogic(x);
} catch(err) {
return next(new Error('oops'));
}
})();
console.log('main endpoint');
});
app.use(function(err, req, res, next) {
console.log('my error', err.message);
});
Using Express is it possible to return an error from inside an IIFE and advance to my error handling middleware?
Yes, that works fine. It will call next(err) just fine. But, your return will return only from the IIFE and the rest of your request handler after the try/catch will still execute (not sure if you want that or not).
FYI, it's probably simpler to declare the request handler as async and then you don't need the IIFE wrapper:
app.get('/', async function(req, res, next) {
try {
let example = await someLogic(x);
console.log('main endpoint');
// send some response here
} catch(err) {
return next(new Error('oops'));
}
});

How to explicitly pass user data to passport.authenticate

I'm making a webapp that uses Socket.io to pass information between the server and the client, one example being login information. The documentation for passport.authenticate says to use it like so:
app.post('/login', passport.authenticate('local', { successRedirect: '/',
failureRedirect: '/login' }));
However, my webapp is using Polymer client-side routing, so the only route my index.js has is this:
app.get('*', function (req, res) {
res.sendFile('./public/index.html', {root: '.'});
});
Instead, I'd like to do something like this:
io.on('connection', function(socket){
socket.on('login', function(data){
passport.authenticate('local', data);
});
});
However, this doesn't work as the authenticate function doesn't even get called right now. Is there a way to make passport work in such a scenario?
You can try something like below .
In your routes define and require the socket module, so you have access to use it in routes.
var express = require('express');
var app = express();
var server = http.createServer(app);
var io = require('socket.io').listen(server);
var router = express.Router();
var passport = require('passport');
io.on('connection', function(socket){
socket.on('login', function(data){
// call the routes
router.post('/login', function(request, response, next) {
passport.authenticate('local', function(err, user, info) {
if (err) {
// return next(err);
socket.emit('loginResult', { success: false });
}
if (!user) {
var message = "Invalid credentials";
socket.emit('loginResult', { success: false , message: message});
}
request.logIn(user, function (err) {
if (err) {
socket.emit('loginResult', { success: false });
}
// if want to save user in session
request.session.user = user;
// after success code
socket.emit('loginResult', { success: true , user : user});
});
})(request, response, next);
});
});
});
Hope this helps.
You can define your custom callback with passport.authenticate(). I have given a example below, you might wanna try that. Go here for more info.
io.on('connection', function(socket){
socket.on('login', function(data){
var req = {}
req.body = data
passport.authenticate('local', function(err, user, info) {
if (err) {
socket.emit('login', { success: false });
}
if (!user) {
socket.emit('login', { success: false });
}
// Set session
req.logIn(user, function(err) {
if (err) {
socket.emit('login', { success: false });
}
socket.emit('login', { success: true });
});
});
});
Update: Problem with previous code was, when using custom callbacks in passport authenticate it uses req object from the closure, which in this case was undefined as it was not in the router. I think, now that you can provide enough authentication data through req.body it should work.