Merge pull request #100 from christroutner/dh-nodemailer-refactor

refactor(nodemailer): Refactored koa nodemailer
This commit is contained in:
Chris Troutner
2021-03-31 06:20:23 -08:00
committed by GitHub
4 changed files with 1346 additions and 20460 deletions
+1052 -20376
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -68,7 +68,7 @@
"husky": "^4.3.8",
"mocha": "^8.2.1",
"nyc": "^15.1.0",
"semantic-release": "^17.3.7",
"semantic-release": "^17.4.2",
"sinon": "^9.2.4",
"standard": "^16.0.3"
},
+66 -83
View File
@@ -1,5 +1,5 @@
/*
A library for controlling the sending email.
A library for controlling the sending of email.
*/
'use strict'
@@ -34,15 +34,6 @@ class NodeMailer {
return transporter
}
// Validate email
async validateEmail (email) {
// eslint-disable-next-line no-useless-escape
if (/^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/.test(email)) {
return true
}
return false
}
// Handles the sending of data via email.
async sendEmail (data) {
try {
@@ -50,10 +41,6 @@ class NodeMailer {
if (!data.email || typeof data.email !== 'string') {
throw new Error("Property 'email' must be a string!")
}
const isEmail = await _this.validateEmail(data.email)
if (!isEmail) {
throw new Error("Property 'email' must be email format!")
}
if (!data.to || !Array.isArray(data.to)) {
throw new Error("Property 'to' must be a array!")
@@ -62,66 +49,28 @@ class NodeMailer {
await _this.validateEmailArray(data.to)
if (!data.formMessage || typeof data.formMessage !== 'string') {
throw new Error("Property 'message' must be a string!")
throw new Error("Property 'formMessage' must be a string!")
}
if (!data.subject || typeof data.subject !== 'string') {
throw new Error("Property 'subject' must be a string!")
}
if (!data.payloadTitle || typeof data.payloadTitle !== 'string') {
throw new Error("Property 'payloadTitle' must be a string!")
// Use the provided html or use a default html generated from the input data
const html = data.htmlData || _this.getHtmlFromObject(data)
const sendObj = {
// from: `${data.email}`, // sender address
from: data.email,
to: data.to, // list of receivers
// subject: `Pearson ${subject}`, // Subject line
subject: data.subject,
// html: '<b>This is a test email</b>' // html body
html
}
const msg = data.formMessage.replace(/(\r\n|\n|\r)/g, '<br />')
const now = new Date()
const subject = data.subject
const to = data.to
// const emailUser = data.email // email from the user who initiated the sharing email
const payload = data.payloadTitle
const bodyJson = data
delete bodyJson.to
delete bodyJson.subject
delete bodyJson.formMessage
// Prototyping email output.
bodyJson.email = 'noreply@launchpadip.net'
delete bodyJson.email
delete bodyJson.emailList
delete bodyJson.payloadTitle
bodyJson.message = msg
// Html body
let htmlData = ''
// maps the object and converts it into html format
Object.keys(bodyJson).forEach(function (key) {
htmlData += `${key}: ${bodyJson[key]}<br/>`
})
// This paragraph just be added to the message for
// emails that are not password reset ones
const paragraphTag = 'This is a test email'
const htmlMsg = `<h3>${subject}:</h3>
${payload === 'Email reset' ? '' : paragraphTag}
<p>
time: ${now.toLocaleString()}<br/>
${htmlData}
</p>`
// send mail with defined transport object
const info = await _this.transporter.sendMail({
// from: `${data.email}`, // sender address
from: 'noreply@launchpadip.net',
to: `${to}`, // list of receivers
// subject: `Pearson ${subject}`, // Subject line
subject: subject,
// html: '<b>This is a test email</b>' // html body
html: htmlMsg
})
const info = await _this.transporter.sendMail(sendObj)
console.log('Message sent: %s', info.messageId)
return info
} catch (err) {
@@ -140,30 +89,64 @@ class NodeMailer {
throw new Error("Property 'emailList' cant be empty!")
}
// Iterates the array and validates each email format
const isValid = await new Promise(resolve => {
emailList.map(async (value, i) => {
const isEmail = await _this.validateEmail(value)
if (!isEmail) {
resolve(false)
}
if (i >= emailList.length - 1) {
resolve(true)
}
})
})
if (!isValid) {
throw new Error('Array must contain emails format!')
}
return true
} catch (err) {
wlogger.error('Error in lib/nodemailer.js/validateEmailArray()')
throw err
}
}
// get the email html from object
getHtmlFromObject (objectData) {
try {
if (!objectData || typeof objectData !== 'object') {
throw new Error("Property 'objectData' must be a object!")
}
if (!objectData.subject) {
throw new Error("Property 'subject' must be a string!")
}
if (!objectData.formMessage) {
throw new Error("Property 'formMessage' must be a string!")
}
const obj = {}
Object.assign(obj, objectData)
// neccesary data
const msg = obj.formMessage.replace(/(\r\n|\n|\r)/g, '<br />')
const now = new Date()
const subject = obj.subject
// Delete unneccesary data if it exist
delete obj.to
delete obj.subject
delete obj.from
delete obj.emailList
delete obj.formMessage
const bodyJson = obj
bodyJson.message = msg
// Html body
let htmlBody = ''
// maps the object and converts it into html format
Object.keys(bodyJson).forEach(function (key) {
htmlBody += `${key}: ${bodyJson[key]}<br/>`
})
const defaultHtmlData = `<h3>${subject}:</h3>
<p>
time: ${now.toLocaleString()}<br/>
${htmlBody}
</p>`
return defaultHtmlData
} catch (error) {
wlogger.error('Error in lib/nodemailer.js/getHtmlFromObject()')
throw error
}
}
}
module.exports = NodeMailer
@@ -0,0 +1,227 @@
/*
Unit tests for the nodemailer.js library.
*/
// Public npm libraries
const assert = require('chai').assert
const sinon = require('sinon')
const NodeMailer = require('../../../src/lib/nodemailer')
let sandbox
let uut
describe('NodeMailer', () => {
beforeEach(() => {
uut = new NodeMailer()
sandbox = sinon.createSandbox()
})
afterEach(() => sandbox.restore())
describe('sendEmail()', () => {
it('should throw error if email property is not provided', async () => {
try {
const data = {
formMessage: 'test msg',
name: 'test name',
subject: 'test subject',
to: ['test2@email.com']
}
await uut.sendEmail(data)
assert(false, 'Unexpected result')
} catch (err) {
assert.include(err.message, "Property 'email' must be a string!")
}
})
it('should throw error if formMessage property is not provided', async () => {
try {
const data = {
email: 'test@email.com',
name: 'test name',
subject: 'test subject',
to: ['test2@email.com']
}
await uut.sendEmail(data)
assert(false, 'Unexpected result')
} catch (err) {
assert.include(err.message, "Property 'formMessage' must be a string!")
}
})
it('should throw error if <to> property is not provided', async () => {
try {
const data = {
email: 'test@email.com',
name: 'test name',
subject: 'test subject'
}
await uut.sendEmail(data)
assert(false, 'Unexpected result')
} catch (err) {
assert.include(err.message, "Property 'to' must be a array!")
}
})
it('should throw error if <to> is wrong type', async () => {
try {
const data = {
email: 'test@email.com',
formMessage: 'test msg',
name: 'test name',
subject: 'test subject',
to: 'test'
}
await uut.sendEmail(data)
assert(false, 'Unexpected result')
} catch (err) {
assert.include(err.message, "Property 'to' must be a array!")
}
})
it('should throw error if subject Property is not provided', async () => {
try {
const data = {
email: 'test@email.com',
formMessage: 'test msg',
name: 'test name',
to: ['test2@email.com']
}
await uut.sendEmail(data)
assert(false, 'Unexpected result')
} catch (err) {
assert.include(err.message, "Property 'subject' must be a string!")
}
})
it('should send email with default html data', async () => {
try {
sandbox
.stub(uut.transporter, 'sendMail')
.resolves({ messageId: 'messageId' })
const data = {
email: 'test@email.com',
formMessage: 'test msg',
to: ['test2@email.com'],
subject: 'test subject'
}
const info = await uut.sendEmail(data)
assert.isObject(info)
assert.isString(info.messageId)
} catch (err) {
assert(false, 'Unexpected result')
}
})
it('should send email if htmlData is provided', async () => {
try {
sandbox
.stub(uut.transporter, 'sendMail')
.resolves({ messageId: 'messageId' })
const data = {
email: 'test@email.com',
formMessage: 'test msg',
name: 'test name',
to: ['test2@email.com'],
subject: 'test subject',
htmlData: '<p> Unit test </p>'
}
const info = await uut.sendEmail(data)
assert.isObject(info)
assert.isString(info.messageId)
} catch (err) {
assert(false, 'Unexpected result')
}
})
})
describe('validateEmailArray()', () => {
it('should throw error if email list is not provided ', async () => {
try {
await uut.validateEmailArray()
assert(false, 'Unexpected result')
} catch (err) {
assert.include(err.message, "Property 'emailList' must be a array!")
}
})
it('should throw error if email list is empty', async () => {
try {
const emailList = []
await uut.validateEmailArray(emailList)
assert(false, 'Unexpected result')
} catch (err) {
assert.include(err.message, "Property 'emailList' cant be empty!")
}
})
it('should return true ', async () => {
try {
const emailList = ['test@email.com', 'simple@email.com']
const result = await uut.validateEmailArray(emailList)
assert.isTrue(result)
} catch (err) {
assert(false, 'Unexpected result')
}
})
})
describe('getHtmlFromObject()', () => {
it('should throw error if the input is not provided ', async () => {
try {
await uut.getHtmlFromObject()
assert(false, 'Unexpected result')
} catch (err) {
assert.include(err.message, "Property 'objectData' must be a object!")
}
})
it('should throw error if the object is empty', async () => {
try {
await uut.getHtmlFromObject({})
assert(false, 'Unexpected result')
} catch (err) {
assert.include(err.message, "Property 'subject' must be a string!")
}
})
it('should throw error if "formMessage" property is not provided', async () => {
try {
const obj = {
subject: 'unit'
}
await uut.getHtmlFromObject(obj)
assert(false, 'Unexpected result')
} catch (err) {
assert.include(err.message, "Property 'formMessage' must be a string!")
}
})
it('should return the html', async () => {
try {
const obj = {
subject: 'unit ',
formMessage: 'test',
value1: 'value1',
value2: 'value2',
value3: 'value3'
}
const result = await uut.getHtmlFromObject(obj)
assert.isString(result)
assert.include(result, '<p>', 'expect html tag')
assert.include(result, '</p>', 'expect html tag')
assert.include(
result,
'value1',
'Expect value 1 is included in the html'
)
assert.include(result, 'value2', 'expect is included in the html')
assert.include(result, 'value3', 'expect is included in the html')
} catch (err) {
assert(false, 'Unexpected result')
}
})
})
})