Refactored email utils to a class with DI params

refs https://github.com/TryGhost/Toolbox/issues/292

- There's a need to reuse these utils in the version mismatch notification service. Having loads of tightly coupled dependencies makes it super hard to rip out this module for reuse
- It's a groundwork for extraction of the email-utils package
- Rewrote the unit tests that were written for these utils previously - they weren't testing anything useful. The goal of this util is to generate specific content based on provided data and available templates - now the tests do test those specific things, not the mailer itself!
This commit is contained in:
Naz 2022-05-04 15:08:35 +08:00 committed by naz
parent 499bb293c9
commit 551bd5e511
4 changed files with 103 additions and 74 deletions

View File

@ -0,0 +1,55 @@
const _ = require('lodash').runInContext();
const fs = require('fs-extra');
const path = require('path');
const htmlToText = require('html-to-text');
_.templateSettings.interpolate = /{{([\s\S]+?)}}/g;
class EmailContentGenerator {
/**
*
* @param {Object} options
* @param {function} options.getSiteUrl
* @param {function} options.getSiteTitle
* @param {string} options.templatesDir - path to the directory containing email templates
*/
constructor({getSiteUrl, getSiteTitle, templatesDir}) {
this.getSiteUrl = getSiteUrl;
this.getSiteTitle = getSiteTitle;
this.templatesDir = templatesDir;
}
/**
*
* @param {Object} options
* @param {string} options.template - HTML template name to use for generation
* @param {Object} [options.data] - variable data to use during HTML template compilation
* @returns {Promise} resolves with an object containing html and text properties
*/
async getContent(options) {
const defaults = {
siteUrl: this.getSiteUrl(),
siteTitle: this.getSiteTitle()
};
const data = _.defaults(defaults, options.data);
// read the proper email body template
return fs.readFile(path.join(this.templatesDir, options.template + '.html'), 'utf8')
.then(function (content) {
// insert user-specific data into the email
const compiled = _.template(content);
const htmlContent = compiled(data);
// generate a plain-text version of the same email
const textContent = htmlToText.fromString(htmlContent);
return {
html: htmlContent,
text: textContent
};
});
}
}
module.exports = EmailContentGenerator;

View File

@ -1,2 +1,15 @@
const path = require('path');
const urlUtils = require('../../../shared/url-utils');
const settingsCache = require('../../../shared/settings-cache');
const EmailContentGenerator = require('./EmailContentGenerator');
const emailContentGenerator = new EmailContentGenerator({
getSiteUrl: () => urlUtils.urlFor('home', true),
getSiteTitle: () => settingsCache.get('title'),
templatesDir: path.resolve(__dirname, '..', 'mail', 'templates')
});
exports.GhostMailer = require('./GhostMailer');
exports.utils = require('./utils');
exports.utils = {
generateContent: emailContentGenerator.getContent.bind(emailContentGenerator)
};

View File

@ -1,34 +0,0 @@
const _ = require('lodash').runInContext();
const fs = require('fs-extra');
const path = require('path');
const htmlToText = require('html-to-text');
const urlUtils = require('../../../shared/url-utils');
const settingsCache = require('../../../shared/settings-cache');
const templatesDir = path.resolve(__dirname, '..', 'mail', 'templates');
_.templateSettings.interpolate = /{{([\s\S]+?)}}/g;
exports.generateContent = function generateContent(options) {
const defaults = {
siteUrl: urlUtils.urlFor('home', true),
siteTitle: settingsCache.get('title')
};
const data = _.defaults(defaults, options.data);
// read the proper email body template
return fs.readFile(path.join(templatesDir, options.template + '.html'), 'utf8')
.then(function (content) {
// insert user-specific data into the email
const compiled = _.template(content);
const htmlContent = compiled(data);
// generate a plain-text version of the same email
const textContent = htmlToText.fromString(htmlContent);
return {
html: htmlContent,
text: textContent
};
});
};

View File

@ -1,41 +1,38 @@
const should = require('should');
const sinon = require('sinon');
const mail = require('../../../../../core/server/services/mail');
const configUtils = require('../../../../utils/configUtils');
const assert = require('assert');
const path = require('path');
describe('Mail: Utils', function () {
const scope = {ghostMailer: null};
const EmailContentGenerator = require('../../../../../core/server/services/mail/EmailContentGenerator');
beforeEach(function () {
configUtils.set({mail: {transport: 'stub'}});
scope.ghostMailer = new mail.GhostMailer();
});
describe('Mail: EmailContentGenerator', function () {
it('generate welcome', async function () {
const emailContentGenerator = new EmailContentGenerator({
getSiteTitle: () => 'The Ghost Blog',
getSiteUrl: () => 'http://myblog.com',
templatesDir: path.resolve(__dirname, '../../../../../core/server/services/mail/templates/')
});
afterEach(function () {
sinon.restore();
configUtils.restore();
});
it('generate welcome', function (done) {
mail.utils.generateContent({
const content = await emailContentGenerator.getContent({
template: 'welcome',
data: {
ownerEmail: 'test@example.com'
}
}).then(function (result) {
return scope.ghostMailer.send({
to: 'test@example.com',
subject: 'lol',
html: result.html,
text: result.text
});
}).then(function () {
done();
}).catch(done);
});
assert.match(content.html, /<title>Welcome to Ghost<\/title>/);
assert.match(content.html, /This email was sent from <a href="http:\/\/myblog.com" style="color: #738A94;">http:\/\/myblog.com<\/a> to <a href="mailto:test@example.com" style="color: #738A94;">test@example.com<\/a><\/p>/);
assert.match(content.text, /Email Address: test@example.com \[test@example.com\]/);
assert.match(content.text, /This email was sent from http:\/\/myblog.com/);
});
it('generates newsletter template', function (done) {
mail.utils.generateContent({
it('generates newsletter template', async function () {
const emailContentGenerator = new EmailContentGenerator({
getSiteTitle: () => 'The Ghost Blog',
getSiteUrl: () => 'http://myblog.com',
templatesDir: path.resolve(__dirname, '../../../../../core/server/services/mail/templates/')
});
const content = await emailContentGenerator.getContent({
template: 'newsletter',
data: {
blog: {
@ -93,15 +90,13 @@ describe('Mail: Utils', function () {
date: 'june, 9th 2016'
}
}
}).then(function (result) {
return scope.ghostMailer.send({
to: 'jbloggs@example.com',
subject: 'The Newsletter Blog',
html: result.html,
text: result.text
});
}).then(function () {
done();
}).catch(done);
});
assert.match(content.html, /<title>The Ghost Blog<\/title>/);
assert.match(content.html, /<span style="text-transform:capitalize">monthly<\/span> digest/);
assert.match(content.html, /<span style="text-transform:capitalize">june, 9th 2016<\/span><\/h3>/);
assert.match(content.text, /MONTHLY DIGEST — JUNE, 9TH 2016/);
assert.match(content.text, /SECOND BLOG POST \[HTTP:\/\/MYBLOG.COM\/SECOND-BLOG-POST\]/);
});
});