How to pass request object through promise chain - node.js

I am trying to get the request object as part of the then statement using the Node.js mssql package.
However, when I try to log it out, it is undefined.
exports.someFunction = (proc, req, res) => {
sql.connect(config.properties).then((pool, req) => {
return pool.request()
.execute(proc)
.then((response, req) => {
console.log(req) // undefined
})
}
How can I pass the request object to the then statement for comparison?

You've defined three separate function arguments as req so they all hide each other and you've tried to declare a 2nd parameter for a .then() handler which doesn't exist so of course it will be undefined.
You can direct access variables in the parent scope so that's all you need to do here:
exports.someFunction = (proc, req, res) => {
return sql.connect(config.properties).then((pool) => {
return pool.request()
.execute(proc)
.then((response) => {
console.log(req) // can directly access the req in the parent scope
// do something with `req` here
})
}

If you want to keep scope, perhaps a better way would be to write this as async/await:
exports.someFunction = async (proc, req, res) => {
const pool = await sql.connect(config.properties)
const result = await pool.request().execute(proc)
console.log(result, req) // both retain scope
})
}
But I think the reason why req is undefined in your console.log is because in:
sql.connect(config.properties).then((pool, req) => {
You are expecting req to be passed to you as a result of the .then() (which is a shadowed variable). If you remove it from here and the other .then() then it should also work

Related

Why does Async firebase fetching is not working? (NODE JS)

Building a NodeJS REST API.
Trying to send load data from FireBase collection, then sending it to the user (as API response).
Looks like the problem is that it's not waits for the firebase fetch to resolve, but send back a response without the collection data. (tried to use ASYNC-AWAIT but its not working)
exports.getChatMessages = async (req, res, next) => {
const chatId = req.params.chatId
const getChatData = () => {
db
.collection('chats')
.doc(chatId)
.collection('messages')
.orderBy('timeStamp', 'asc')
.onSnapshot((snapshot) => {
snapshot.docs.forEach(msg => {
console.log(msg.data().messageContent)
return {
authorID: msg.data().authorID,
messageContent: msg.data().messageContent,
timeStamp: msg.data().timeStamp,
}
})
})
}
try {
const chatData = await getChatData()
console.log(chatData)
res.status(200).json({
message: 'Chat Has Found',
chatData: chatData
})
} catch (err) {
if (!err.statusCode) {
err.statusCode(500)
}
next(err)
}
}
As you can see, I've used 2 console.logs to realize what the problem, Terminal logs looks like:
[] (from console.logs(chatData))
All messages (from console.log(msg.data().messageContent))
Is there any way to block the code unti the firebase data realy fetched?
If I correctly understand, you want to send back an array of all the documents present in the messages subcollection. The following should do the trick.
exports.getChatMessages = async (req, res, next) => {
const chatId = req.params.chatId;
const collectionRef = db
.collection('chats')
.doc(chatId)
.collection('messages')
.orderBy('timeStamp', 'asc');
try {
const chatsQuerySnapshot = await collectionRef.get();
const chatData = [];
chatsQuerySnapshot.forEach((msg) => {
console.log(msg.data().messageContent);
chatData.push({
authorID: msg.data().authorID,
messageContent: msg.data().messageContent,
timeStamp: msg.data().timeStamp,
});
});
console.log(chatData);
res.status(200).json({
message: 'Chat Has Found',
chatData: chatData,
});
} catch (err) {
if (!err.statusCode) {
err.statusCode(500);
}
next(err);
}
};
The asynchronous get() method returns a QuerySnapshot on which you can call forEach() for enumerating all of the documents in the QuerySnapshot.
You can only await a Promise. Currently, getChatData() does not return a Promise, so awaiting it is pointless. You are trying to await a fixed value, so it resolves immediately and jumps to the next line. console.log(chatData) happens. Then, later, your (snapshot) => callback happens, but too late.
const getChatData = () => new Promise(resolve => { // Return a Promise, so it can be awaited
db.collection('chats')
.doc(chatId)
.collection('messages')
.orderBy('timeStamp', 'asc')
.onSnapshot(resolve) // Equivalent to .onSnapshot((snapshot) => resolve(snapshot))
})
const snapshot = await getChatData();
console.log(snapshot)
// Put your transform logic out of the function that calls the DB. A function should only do one thing if possible : call or transform, not both.
const chatData = snapshot.map(msg => ({
authorID: msg.data().authorID,
messageContent: msg.data().messageContent,
timeStamp: msg.data().timeStamp,
}));
res.status(200).json({
message: 'Chat Has Found',
chatData
})
Right now, getChatData is this (short version):
const getChatData = () => {
db
.collection('chats')
.doc(chatId)
.collection('messages')
.orderBy('timeStamp', 'asc')
.onSnapshot((snapshot) => {}) // some things inside
}
What that means is that the getChatData function calls some db query, and then returns void (nothing). I bet you'd want to return the db call (hopefully it's a Promise), so that your await does some work for you. Something along the lines of:
const getChatData = async () =>
db
.collection('chats')
// ...
Which is the same as const getChatData = async() => { return db... }
Update: Now that I've reviewed the docs once again, I see that you use onSnapshot, which is meant for updates and can fire multiple times. The first call actually makes a request, but then continues to listen on those updates. Since that seems like a regular request-response, and you want it to happen only once - use .get() docs instead of .onSnapshot(). Otherwise those listeners would stay there and cause troubles. .get() returns a Promise, so the sample fix that I've mentioned above would work perfectly and you don't need to change other pieces of the code.

Jest Express testing middleware with arguments

I'm pretty new to node and this is my first time unit testing an app. I'm doing well with Jest faking the request with Jest function as below
// Create a fake request
const mockRequest = (sessionData, body) => ({
session: { data: sessionData },
body
});
// Create a fake response
const mockResponse = () => {
const res = {};
res.status = jest.fn().mockReturnValue(res);
res.json = jest.fn().mockReturnValue(res);
return res;
};
const mockNext = () => {
const next = jest.fn();
return next;
};
So I can use them like follows
doSomething(req, res, next);
expect(res.status).toHaveBeenCalledWith(201);
//or
expect(next).toHaveBeenCalled();
That's enough for all the cases until I found that my authorisation middleware includes a couple of parameters so I can not pass the fake res and req as below
exports.isAllowedTo = (par1, par2) => {
return async (req, res, next) => {
try {
//
// Grant logic here that needs par1 and par2
//
if(granted)
next();
else
return res.status(401).json({
error: "You don't have enough permission to perform this action"
});
} catch (err) {
res.status(406).json({
error: err.toString(),
})
}
}
}
If I test isAllowTo(req, res, next) with the mock req, res and next then I'm missing the 2 parameters needed by the function. Actually when I do this, the function isAllowTo() is not even called. I don't know how to deal with that. Any suggestion or approach?
Two months later I realized that the real problem is that I'm testing a function inside of another function.
So firstly I store the function in a variable so I can test it as a regular middleware.
test('Grant access if user role is allowed to', async () => {
const isAllowToTester = userController.isAllowedTo(par1, par2);
await isAllowToTester(req, res, next)
expect(next).toHaveBeenCalled();
});
Hope this helps someone else.
Credits to this post
Check out https://github.com/nock/nock it's a library dedicated to mocking requests and responses, it's really easy to use with unit tests/jest. I personally don't think is worth it to write your own mocking implementation.

How should I pass three arguments to an express/node arrow function?

I am declaring my function like this:
const parseConnections = (connectionsCSVPath, req, res) => {
//do a bunch of stuff
}
Inside the function, if I try to call res.locals.something, I get an error saying "cannot read property locals of undefined" I have tried several other syntaxes, such as this:
const parseConnections = ((connectionsCSVPath, req, res) => {
//do a bunch of stuff
})
this:
const parseConnections = (connectionsCSVPath, (req, res) => {
//do a bunch of stuff
})
and this:
const parseConnections = connectionsCSVPath, (req, res) => {
//do a bunch of stuff
}
and they all throw errors. What is the proper way to pass these 3 arguments to the function so that all 3 are defined inside?
Edit*: The function is then called like this:
router.post(
'/upload/engagements/batch', checkIfAuthenticated,
parseConnections('./tmp/connections.csv'),
parseMessages('./tmp/messages.csv'), (req, res) => {
//do a bunch of stuff
}
The problem is not (necessarily) with how you define the function but with how you are using it.
parseConnections('./tmp/connections.csv') calls the function right then and there. You are only passing a single argument to it, so req and res will be undefined.
function foo(a, b, c) {
console.log('a:', a);
console.log('b:', b);
console.log('c:', c);
}
foo('first argument');
However, you cannot pass values for req and res because these values are created and passed by express itself.
Essentially you are making the mistake of calling a function where you should be passing it. router.post expects to be passed one or more functions. But you are calling parseConnections and pass its return value instead which is probably undefined.
Here is a simple example that demonstrates the difference:
function foo(x) {
console.log('inside foo', 'x is ', x);
}
// bar expects to be passed a function that it can call
function bar(callback) {
console.log('bar received:', callback);
try {
callback(42);
} catch(e) {
console.error(e);
}
}
// this will work as expected
console.log('Passing a function');
bar(foo);
// this will error because `bar` doesn't receive a function.
// this is what you are doing
console.log('Calling a function and passing its return value');
bar(foo(21));
One way to fix your problem is to make parseConnections return a function, which is then received by router.post. I'm using normal function declarations here so that the syntax is not too confusing:
function parseConnections(connectionsCSVPath) {
return function(req, res) {
//do a bunch of stuff
};
}
This requires no changes to your router.post call.
Another solution is to pass a function to router.post that calls parseConnections instead, passing along req and res:
router.post(
'/upload/engagements/batch',
checkIfAuthenticated,
(req, res) => parseConnections('./tmp/connections.csv', req, res),
// alternatively you can use `.bind`:
// parseConnections.bind(null, './tmp/connections.csv'),
parseMessages('./tmp/messages.csv'), // <- this is likely wrong as well,
// but I leave this to you to figure out
(req, res) => {
//do a bunch of stuff
}
);

Using req & res across functions

I found it frustrating to do function ***(req, res){ ... } every time when working with promises. So I had an idea of making a top, reference objects instead, Req and Res
let Req;
let Res;
// i can also do "const { username } = Req.body" to make "username" avaliable to all functions
function promise() { return Promise.resolve(); }
function promise2() { Res.send('test'); }
module.exports = (req, res, next) => {
Req = req
Res = res
return promise().then(() => promise2());
}
What are your thoughts on this, pros and cons?
If this is anti-patern let me know and suggest a preferred way.
Thanks
Instead of having every promise to resolve with req and res (would need to be in a single object, because you can only resolve one thing).
You can use use this pattern.
function promise1(req, res) {
req.whatever = ''; // will be available in future promises
return Promise.resolve(foo); // you don't need to resolve req and res
}
function promise2(req, res) {
return function (foo) {
// whatever promise1 resolved to, will be here :)
res.send('test');
}
}
module.exports = (req, res, next) => {
return promise1(req, res)
.then(promise2(req, res));
}
In this case you are not hoisting your Req and Res variables in the global scope of your module (you really don't need it to be global in this case)

Unit testing validation with express-validator

How can I unit test my validations that are done using express-validator?
I have tried creating a dummy request object, but I get the error: TypeError: Object #<Object> has no method 'checkBody'. I am able to manually test that the validation works in the application.
Here is what I have tried:
describe('couponModel', function () {
it('returns errors when necessary fields are empty', function(done){
var testBody = {
merchant : '',
startDate : '',
endDate : ''
};
var request = {
body : testBody
};
var errors = Model.validateCouponForm(request);
errors.should.not.be.empty;
done();
});
});
My understanding is that the checkBody method is added to the request object when I have app.use(expressValidator()) in my express application, but as I am only testing that the validation is working in this unit test I do not have an instance of the express application available, and the validation method that I am testing is not called directly from it anyway as it is only called through a post route, which I do not want to call for a unit test as it involves a database operation.
Here's a solution for the new express-validator api (v4):
tl;dr: You can use this function:
exports.testExpressValidatorMiddleware = async (req, res, middlewares) => {
await Promise.all(middlewares.map(async (middleware) => {
await middleware(req, res, () => undefined);
}));
};
It can be called like this:
const { validationResult } = require('express-validator/check');
await testExpressValidatorMiddleware(req, res, expressValidatorMiddlewareArray);
const result = validationResult(req);
expect(result....
These solutions assume you have the async/await syntax available. You can use the node-mocks-http library to create the req and res objects.
Explanation:
Each element in an express-validator array is applied to the route as middleware. Say this is your array:
[
check('addresses.*.street').exists(),
check('addresses.*.postalCode').isPostalCode(),
]
Each check will be loaded as middleware.
In order to test middleware, we need to implement a function which acts similarly to how express implements middleware.
Express middleware always accepts three params, the request and response objects, and the next function it should call (next by convention). Why do we need next? For scenarios where we want our middleware to do something before and after the proceeding function, e.g.
const loggerMiddleware = (req, res, next) => {
console.log('req body is ' + req.body);
next();
console.log('res status is ' + res.status);
};
But express-validator doesn't do this, it just calls next() once each of its validators is finished. For that reason, our implementation doesn't really need to bother with next().
Instead, we can just run each of our middlewares in turn and pass an empty function as next to avoid a TypeError:
middlewares.map((middleware) => {
middleware(req, res, () => undefined);
});
But this won't work, because express-validator middleware returns promises and we need to wait for them to resolve...
middlewares.map(async (middleware) => {
await middleware(req, res, () => undefined);
});
And we don't want to move on until all promises in our iteration are resolved (Mozilla docs on Promise.all are here):
await Promise.all(middlewares.map(async (middleware) => {
await middleware(req, res, () => undefined);
}));
And we should extract this as a reusable function:
exports.testExpressValidatorMiddleware = async (req, res, middlewares) => {
await Promise.all(middlewares.map(async (middleware) => {
await middleware(req, res, () => undefined);
}));
};
And now we've arrived at my solution. If someone can improve on this implementation, I'm very happy to make edits.
I faced the same issue and I had to create the methods using this:
var validRequest = {
// Default validations used
checkBody: function () { return this; },
checkQuery: function () { return this; },
notEmpty: function () { return this; },
// Custom validations used
isArray: function () { return this; },
gte: function () { return this; },
// Validation errors
validationErrors: function () { return false; }
};
function getValidInputRequest(request) {
Object.assign(request, validRequest);
return request;
}
So, in your code you have to call the getValidInputRequest helper:
describe('couponModel', function () {
it('returns errors when necessary fields are empty', function(done){
var testBody = {
merchant : '',
startDate : '',
endDate : ''
};
var request = {
body : testBody
};
request = getValidInputRequest(request); // <-- Update the request
var errors = Model.validateCouponForm(request);
errors.should.not.be.empty;
done();
});
});
Now, the request object has the body property and all the methods needed by express-validator.
If you want to test the cases that the validator fails, you should use something like this:
function getInvalidInputRequest(request, errorParams) {
// Get de default valid request
Object.assign(request, validRequest);
// Override the validationErrors function with desired errors
request.validationErrors = function () {
var errors = [];
errorParams.forEach(function(error){
errors.push({msg: 'the parameter "'+ error +'" is mandatory'})
});
return errors;
};
return request;
}
And to update the request you should do:
request = getInvalidInputRequest(request, ['mandatory_param_1', 'mandatory_param_2']);

Resources