From 279992379ff4a18ddbf9c9a30ac8c2f5637f47a3 Mon Sep 17 00:00:00 2001 From: Tankred Hase Date: Mon, 30 May 2016 15:36:32 +0200 Subject: [PATCH] Implement email.sendVerifyRemove Write email.js unit tests --- src/dao/email.js | 26 ++++++ src/dao/message.json | 7 +- src/route/rest.js | 2 +- test/unit/email-test.js | 173 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 206 insertions(+), 2 deletions(-) create mode 100644 test/unit/email-test.js diff --git a/src/dao/email.js b/src/dao/email.js index 57021bf..a3248b9 100644 --- a/src/dao/email.js +++ b/src/dao/email.js @@ -98,6 +98,32 @@ class Email { return yield this.send(msg); } + /** + * Send the verification email to the user to verify key removal. Only one email + * needs to sent to a single user id to authenticate removal of all user ids + * that belong the a certain key id. + * @param {Object} userId user id document + * @param {Object} origin origin of the server + * @yield {Object} send response from the SMTP server + */ + *sendVerifyRemove(options) { + let userId = options.userId, origin = options.origin; + let msg = { + from: this._sender, + to: userId, + subject: message.verifyRemove.subject, + text: message.verifyRemove.text, + html: message.verifyRemove.html, + params: { + name: userId.name, + baseUrl: origin.protocol + '://' + origin.host, + keyid: encodeURIComponent(userId.keyid), + nonce: encodeURIComponent(userId.nonce) + } + }; + return yield this.send(msg); + } + /** * A generic method to send an email message via nodemailer. * @param {Object} from sender user id object: { name:'Jon Smith', email:'j@smith.com' } diff --git a/src/dao/message.json b/src/dao/message.json index c1b3e9f..0b5d607 100644 --- a/src/dao/message.json +++ b/src/dao/message.json @@ -1,7 +1,12 @@ { "verifyKey": { "subject": "Verify Your Key", - "text": "Hello {{name}},\n\nplease click here to verify your key: {{baseUrl}}/api/v1/verify/?keyid={{keyid}}&nonce={{nonce}}", + "text": "Hello {{name}},\n\nplease click here to verify your key: {{baseUrl}}/api/v1/verify?keyid={{keyid}}&nonce={{nonce}}", + "html": "" + }, + "verifyRemove": { + "subject": "Verify Key Removal", + "text": "Hello {{name}},\n\nplease click here to verify the removal of your key: {{baseUrl}}/api/v1/verifyRemove?keyid={{keyid}}&nonce={{nonce}}", "html": "" } } \ No newline at end of file diff --git a/src/route/rest.js b/src/route/rest.js index a2eaae9..07657c8 100644 --- a/src/route/rest.js +++ b/src/route/rest.js @@ -46,7 +46,7 @@ class REST { ctx.throw(400, 'Invalid request!'); } let origin = util.getOrigin(ctx); - yield this._publicKey({ publicKeyArmored, primaryEmail, origin }); + yield this._publicKey.put({ publicKeyArmored, primaryEmail, origin }); ctx.status = 201; } diff --git a/test/unit/email-test.js b/test/unit/email-test.js new file mode 100644 index 0000000..4c9dbcf --- /dev/null +++ b/test/unit/email-test.js @@ -0,0 +1,173 @@ +'use strict'; + +require('co-mocha')(require('mocha')); // monkey patch mocha for generators + +const expect = require('chai').expect; +const log = require('npmlog'); +const Email = require('../../src/dao/email'); +const nodemailer = require('nodemailer'); +const sinon = require('sinon'); + + +describe('Email Unit Tests', function() { + let email, sendFnStub; + + let sender = { + name: 'Foo Bar', + email: 'foo@bar.com' + }; + let userId1 = { + name: 'name1', + email: 'email1', + keyid: '0123456789ABCDF0', + nonce: 'qwertzuioasdfghjkqwertzuio' + }; + let userId2 = { + name: 'name2', + email: 'email2', + keyid: '0123456789ABCDF0', + nonce: 'qwertzuioasdfghjkqwertzuio' + }; + let origin = { + protocol: 'http', + host: 'localhost:8888' + }; + let mailOptions = { + from: sender, + to: sender, + subject: 'Hello ✔', // Subject line + text: 'Hello world 🐴', // plaintext body + html: 'Hello world 🐴' // html body + }; + + beforeEach(function() { + sendFnStub = sinon.stub(); + sinon.stub(nodemailer, 'createTransport').returns({ + templateSender: function() { return sendFnStub; } + }); + + sinon.stub(log, 'warn'); + sinon.stub(log, 'error'); + + email = new Email(nodemailer); + email.init({ + host: 'host', + auth: { user:'user', pass:'pass' }, + sender: sender + }); + expect(email._sender).to.equal(sender); + }); + + afterEach(function() { + nodemailer.createTransport.restore(); + log.warn.restore(); + log.error.restore(); + }); + + describe("sendVerifyKey", function() { + + beforeEach(function() { + sinon.stub(email, '_sendVerifyKeyHelper').returns(Promise.resolve({ response:'250' })); + }); + + afterEach(function() { + email._sendVerifyKeyHelper.restore(); + }); + + it('should send one email if primary email is given', function *() { + let options = { + userIds: [userId1, userId2], + primaryEmail: userId1.email, + origin: origin + }; + yield email.sendVerifyKey(options); + + expect(email._sendVerifyKeyHelper.withArgs(userId1, origin).calledOnce).to.be.true; + }); + + it('should send two emails if primary email is not given', function *() { + let options = { + userIds: [userId1, userId2], + origin: origin + }; + yield email.sendVerifyKey(options); + + expect(email._sendVerifyKeyHelper.calledTwice).to.be.true; + }); + + it('should send two emails if primary email does not match', function *() { + let options = { + userIds: [userId1, userId2], + primaryEmail: 'other', + origin: origin + }; + yield email.sendVerifyKey(options); + + expect(email._sendVerifyKeyHelper.calledTwice).to.be.true; + }); + }); + + describe("_sendVerifyKeyHelper", function() { + beforeEach(function() { + sinon.stub(email, 'send').returns(Promise.resolve({ response:'250' })); + }); + + afterEach(function() { + email.send.restore(); + }); + + it('should work', function *() { + let info = yield email._sendVerifyKeyHelper(userId1, origin); + + expect(info.response).to.match(/^250/); + }); + }); + + describe("sendVerifyRemove", function() { + beforeEach(function() { + sinon.stub(email, 'send').returns(Promise.resolve({ response:'250' })); + }); + + afterEach(function() { + email.send.restore(); + }); + + it('should work', function *() { + let info = yield email.sendVerifyRemove({userId:userId1, origin}); + + expect(info.response).to.match(/^250/); + }); + }); + + describe("send", function() { + it('should work', function *() { + sendFnStub.returns(Promise.resolve({ response:'250' })); + + let info = yield email.send(mailOptions); + + expect(info.response).to.match(/^250/); + }); + + it('should log warning for reponse error', function *() { + sendFnStub.returns(Promise.resolve({ response:'554' })); + + let info = yield email.send(mailOptions); + + expect(info.response).to.match(/^554/); + expect(log.warn.calledOnce).to.be.true; + }); + + it('should fail', function *() { + sendFnStub.returns(Promise.reject(new Error('boom'))); + + try { + yield email.send(mailOptions); + } catch(e) { + expect(log.error.calledOnce).to.be.true; + expect(e.status).to.equal(500); + expect(e.message).to.match(/failed/); + } + }); + }); + +}); \ No newline at end of file