Fixed TXT content of report emails (#15090)

fixes https://github.com/TryGhost/Team/issues/1718

- Text content of report emails still had some placeholder text
- Converts HTML comments to TXT to include in the TXT version of the email
- Added support for Regexp matchers in the email mocker
- Added tests to check if the email content is in the new comment/report emails
This commit is contained in:
Simon Backx 2022-07-25 16:27:38 +02:00 committed by GitHub
parent 308a28d31a
commit 57a743e3aa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 30 additions and 14 deletions

View File

@ -1,6 +1,7 @@
const {promises: fs} = require('fs');
const path = require('path');
const moment = require('moment');
const htmlToPlaintext = require('../../../shared/html-to-plaintext');
class CommentsServiceEmails {
constructor({config, logging, models, mailer, settingsCache, urlService, urlUtils}) {
@ -128,7 +129,7 @@ class CommentsServiceEmails {
postTitle: post.get('title'),
postUrl: this.urlService.getUrlByResourceId(post.get('id'), {absolute: true}),
commentHtml: comment.get('html'),
commentText: 'todo: we need to convert html to text first!',
commentText: htmlToPlaintext.email(comment.get('html')),
commentDate: moment(comment.get('created_at')).tz(this.settingsCache.get('timezone')).format('D MMM YYYY'),
reporterName: reporter.name,

View File

@ -95,6 +95,7 @@ module.exports = class GhostMailer {
* @param {string} message.html - email content
* @param {string} message.to - email recipient address
* @param {string} [message.from] - sender email address
* @param {string} [message.text] - text version of this message
* @param {boolean} [message.forceTextContent] - maps to generateTextFromHTML nodemailer option
* which is: "if set to true uses HTML to generate plain text body part from the HTML if the text is not defined"
* (ref: https://github.com/nodemailer/nodemailer/tree/da2f1d278f91b4262e940c0b37638e7027184b1d#e-mail-message-fields)

View File

@ -40,7 +40,7 @@ Object {
Object {
"created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"edited_at": null,
"html": "This is a message",
"html": "<p>This is a message</p><p>New line</p>",
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"liked": Any<Boolean>,
"likes_count": Any<Number>,
@ -72,7 +72,7 @@ exports[`Comments API when authenticated Can browse all comments of a post 2: [h
Object {
"access-control-allow-origin": "*",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
"content-length": "1035",
"content-length": "1057",
"content-type": "application/json; charset=utf-8",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"vary": "Accept-Encoding",
@ -86,7 +86,7 @@ Object {
Object {
"created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"edited_at": null,
"html": "This is a message",
"html": "<p>This is a message</p><p>New line</p>",
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"liked": false,
"likes_count": 0,
@ -101,7 +101,7 @@ exports[`Comments API when authenticated Can comment on a post 2: [headers] 1`]
Object {
"access-control-allow-origin": "*",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
"content-length": "197",
"content-length": "219",
"content-type": "application/json; charset=utf-8",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"location": StringMatching /https\\?:\\\\/\\\\/\\.\\*\\?\\\\/comments\\\\/\\[a-f0-9\\]\\{24\\}\\\\//,
@ -169,7 +169,7 @@ Object {
Object {
"created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"edited_at": null,
"html": "This is a message",
"html": "<p>This is a message</p><p>New line</p>",
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"liked": Any<Boolean>,
"likes_count": Any<Number>,
@ -239,7 +239,7 @@ exports[`Comments API when authenticated Can like a comment 2: [headers] 1`] = `
Object {
"access-control-allow-origin": "*",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
"content-length": "626",
"content-length": "648",
"content-type": "application/json; charset=utf-8",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"vary": "Accept-Encoding",
@ -262,7 +262,7 @@ Object {
Object {
"created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"edited_at": null,
"html": "This is a message",
"html": "<p>This is a message</p><p>New line</p>",
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"liked": Any<Boolean>,
"likes_count": Any<Number>,
@ -301,7 +301,7 @@ exports[`Comments API when authenticated Can like a comment 5: [headers] 1`] = `
Object {
"access-control-allow-origin": "*",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
"content-length": "625",
"content-length": "647",
"content-type": "application/json; charset=utf-8",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"vary": "Accept-Encoding",
@ -473,7 +473,7 @@ Object {
Object {
"created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"edited_at": null,
"html": "This is a message",
"html": "<p>This is a message</p><p>New line</p>",
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"liked": Any<Boolean>,
"likes_count": Any<Number>,
@ -512,7 +512,7 @@ exports[`Comments API when authenticated Can remove a like 3: [headers] 1`] = `
Object {
"access-control-allow-origin": "*",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
"content-length": "626",
"content-length": "648",
"content-type": "application/json; charset=utf-8",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"vary": "Accept-Encoding",

View File

@ -40,6 +40,10 @@ async function sleep(ms) {
});
}
function escapeRegExp(string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
describe('Comments API', function () {
before(async function () {
membersAgent = await agentProvider.getMembersAPIAgent();
@ -101,7 +105,7 @@ describe('Comments API', function () {
.post(`/api/comments/`)
.body({comments: [{
post_id: postId,
html: 'This is a message'
html: '<p>This is a <strong>message</strong></p><p>New line</p>'
}]})
.expectStatus(201)
.matchHeaderSnapshot({
@ -121,7 +125,9 @@ describe('Comments API', function () {
mockManager.assert.sentEmailCount(1);
mockManager.assert.sentEmail({
subject: '💬 You have a new comment on one of your posts',
to: fixtureManager.get('users', 0).email
to: fixtureManager.get('users', 0).email,
// Note that the <strong> tag is removed by the sanitizer
html: new RegExp(escapeRegExp('<p>This is a message</p><p>New line</p>'))
});
});
@ -294,7 +300,9 @@ describe('Comments API', function () {
mockManager.assert.sentEmail({
subject: '🚩 A comment has been reported on your post',
to: fixtureManager.get('users', 0).email
to: fixtureManager.get('users', 0).email,
html: new RegExp(escapeRegExp('<p>This is a message</p><p>New line</p>')),
text: new RegExp(escapeRegExp('This is a message\n\nNew line'))
});
});

View File

@ -85,6 +85,12 @@ const sentEmail = (matchers) => {
// We use assert, rather than sinon.assert.calledWith, as we end up with much better error messaging
assert.notEqual(spyCall.args[0][key], undefined, `Expected email to have property ${key}`);
if (value instanceof RegExp) {
assert.match(spyCall.args[0][key], value, `Expected Email ${emailCount} to have ${key} that matches ${value}, got ${spyCall.args[0][key]}`);
return;
}
assert.equal(spyCall.args[0][key], value, `Expected Email ${emailCount} to have ${key} of ${value}`);
});
};