diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000..02f08fb --- /dev/null +++ b/.babelrc @@ -0,0 +1,5 @@ +{ + "presets": [ + "es2015" + ] +} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 1fe1b00..37fbab1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .idea/ node_modules/ +sandbox/ diff --git a/gulpfile.babel.js b/gulpfile.babel.js new file mode 100644 index 0000000..92c43de --- /dev/null +++ b/gulpfile.babel.js @@ -0,0 +1,10 @@ +'use strict'; + +let gulp = require('gulp'); +let gulpMocha = require('gulp-mocha'); + +gulp.task('test', () => { + return gulp.src(['**/*.spec.js', '!node_modules/**', '!**/node_modules/**'], {read: false}).pipe(gulpMocha()); +}); + +gulp.task('default', ['test']); diff --git a/package.json b/package.json index bf9e7ca..7fe605d 100644 --- a/package.json +++ b/package.json @@ -13,14 +13,18 @@ }, "peerDependencies": {}, "dependencies": { - "lodash": "^4.11.1", + "lodash": "^4.13.1", "mandrill-api": "^1.0.45", - "nodemailer": "^2.3.2", + "nodemailer": "^2.4.2", "swig": "^1.4.2" }, "devDependencies": { + "babel-core": "^6.9.1", + "babel-preset-es2015": "^6.9.0", + "gulp": "^3.9.1", + "gulp-mocha": "^2.2.0", "rewire": "^2.5.1", - "should": "^8.3.1", - "sinon": "^1.17.3" + "should": "^9.0.0", + "sinon": "^1.17.4" } } diff --git a/providers/mandrill.provider.js b/providers/mandrill.provider.js index 7689c02..5e1f168 100644 --- a/providers/mandrill.provider.js +++ b/providers/mandrill.provider.js @@ -1,23 +1,22 @@ 'use strict'; let templateService = require('../template.service.js'), _ = require('lodash'), - swig = require('swig'), mandrill = require('mandrill-api/mandrill'); class MandrillProvider { constructor(config) { this.name = "mandrill"; - + let useTestApi = process.env.NODE_ENV === 'e2e' || process.env.NODE_ENV === 'test'; let api_key = useTestApi ? config.testApiKey : config.apiKey; this.mandrill_client = new mandrill.Mandrill(api_key); } send(template, mailSettings, templateData) { - + return new Promise((resolve, reject) => { templateService.getHtml(template, templateData).then((stringHtml) => { - + let recipientMetadata = []; if (mailSettings.existingUsers) { @@ -52,7 +51,7 @@ class MandrillProvider { from_email: from.email, from_name: from.name, to: to_emails, - subject:swig.render((mailSettings.subject || template.subject), {locals:templateData}), + subject: templateService.getHtmlSubject(mailSettings.subject || template.subject, templateData), html: stringHtml, track_opens: true, track_clicks: true, @@ -76,10 +75,10 @@ class MandrillProvider { let results = responses.map((response) => { let result = { status: response.status, - email: response.email, + email: response.email }; - if(response.reject_reason) { + if (response.reject_reason) { result.reason = response.reject_reason; } diff --git a/tests/mandrill.provider.spec.js b/providers/mandrill.provider.spec.js similarity index 94% rename from tests/mandrill.provider.spec.js rename to providers/mandrill.provider.spec.js index 9162980..984ad92 100644 --- a/tests/mandrill.provider.spec.js +++ b/providers/mandrill.provider.spec.js @@ -33,10 +33,11 @@ describe('Mandrill email provider service', () => { }); beforeEach(function () { - mandrillEmailServiceClass = rewire('../providers/mandrill.provider.js'); + mandrillEmailServiceClass = rewire('./mandrill.provider.js'); templateService = { - getHtml: sinon.stub().returns(Promise.resolve()) + getHtml: sinon.stub().returns(Promise.resolve()), + getHtmlSubject: () => Promise.resolve() }; mandrill_client = { diff --git a/providers/nodemailer.provider.js b/providers/nodemailer.provider.js index 6288485..ae271da 100644 --- a/providers/nodemailer.provider.js +++ b/providers/nodemailer.provider.js @@ -1,59 +1,53 @@ 'use strict'; var templateService = require('../template.service.js'), - swig = require('swig'), nodemailer = require('nodemailer'); class NodemailerProvider { constructor(config) { this.name = "nodemailer"; - this.transport = nodemailer.createTransport(config); + this.config = config; } send(template, mailSettings, templateData) { let from = typeof mailSettings.from !== 'undefined' ? mailSettings.from : template.from; - return new Promise((resolve, reject) => { - templateService.getHtml(template, templateData).then((stringHtml) => { - - // prepare options object - var transportOptions = { - from: `${from.name} <${from.email}>`, // Do not change - must be verified email address of startapp.com - to: mailSettings.to, - sender: mailSettings.sender || template.sender, - replyTo: mailSettings.replyTo || template.replyTo, - subject:swig.render((mailSettings.subject || template.subject), {locals:templateData}), - html: stringHtml - }; - - // send email - this.transport.sendMail(transportOptions, (err, responses) => { - this.transport.close(); - if (err) { - return reject(err); - } - - let results = []; - - responses.accepted.forEach((accepted) => { - results.push({ - status: 'sent', - email: accepted - }); - }); - - responses.rejected.forEach((rejected) => { - results.push({ - status: 'rejected', - email: rejected - }); - }); - - resolve(results); + return templateService.getHtml(template, templateData).then((stringHtml) => { + + let nodemailer_transport = nodemailer.createTransport(this.config); + + // prepare options object + var transportOptions = { + from: `${from.name} <${from.email}>`, // Do not change - must be verified email address of startapp.com + to: mailSettings.to, + sender: mailSettings.sender || template.sender, + replyTo: mailSettings.replyTo || template.replyTo, + subject: templateService.getHtmlSubject(mailSettings.subject || template.subject, templateData), + html: stringHtml + }; + + // send email + return nodemailer_transport.sendMail(transportOptions); + + }).then((responses) => { + + let results = []; + + responses.accepted.forEach((accepted) => { + results.push({ + status: 'sent', + email: accepted }); - }, (err) => { - return reject(err); }); + + responses.rejected.forEach((rejected) => { + results.push({ + status: 'rejected', + email: rejected + }); + }); + + return results; }); } } diff --git a/providers/nodemailer.provider.spec.js b/providers/nodemailer.provider.spec.js new file mode 100644 index 0000000..1a39b41 --- /dev/null +++ b/providers/nodemailer.provider.spec.js @@ -0,0 +1,107 @@ +'use strict'; + +describe('nodemailer email provider service', () => { + + + let rewire = require('rewire'); + let should = require('should'); + + let nodemailerEmailServiceClass, + nodemailerEmailProvider, + templateServiceMock, + template, + transport, + nodemailerMock; + + before(function () { + template = { + "from": { + "email": "test@test.com", + "name": "Test" + } + }; + }); + + beforeEach(function () { + nodemailerEmailServiceClass = rewire('./nodemailer.provider.js'); + + templateServiceMock = { + getHtml: () => Promise.resolve(), + getHtmlSubject: () => Promise.resolve() + }; + + transport = { + sendMail: () => Promise.resolve({ + accepted: ["test@test.com"], + rejected: [] + }) + }; + + nodemailerMock = { + createTransport: () => { + return transport; + } + }; + + nodemailerEmailServiceClass.__set__("templateService", templateServiceMock); + nodemailerEmailServiceClass.__set__("nodemailer", nodemailerMock); + + nodemailerEmailProvider = new nodemailerEmailServiceClass(); + + }); + + + it("should return multiple results for multiple recipients", (done) => { + transport = { + sendMail: () => Promise.resolve({ + accepted: [ + "test@test.com", + "test2@test.com" + ], + rejected: [] + }) + }; + + nodemailerEmailProvider.send(template, {}, {}).then((res) => { + res.length.should.equal(2); + return res; + }).then(done.bind(null, null), done); + }); + + it("should return single result with single recipient", (done) => { + nodemailerEmailProvider.send(template, {}, {}).then((res) => { + res.length.should.equal(1); + return res; + }).then(done.bind(null, null), done); + }); + + it("should return result object", (done) => { + nodemailerEmailProvider.send(template, {}, {}).then((res) => { + let result = res[0]; + result.should.have.ownProperty('status'); + result.should.have.ownProperty('email'); + return res; + }).then(done.bind(null, null), done); + }); + + it("should reject if template service fails", () => { + templateServiceMock.getHtml = () => Promise.reject(new Error("Template Service Error")); + return nodemailerEmailProvider.send(template, {}, {}).should.be.rejectedWith("Template Service Error"); + }); + + it("should reject if nodemailer service fails", () => { + transport.sendMail = () => Promise.reject(new Error("nodemailer error")); + return nodemailerEmailProvider.send(template, {}, {}).should.be.rejectedWith("nodemailer error"); + }); + + it("should return status rejected if nodemailer rejects", (done) => { + transport.sendMail = () => Promise.resolve({ + accepted: [], + rejected: ["test@test.com"] + }); + return nodemailerEmailProvider.send(template, {}, {}).then((res) => { + res[0].status.should.equal("rejected"); + }).then(done.bind(null, null), done); + }); + +}); \ No newline at end of file diff --git a/template.service.js b/template.service.js index 7c88b64..0f4f7e5 100644 --- a/template.service.js +++ b/template.service.js @@ -5,9 +5,7 @@ var swig = require('swig'), class EmailTemplateService { - - constructor() {} - + /** * Method to get a specific rendered template by name * @param template - {Template Object} template from config @@ -33,6 +31,10 @@ class EmailTemplateService { }); }); } + + getHtmlSubject(template, data) { + return swig.render(template, {locals: data}); + } } module.exports = new EmailTemplateService(); \ No newline at end of file diff --git a/tests/template.service.spec.js b/template.service.spec.js similarity index 95% rename from tests/template.service.spec.js rename to template.service.spec.js index 59d7397..7a6a470 100644 --- a/tests/template.service.spec.js +++ b/template.service.spec.js @@ -11,7 +11,7 @@ describe('Email template service', () => { templateService; beforeEach(function () { - templateService = rewire('../template.service.js'); + templateService = rewire('./template.service.js'); swig = {}; templateService.__set__("swig", swig);