Node js make test stub for method chaining - node.js

My function is sendMail i want to stub function mailjet and it has a method chain mailjet.post('send').request...
I want to assert callback is called on mail success or fail.
So how i stub this method chain?
var sendMail = function (templateName, callback) {
// From template name find template id of mailjet
mailingExternalTemplateModel.findMailingTemplateId(templateName, function (err, result) {
const request = mailjet
.post("send")
.request(params)
request
.then((result) => {
if (typeof callback === 'function') {
callback(null, result.body);
}
})
.catch((err) => {
if (typeof callback === 'function') {
callback(err, null);
}
})
} else {
callback(err, null);
}
});};
I have done
sinon.stub(mailjet, 'post').withArgs('send').returns(mailjetClient);
sinon.stub(mailjetClient, 'request').returns(Promise);
But i got error TypeError: Attempted to wrap undefined property request as function

I'm the developer in charge of each Mailjet Wrappers, including the NodeJS one.
I'm actually updating each of them and adding features such as the possibility to make the call (or not). For the NodeJS version, a beta will be deployed on npm by tomorrow evening.
I'll update this answer with the modifications you'll have to make (which are few) once the beta is available.
If you are curious, you can still take a look at the modifications I made : https://github.com/mailjet/mailjet-apiv3-nodejs/pull/21

Related

Can't pass callback function to SocketIO in typescript

Stack: Node.js + Express + TypeScript + Socket.io
Problem: I cannot transfer the callback provided in the library using TypeScript
How do I call the callback correctly? The code below throws an error.
socket.on('method', async (params: any, callback: (res: any) => void) => {
// endpoint's logic
const result = await this._service.ServiceMethodAsync(params);
// acknowledgement
callback(result);
})
Error
TypeError: callback is not a function
If you are performing validation on the event that has triggered on the socket then I would say follow the example provided on their page here.
In the case you have provided above you would have:
socket.on('method', async (params, callback) => {
if (typeof callback !== "function") {
// event is not an acknowledgement, do something i.e. return
return;
}
const result = await this._service.ServiceMethodAsync(params);
// acknowledgement
callback(result);
})
Here is another resource on understanding Acknowledgements in SocketIO.
checking the client-side code and turns out the snippet mentioned above is just fine and there was a problem with emitting the method.
fixed client:
socket.emit('method', msg, (res) => {
console.log(res);
});

Proper way to report error back in Firebase Functions HTTPS request in nodejs

I implemented a Firebase function to be called plainly on HTTPS via browser (I use postman for testing) in node.js :
exports.notifToAdmin = functions.https.onRequest((request, response) => {
const title = request.query.title
const body = request.query.body
const badge = request.query.badge
if (typeof title === 'undefined') { return response.status(500).send("title missing") }
if (typeof body === 'undefined') { return response.status(500).send("body missing") }
if (typeof badge === 'undefined') { return response.status(500).send("badge missing") }
notifications.sendNotifToAdmin(title, body, badge)
.then(message => {
const ackString = fingerPrint(msg);
return response.send(ackString);
})
.catch(error => {
console.error(error);
return response.status(500).send(error);
});
});
am I using a correct way to send errors back to the caller (via the response.status(500).send("...."))? In the Firebase errors documentation I see the usage of throw new Error(...). So I am unsure if what I do is the most optimal way? I did notice the doc saying //Will cause a cold start if not caught(linked to this throw error), I don't want to restart anything just report an error to the caller...
I know that the onRequest result should be a promise should I change my code and put a return in front of the notifications.SendNotifToAdmin(...) (this returns a promise) but how does this add up with the return response.send(...)? Is this also returning a promise then?
am I using a correct way to send errors back to the caller (via the response.status(500).send("...."))
Yes, that is standard for HTTP type functions that need to send an HTTP status code. But you should send a 4xx range HTTP codes for errors that are related to the client sending incorrect information.
I know that the onRequest result should be a promise
There is absolutely no obligation for an onRequest type function to return a promise. The function just needs to send a response after all promises are resolved so that the async work can complete before the function is terminated when the response is delivered.

Keep variable between promises in a function?

On firebase function I need to get data from Paypal and do 4 things :
1. returns an empty HTTP 200 to them.
2. send the complete message back to PayPal using `HTTPS POST`.
3. get back "VERIFIED" message from Paypal.
4. *** write something to my Firebase database only here.
What I do now works but i am having a problem with (4).
exports.contentServer = functions.https.onRequest((request, response) => {
....
let options = {
method: 'POST',
uri: "https://ipnpb.sandbox.paypal.com/cgi-bin/webscr",
body: verificationBody
};
// ** say 200 to paypal
response.status(200).end();
// ** send POST to paypal back using npm request-promise
return rp(options).then(body => {
if (body === "VERIFIED") {
//*** problem is here!
return admin.firestore().collection('Users').add({request.body}).then(writeResult => {return console.log("Request completed");});
}
return console.log("Request completed");
})
.catch(error => {
return console.log(error);
})
As you can see when I get final VERIFIED from Paypal I try to write to the db with admin.firestore().collection('Users')..
I get a warning on compile :
Avoid nesting promises
for the write line.
How and where should I put this write at that stage of the promise ?
I understand that this HTTPS Cloud Function is called from Paypal.
By doing response.status(200).end(); at the beginning of your HTTP Cloud Function you are terminating it, as explained in the doc:
Important: Make sure that all HTTP functions terminate properly. By
terminating functions correctly, you can avoid excessive charges from
functions that run for too long. Terminate HTTP functions with
res.redirect(), res.send(), or res.end().
This means that in most cases the rest of the code will not be executed at all or the function will be terminated in the middle of the asynchronous work (i.e. the rp() or the add() methods)
You should send the response to the caller only when all the asynchronous work is finished. The following should work:
exports.contentServer = functions.https.onRequest((request, response) => {
let options = {
method: 'POST',
uri: "https://ipnpb.sandbox.paypal.com/cgi-bin/webscr",
body: verificationBody
};
// ** send POST to paypal back using npm request-promise
return rp(options)
.then(body => {
if (body === "VERIFIED") {
//*** problem is here!
return admin.firestore().collection('Users').add({ body: request.body });
} else {
console.log("Body is not verified");
throw new Error("Body is not verified");
}
})
.then(docReference => {
console.log("Request completed");
response.send({ result: 'ok' }); //Or any other object, or empty
})
.catch(error => {
console.log(error);
response.status(500).send(error);
});
});
I would suggest you watch the official Video Series on Cloud Functions from Doug Stevenson (https://firebase.google.com/docs/functions/video-series/) and in particular the first video on Promises titled "Learn JavaScript Promises (Pt.1) with HTTP Triggers in Cloud Functions".

Meteor : Retrieve value in Meteor.call which calls a server method having promise

I have an internally maintained npm package myNpmPackage which exports a function (for e.g. fnTestMicroSerConn ) as below:
const rp = require('request-promise-native')
exports.fnTestMicroSerConn = function () {
return new Promise(function(resolve, reject) {
var options = {
method: 'GET',
uri : "http://example.net",
resolveWithFullResponse: true,
}
rp(options)
.then(function (response) {
if (response.statusCode !== 200){
console.error("http not 200 but : ",response.statusCode)
resolve(false)
} else {
console.info("connected successfully : "+response.body)
resolve(response)
}
})
.catch(function (err) {
console.error("Error in establishing connectivity : ",err)
resolve(false)
})
})
}
I then need to call the above exported function from a Meteor method like so:
import { Meteor } from 'meteor/meteor';
import myNpmPackage from 'myNpmPackage';
Meteor.methods({
foo: function () {
myNpmPackage.fnTestMicroSerConn().then(function (response){
console.log(" My response: ",response.body);
return(response.body)
})
}
});
console.log(" My response: ",response.body); gets executed successfully and I can see the expected value in the server console log. So till here it's good.
However, now I want to pass the value of response.body to the client side. In short, when I do below on the client :
Meteor.call("foo", function (err, response) {
console.log("calling foo");
if(!err){
console.log("response : ",response);
} else {
console.log("err : ",err);
}
})
Unfortunately, currently I am getting undefined on the client for console.log("response : ",response);
Note: I am using the Meteor Promise package from here
Let me know if any more details are needed or any thing is unclear. I am very new to the Promise style of coding, hence, this can sound as a noob question.
Meteor methods called from clients by Meteor.call run synchronously to prevent clients from pending, even if a callback is supplied.
Your foo method does not wait for that promise inside. It runs past fnTestMicroSerConn() call without hesitation and ends up with no more statement to execute, returning undefined as a result. By the time the promise resolved and logged the expected message on the server console, the method had been exited.
To get resolved/rejected result of that promise, you can return the promise from the method to the caller, and the client would be able to respond to the promise.
Meteor.methods({
foo: function () {
return myNpmPackage.fnTestMicroSerConn();
}
});
Meteor.call("foo")
.then( response => console.log("My response: ", response.body) )
.catch( err => console.log("err : ",err) );
Meteor methods is powerful. The API documentation of methods contains much information and is worth mastery.

Export a function from a function

I have an ExpressJS app where I have api.js in routes that manages connecting to Couchbase and then emits event couchbaseConnected that is awaited by init() function inside api.js.
Inside init() I want to push those exports.someFunction(req, res){return something;}. But when I just put these exports inside init() function, I get an error .get() requires callback functions but got a [object Undefined] so it seems like I am doing it wrong.
The question is how I can export functions from another function in NodeJS?
Here is the code:
//connecting to couchbase and emitting event on connection
couchbase.connect(dbConfiguration, function (err, bucket) {
if (err) {
console.log(err);
}
cb = bucket;
eventEmitter.emit('couchbaseConnected');
});
//listening for the event and fire init() when it's there
eventEmitter.on('couchbaseConnected', function (e) {
console.log('Connected to Couchbase.'.green);
init();
});
function init() {
exports.getUserData = function (req, res) {
if (req.user != undefined && req.user.meta != undefined) {
res.json(200, {result: 'ok'})
}
else {
res.json(401, {error: 'Unauthorized request.'})
}
};
}
Here is the ExpressJS .get() that is located in app.js:
app.get('/api/user/data/:type', api.getUserData);
Here is the ExpressJS .get() that is located in app.js:
app.get('/api/user/data/:type', api.getUserData);
The error message .get() requires callback functions but got a [object Undefined] makes quite clear what happens: You require the API module, it starts to connect to the db, you are defining your express app by passing a non-existent property to .get - which fails, since init has not yet been called and getUserData has not yet been assigned. What you need to do is
var api = require('api');
eventEmitter.on('couchbaseConnected', function () {
app.get('/api/user/data/:type', api.getUserData); // now it is available
});
However, this does not look like good code. Instead of loosely coupling them via that couchbaseConnected event you better should use explicit callbacks that are invoked with the requested values (i.e. the cb bucket, or the getUserData method). At least pass them as parameters to the emitted event.
Also, your setup is unconventional. I don't see why getUserData would need to be asynchronously defined - it should always be available. If the couchbase connection failed, I would not expect the /api/user/data/ service to not exist, but to respond with some 500 internal server error message.
This is an answer that I have made some assumptions as the data provided by you is not sufficient to know what you are doing when you start your app.
I would suggest some change in code:
module.exports.connect = function(callback){
couchbase.connect(dbConfiguration, function (err, bucket) {
if (err) {
console.log(err);
callback(err);
}
cb = bucket;
module.exports.getUserData = getUserData();
callback(err); //just callback with no error as you don't require to send the database
});
}
function getUserData(req, res){
if (req.user != undefined && req.user.meta != undefined) {
res.json(200, {result: 'ok'})
}
else {
res.json(401, {error: 'Unauthorized request.'})
}
};
and in your app.js file where you are starting the app just do this
var api = require('./api');
api.connect(function(error){
if (error) throw error;
app.listen(3000);
console.log('Express started on port 3000');
});
And then you can continue doing exactly what you were doing. It should work.
I have assumed that you are not explicitly calling the connect or its equivalent when you are starting the app.....So your getting that error.

Resources