creating an observable from transporter.sendMail - node.js

i am trying to create an observable from sendMail, currently my test for that looks like that:
/// <reference path="../../typings/index.d.ts" />
import * as chai from "chai";
let assert = chai.assert;
const Rx = require('rxjs');
var nodemailer = require('nodemailer');
describe("emailPlugin", function() {
it.only('should emit one value from a callback', function () {
let mailOptions = {
from: '"Fred Foo 👥" <MY#gmail.com>', // sender address
to: 'YOUR#gmail.com', // list of receivers; comma seperated
subject: 'Hello ✔', // Subject line
text: 'Hello world 🐴', // plaintext body
html: '<b>Hello world 🐴</b>' // html body
};
let transporter = nodemailer.createTransport('smtps://MY%40gmail.com:MY#smtp.gmail.com');
let boundCallback = Rx.Observable.bindNodeCallback(transporter.sendMail);
boundCallback(mailOptions, function(error, info){
if(error){
return error;
}
return info.response;
}).subscribe(x => console.log(x), e => console.error(e));
});
});
the result is an error:
[TypeError: Cannot convert undefined or null to object]
can someone help me out here?

I had exactly the same problem as you, I found this fixed it. Instead of:
let boundCallback = Rx.Observable.bindNodeCallback(transporter.sendMail);
This works for me:
let boundCallback = Rx.Observable.bindNodeCallback((options: any, callback: any) => {
return transporter.sendMail(options, callback);
});
I have no idea why, it seems to me the original form should be correct. Looks like TypeScript struggles with the type of the sendMail function. Hope that helps!

Related

How do you query data using Mongoose within a service function?

I have a service in my node application where I am trying to use nodemailer to send an email with a link to clients. The issue I am having is that when I try to query the recipient email address using Mongoose in the service I get this response message in the console log Promise { <pending> } and obviously my email does not send because I am not returning an email address. I am using the code below as the service that is called from my controller. It looks like everything is working fine in the controller because when I use a test email address as the recipient email the message is sent and function returns no errors. Am I writing the function to query the email wrong or is there a better way I should be setting this up?
var nodemailer = require('nodemailer');
const Obrf = require("../models/Obrf");
const sendLink = (x) => {
var id = x;
const offerid = id.toString();
const url = 'http://localhost:3000/offerletter/' + offerid
async function returnEmail (y) {
const email = await Obrf.findById(y, 'email_address').exec();
return email;
};
const sendToEmail = returnEmail(id);
console.log(sendToEmail);
var transporter = nodemailer.createTransport({
service: 'gmail',
auth: {
user: 'sender#gmail.com',
pass: 'password'
}
});
var mailOptions = {
from: 'sender#gmail.com',
to: sendToEmail,
subject: 'Congratulations on your offer!',
text: 'Go to: ' + url + ' to sign your offer letter!'
};
transporter.sendMail(mailOptions, function(error, info){
if (error) {
console.log(error);
} else {
console.log('Email sent: ' + info.response);
}
});
};
module.exports = {
sendLink,
};
async before a function makes the function return a promise:
are you sure if Obrf.findById(y, 'email_address').exec() returning promise ?
if not remove the aysnc await
function returnEmail (y) {
const email = Obrf.findById(y, 'email_address').exec();
return email;
};
const sendToEmail = returnEmail(id);
if its returning the promise then do something like this
const sendToEmail = returnEmail(id).then(id=>id);

Variable not picking up returned value from function

I'm trying to collect the of returned values of two functions, send-mail and sendsms, into a variable(var ordermessage). It picks up the returned value from send-sms just fine.
I'm using the mailgun api for send-mail, and ordermessage just picks up 'undefined'. But send-mail keeps running.
I've tried `await mailgun.messages().send(...)`
I've tried `const myvar = await mailgun.messages().send(...)`
and also `let myvar = await mailgun.messages().send...`
Nada.
I tried using a function that had the api call as a callback. Still got undefined. The email and sms both get sent, but I need the reply from the email server. I'm using Mailgun trial so I need to return a response.
send-mail.js
var mailgun = require('mailgun-js')({apiKey: process.env.MAILGUN_API_KEY, domain: process.env.MAILGUN_DOMAIN});
var processresponse = "\n";
var data = {
from: 'Zigy Demo Store <admin_demo#zigy.com>',
to: email,
subject: 'You have placed an order.',
text: body
};
console.log("\n----------START EMAIL-------------\n");
mailgun.messages()
.send(data, function (error, body) {
console.log("\nFinally running MAILGUN. Return body is\n", body);
if (body == undefined || body == false) {
console.log("Got nothing from server.");
} else {
processresponse += body.message;
console.log("***********************Gotten reply from Mailgun server.*******************************\n", processresponse);
}
});
OrderController function
module.exports = {
neworder: async function(req, res) {
var sendemail = require('./send-mail');
var sendtext = require('./send-sms');
var orderdetails = 'New order created at '+ new Date() + '\nItem ---> Price'; //Message that will be sent to users.
var item;
var printcart = await Shoppingcart.findOne({
where: {
id: req.body.cart,
owner: req.body.user
}
}).populate('product');
var ordermessage = '';
for (items in printcart.product) {
item = printcart.product[items];
orderdetails += '\n' + item.name + ' ---> ' + item.price;
}
console.log(orderdetails);
//to get email addr and phone number
phone = req.body.phone;
var email = req.body.email;
var user = await User.findOne({id:printcart.owner});
ordermessage += await sendemail(email, orderdetails);
console.log("\nAfter email, the message is ", ordermessage);
ordermessage += await sendtext(phone, orderdetails);
console.log("\nAfter text, Printing order message to be returned to browser ", ordermessage);
console.log("Final message ", ordermessage);
res.send(ordermessage);
}
};
Terminal
----------START EMAIL-------------
Calling test function
After email, the message is
Finally running MAILGUN. Return body is
{ id:
'<20190222062410.1.FD7A4868FA0ADF5E#sandbox612cf746219c46ad93d5dc588f9341ff.mailgun.org>',
message: 'Queued. Thank you.' }
***********************Gotten reply from Mailgun server.*******************************
Queued. Thank you.
Checking list of verified numbers...
Found the phonenumber!
You will receive an SMS message with your order details soon.
After text, Printing order message to be returned to browser
You will receive an SMS message with your order details soon.
Final message
You will receive an SMS message with your order details soon.
SM9bc04208f9354834a153fb1ffd7dc8bb
Any help will be greatly appreciated.
Edit: I called res.write from inside send-mail.js and send-sms.js instead and got rid of the variable ordermessage.
This is a guess: I think the mailgun.messages().send(...) method doesn't return anything as it uses a classic callback. So you will always get undefined from it.
But you get the result from the body parameter in the callback. You could use Promisify to transform the callback into a promise style method.
const util = require('util');
const fs = require('fs');
const messages = mailgun.messages();
const sendPromisified = util.promisify(messages.send());
sendPromisified(data).then((body) => {
// Do something with `body`
}).catch((error) => {
// Handle mailgun error
});
// Async / await style
try {
const mailBody = await sendPromisified(data);
} catch (err) {
// handle mailgun error `err`
}

How to stub a nodejs "required" constructor using sinon?

I'm writing unit tests for a method that uses the email-templates module like this:
var EmailTemplate = require('email-templates').EmailTemplate;
module.exports = {
sendTemplateEmail: function (emailName, data, subject, to, from) {
var template = new EmailTemplate(__dirname + "/../emails/" + emailName);
data.from = FROM;
data.host = config.host;
return template.render(data)
.then(function (result) {
return mailer.sendEmail(subject, to, from, result.html, result.text);
})
.then(function () {
log.info(util.format("Sent %s email to %s. data=%s", emailName, to, JSON.stringify(data)));
return Promise.resolve();
})
.catch(function (err) {
return Promise.reject(new InternalError(err, "Error sending %s email to %s. data=%s", emailName, to, JSON.stringify(data)));
});
}
};
The unit test looks like this:
var assert = require("assert"),
sinon = require("sinon"),
Promise = require("bluebird"),
proxyquire = require("proxyquire");
describe('mailer#sendTemplateEmail', function () {
var templates,
template;
beforeEach(function() {
templates = {
EmailTemplate: function(path) {}
};
template = {
render: function(data) {}
};
sinon.stub(templates, "EmailTemplate").returns(template);
});
it("should reject immediately if template.render fails", function () {
const TO = {email: "user1#example.com", first: "User"};
const FROM = {email: "user2#example.com", first: "User"};
const EMAIL_NAME = "results";
const SUBJECT = "Results are in!";
const DATA = {
week: 10,
season: "2015"
};
var err = new Error("error");
var mailer = proxyquire("../src/mailer", {
"email-templates": templates
});
sinon.stub(template, "render").returns(Promise.reject(err));
return mailer.sendTemplateEmail(EMAIL_NAME, DATA, SUBJECT, TO, FROM)
.then(function () {
assert.fail("Expected a rejected promise.");
})
.catch(function (err) {
assert(err.message === "error");
assert(mailer.sendEmail.notCalled);
});
});
};
The problem I'm encountering is on the first line of the sendTemplateEmail function which instantiates a new EmailTemplate object. The EmailTemplate constructor being called points to the non-stub EmailTemplate function defined in the beforeEach, rather than the sinon stub created on the last line of the beforeEach. If I evaluate the require('email-templates').EmailTemplate statement, however, it correctly points to the sinon stub. I'd prefer not to have to change my code to call the require statement inline like:
var template = new require('email-templates').EmailTemplate(__dirname + "/../emails/" + emailName);
Is there any way to accomplish the stub the way I'm intending?
You can inject your dependency when you construct your mailer - exp:
function mailer(options) {
options = options || {};
this.email_template = options.email_template;
}
Then in the sendTemplateEmail function - use the email_template member.
Also - not sure about your mailer code - but if you need your mailer to act as a singleton in your code (and it isn't already) - you can add this to your mailer:
module.exports = {
getInstance: function(emailTemplate) {
if(this.instance === null){
this.instance = new mailer(emailTemplate);
}
return this.instance;
}
}
Then when you require your mailer you can use the syntax:
var template = new require('email-templates').EmailTemplate(__dirname + "/../emails/" + emailName);
var mail = mailer.getInstance(template);
This way your application (unit test framework or your actual/real-world application) will determine the type of mailer that will be used for the lifetime of the process.

nodejs exporting function email.send doesn't execute

Have mailer.js below but when importing 'var email = require('./models/mailer')' my 'email.send' doesn't execute ?
var config = require('./config');
var email = require('emailjs');
var server = email.server.connect({
host: config.smptRelay
});
module.exports = function send() {
server.send({
text: config.emailText,
from: config.emailFrom,
to: config.emailTo,
subject: config.emailSubject
}, function (err, message) { /*console.log(err || message); */
});
};
You're exporting the function directly, as module.exports, and not as a property of it. Just call it as email().

Mocking email function in nodejs

I've got a mailer function I've built and trying to shore up the coverage. Trying to test parts of it have proven tricky, specifically this mailer.smtpTransport.sendMail
var nodemailer = require('nodemailer')
var mailer = {}
mailer.smtpTransport = nodemailer.createTransport('SMTP', {
'service': 'Gmail',
'auth': {
'XOAuth2': {
'user': 'test#test.com',
'clientId': 'googleClientID',
'clientSecret': 'superSekrit',
'refreshToken': '1/refreshYoSelf'
}
}
})
var mailOptions = {
from: 'Some Admin <test#tester.com>',
}
mailer.verify = function(email, hash) {
var emailhtml = 'Welcome to TestCo. Click this '+hash+''
var emailtxt = 'Welcome to TestCo. This is your hash: '+hash
mailOptions.to = email
mailOptions.subject = 'Welcome to TestCo!'
mailOptions.html = emailhtml
mailOptions.text = emailtxt
mailer.smtpTransport.sendMail(mailOptions, function(error, response){
if(error) {
console.log(error)
} else {
console.log('Message sent: '+response.message)
}
})
}
I'm unsure of how to go about testing, specifically ensuring that my mailer.smtpTransport.sendMail function is passing the correct parameters without actually sending the email. I'm trying to use https://github.com/whatser/mock-nodemailer/tree/master, but I'm probably doing it wrong. Should I be mocking out the method?
var _ = require('lodash')
var should = require('should')
var nodemailer = require('nodemailer')
var mockMailer = require('./helpers/mock-nodemailer')
var transport = nodemailer.createTransport('SMTP', '')
var mailer = require('../../../server/lib/account/mailer')
describe('Mailer', function() {
describe('.verify()', function() {
it('sends a verify email with a hashto an address when invoked', function(done) {
var email ={
'to': 'dave#testco.com',
'html': 'Welcome to Testco. Click this bleh',
'text': 'Welcome to Testco. This is your hash: bleh',
'subject': 'Welcome to Testco!'
}
mockMailer.expectEmail(function(sentEmail) {
return _.isEqual(email, sentEmail)
}, done)
mailer.verify('dave#testco.com','bleh')
transport.sendMail(email, function() {})
})
})
You can use a 'Stub' transport layer on your test instead of SMTP.
var stubMailer = require("nodemailer").createTransport("Stub"),
options = {
from: "from#email.com",
to: "to#email.com",
text: "My Message!"
};
stubMailer.sendMail(options, function(err, response){
var message = response.message;
})
So, in that case, 'message' will be the email in text format. Something like this:
MIME-Version: 1.0
X-Mailer: Nodemailer (0.3.43; +http://www.nodemailer.com/)
Date: Fri, 25 Feb 2014 11:11:48 GMT
Message-Id: <123412341234.e23232#Nodemailer>
From: from#email.com
To: to#email.com
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: quoted-printable
My Message!
For more examples, take a look at nodemailer test suite:
https://github.com/andris9/Nodemailer/blob/master/test/nodemailer-test.js
You can directly mock the sendMail function but it's not obvious how to access it from the tests. A Mailer instance is returned when you create a transport so you need to directly import that class in to your test.
const Mailer = require('nodemailer/lib/mailer')
Then you can mock or stub the sendMail method on the prototype in the usual way. Using Jasmine, you can do it like this:
beforeEach(function () {
spyOn(Mailer.prototype, 'sendMail').and.callFake(function (mailOptions, cb) {
cb(null, true)
})
})
The callFake ensures that the sendMail's callback is still executed encase you need to test what happens next. You can easily simulate an error by passing a first argument to cb: cb(new Error('Email failed'))
Now that the mock is set up, you can check everything is working as intended:
expect(Mailer.prototype.sendMail).toHaveBeenCalled()
expectEmail simply hooks into the transport layer, and expects you to identify the email ( return true if this is the email you are expecting ) by looking at the sentEmail contents.
In this case, return sentEmail.to === 'dave#testco.com' should suffice.
Keep in mind however, this module was designed in an environment where tests are ran in a random order and concurrently. You should propably randomize your data heavily to prevent collisions and false positives. BTW we use something like: var to = Date.now().toString(36) + Faker.Internet.email();
This example works fine for me
======== myfile.js ========
// SOME CODE HERE
transporter.sendMail(mailOptions, (err, info) => {
// PROCESS RESULT HERE
});
======== myfile.spec.js (unit test file) ========
const sinon = require('sinon');
const nodemailer = require('nodemailer');
const sandbox = sinon.sandbox.create();
describe('XXX', () => {
afterEach(function() {
sandbox.restore();
});
it('XXX', done => {
const transport = {
sendMail: (data, callback) => {
const err = new Error('some error');
callback(err, null);
}
};
sandbox.stub(nodemailer, 'createTransport').returns(transport);
// CALL FUNCTION TO TEST
// EXPECT RESULT
});
});

Resources