mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-02-03 23:00:14 -05:00
Added E2E Koenig Lexical card tests for paragraphs and signup card (#17159)
refs https://ghost.slack.com/archives/C04TMVA1D7A/p1687987311455289
This commit is contained in:
parent
a85d455d1b
commit
c41694f5e6
4 changed files with 348 additions and 192 deletions
|
@ -6,13 +6,12 @@ const sinon = require('sinon');
|
||||||
const assert = require('assert/strict');
|
const assert = require('assert/strict');
|
||||||
const jobManager = require('../../../../core/server/services/jobs/job-service');
|
const jobManager = require('../../../../core/server/services/jobs/job-service');
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const escapeRegExp = require('lodash/escapeRegExp');
|
|
||||||
const configUtils = require('../../../utils/configUtils');
|
const configUtils = require('../../../utils/configUtils');
|
||||||
const {settingsCache} = require('../../../../core/server/services/settings-helpers');
|
const {settingsCache} = require('../../../../core/server/services/settings-helpers');
|
||||||
const DomainEvents = require('@tryghost/domain-events');
|
const DomainEvents = require('@tryghost/domain-events');
|
||||||
const emailService = require('../../../../core/server/services/email-service');
|
const emailService = require('../../../../core/server/services/email-service');
|
||||||
const should = require('should');
|
|
||||||
const {mockSetting, stripeMocker} = require('../../../utils/e2e-framework-mock-manager');
|
const {mockSetting, stripeMocker} = require('../../../utils/e2e-framework-mock-manager');
|
||||||
|
const {sendEmail, createPublishedPostEmail, lastEmailMatchSnapshot, getDefaultNewsletter, retryEmail} = require('./utils');
|
||||||
|
|
||||||
const mobileDocExample = '{"version":"0.3.1","atoms":[],"cards":[],"markups":[],"sections":[[1,"p",[[0,[],0,"Hello world"]]]],"ghostVersion":"4.0"}';
|
const mobileDocExample = '{"version":"0.3.1","atoms":[],"cards":[],"markups":[],"sections":[[1,"p",[[0,[],0,"Hello world"]]]],"ghostVersion":"4.0"}';
|
||||||
const mobileDocWithPaywall = '{"version":"0.3.1","markups":[],"atoms":[],"cards":[["paywall",{}]],"sections":[[1,"p",[[0,[],0,"Free content"]]],[10,0],[1,"p",[[0,[],0,"Members content"]]]]}';
|
const mobileDocWithPaywall = '{"version":"0.3.1","markups":[],"atoms":[],"cards":[["paywall",{}]],"sections":[[1,"p",[[0,[],0,"Free content"]]],[10,0],[1,"p",[[0,[],0,"Members content"]]]]}';
|
||||||
|
@ -25,7 +24,6 @@ const mobileDocWithReplacements = '{"version":"0.3.1","atoms":[],"cards":[["emai
|
||||||
let agent;
|
let agent;
|
||||||
let stubbedSend;
|
let stubbedSend;
|
||||||
let frontendAgent;
|
let frontendAgent;
|
||||||
let lastEmailModel;
|
|
||||||
|
|
||||||
function sortBatches(a, b) {
|
function sortBatches(a, b) {
|
||||||
const aId = a.get('provider_id');
|
const aId = a.get('provider_id');
|
||||||
|
@ -39,165 +37,6 @@ function sortBatches(a, b) {
|
||||||
return aId.localeCompare(bId);
|
return aId.localeCompare(bId);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getDefaultNewsletter() {
|
|
||||||
const newsletterSlug = fixtureManager.get('newsletters', 0).slug;
|
|
||||||
return await models.Newsletter.findOne({slug: newsletterSlug});
|
|
||||||
}
|
|
||||||
|
|
||||||
let postCounter = 0;
|
|
||||||
|
|
||||||
async function createPublishedPostEmail(settings = {}, email_recipient_filter) {
|
|
||||||
const post = {
|
|
||||||
title: 'A random test post',
|
|
||||||
status: 'draft',
|
|
||||||
feature_image_alt: 'Testing sending',
|
|
||||||
feature_image_caption: 'Testing <b>feature image caption</b>',
|
|
||||||
created_at: moment().subtract(2, 'days').toISOString(),
|
|
||||||
updated_at: moment().subtract(2, 'days').toISOString(),
|
|
||||||
created_by: ObjectId().toHexString(),
|
|
||||||
updated_by: ObjectId().toHexString(),
|
|
||||||
...settings
|
|
||||||
};
|
|
||||||
|
|
||||||
const res = await agent.post('posts/')
|
|
||||||
.body({posts: [post]})
|
|
||||||
.expectStatus(201);
|
|
||||||
|
|
||||||
const id = res.body.posts[0].id;
|
|
||||||
|
|
||||||
// Make sure all posts are published in the samre order, with minimum 1s difference (to have consistent ordering when including latests posts)
|
|
||||||
postCounter += 1;
|
|
||||||
|
|
||||||
const updatedPost = {
|
|
||||||
status: 'published',
|
|
||||||
updated_at: res.body.posts[0].updated_at,
|
|
||||||
// Fixed publish date to make sure snapshots are consistent
|
|
||||||
published_at: moment(new Date(2050, 0, 1, 12, 0, postCounter)).toISOString()
|
|
||||||
};
|
|
||||||
|
|
||||||
const newsletterSlug = fixtureManager.get('newsletters', 0).slug;
|
|
||||||
await agent.put(`posts/${id}/?newsletter=${newsletterSlug}${email_recipient_filter ? `&email_segment=${email_recipient_filter}` : ''}`)
|
|
||||||
.body({posts: [updatedPost]})
|
|
||||||
.expectStatus(200);
|
|
||||||
|
|
||||||
const emailModel = await models.Email.findOne({
|
|
||||||
post_id: id
|
|
||||||
});
|
|
||||||
assert(!!emailModel);
|
|
||||||
|
|
||||||
return emailModel;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function sendEmail(settings, email_recipient_filter) {
|
|
||||||
// Prepare a post and email model
|
|
||||||
const completedPromise = jobManager.awaitCompletion('batch-sending-service-job');
|
|
||||||
const emailModel = await createPublishedPostEmail(settings, email_recipient_filter);
|
|
||||||
|
|
||||||
// Await sending job
|
|
||||||
await completedPromise;
|
|
||||||
|
|
||||||
await emailModel.refresh();
|
|
||||||
assert.equal(emailModel.get('status'), 'submitted');
|
|
||||||
|
|
||||||
lastEmailModel = emailModel;
|
|
||||||
|
|
||||||
// Get the email that was sent
|
|
||||||
return {emailModel, ...(await getLastEmail())};
|
|
||||||
}
|
|
||||||
|
|
||||||
async function retryEmail(emailId) {
|
|
||||||
await agent.put(`emails/${emailId}/retry`)
|
|
||||||
.expectStatus(200);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the last email that was sent via the stub, with all recipient variables replaced
|
|
||||||
*/
|
|
||||||
async function getLastEmail() {
|
|
||||||
// Get the email body
|
|
||||||
sinon.assert.called(stubbedSend);
|
|
||||||
const messageData = stubbedSend.lastArg;
|
|
||||||
let html = messageData.html;
|
|
||||||
let plaintext = messageData.text;
|
|
||||||
const recipientVariables = JSON.parse(messageData['recipient-variables']);
|
|
||||||
const recipientData = recipientVariables[Object.keys(recipientVariables)[0]];
|
|
||||||
|
|
||||||
for (const [key, value] of Object.entries(recipientData)) {
|
|
||||||
html = html.replace(new RegExp(`%recipient.${key}%`, 'g'), value);
|
|
||||||
plaintext = plaintext.replace(new RegExp(`%recipient.${key}%`, 'g'), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
emailModel: lastEmailModel,
|
|
||||||
...messageData,
|
|
||||||
html,
|
|
||||||
plaintext,
|
|
||||||
recipientData
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function testCleanedSnapshot(html, ignoreReplacements) {
|
|
||||||
for (const {match, replacement} of ignoreReplacements) {
|
|
||||||
if (match instanceof RegExp) {
|
|
||||||
html = html.replace(match, replacement);
|
|
||||||
} else {
|
|
||||||
html = html.replace(new RegExp(escapeRegExp(match), 'g'), replacement);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
should({html}).matchSnapshot();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function lastEmailMatchSnapshot() {
|
|
||||||
const lastEmail = await getLastEmail();
|
|
||||||
const defaultNewsletter = await lastEmail.emailModel.getLazyRelation('newsletter');
|
|
||||||
const linkRegexp = /http:\/\/127\.0\.0\.1:2369\/r\/\w+/g;
|
|
||||||
|
|
||||||
const ignoreReplacements = [
|
|
||||||
{
|
|
||||||
match: /\d{1,2}\s\w+\s\d{4}/g,
|
|
||||||
replacement: 'date'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
match: defaultNewsletter.get('uuid'),
|
|
||||||
replacement: 'requested-newsletter-uuid'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
match: lastEmail.emailModel.get('post_id'),
|
|
||||||
replacement: 'post-id'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
match: (await lastEmail.emailModel.getLazyRelation('post')).get('uuid'),
|
|
||||||
replacement: 'post-uuid'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
match: linkRegexp,
|
|
||||||
replacement: 'http://127.0.0.1:2369/r/xxxxxx'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
match: linkRegexp,
|
|
||||||
replacement: 'http://127.0.0.1:2369/r/xxxxxx'
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
if (lastEmail.recipientData.uuid) {
|
|
||||||
ignoreReplacements.push({
|
|
||||||
match: lastEmail.recipientData.uuid,
|
|
||||||
replacement: 'member-uuid'
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// Sometimes uuid is not used if link tracking is disabled
|
|
||||||
// Need to replace unsubscribe url instead (uuid is missing but it is inside the usubscribe url, causing snapshot updates)
|
|
||||||
// Need to use unshift to make replacement work before newsletter uuid
|
|
||||||
ignoreReplacements.unshift({
|
|
||||||
match: lastEmail.recipientData.unsubscribe_url,
|
|
||||||
replacement: 'unsubscribe_url'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
testCleanedSnapshot(lastEmail.html, ignoreReplacements);
|
|
||||||
testCleanedSnapshot(lastEmail.plaintext, ignoreReplacements);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test amount of batches and segmenting for a given email
|
* Test amount of batches and segmenting for a given email
|
||||||
*
|
*
|
||||||
|
@ -207,7 +46,7 @@ async function lastEmailMatchSnapshot() {
|
||||||
*/
|
*/
|
||||||
async function testEmailBatches(settings, email_recipient_filter, expectedBatches) {
|
async function testEmailBatches(settings, email_recipient_filter, expectedBatches) {
|
||||||
const completedPromise = jobManager.awaitCompletion('batch-sending-service-job');
|
const completedPromise = jobManager.awaitCompletion('batch-sending-service-job');
|
||||||
const emailModel = await createPublishedPostEmail(settings, email_recipient_filter);
|
const emailModel = await createPublishedPostEmail(agent, settings, email_recipient_filter);
|
||||||
|
|
||||||
assert.equal(emailModel.get('source_type'), 'mobiledoc');
|
assert.equal(emailModel.get('source_type'), 'mobiledoc');
|
||||||
assert(emailModel.get('subject'));
|
assert(emailModel.get('subject'));
|
||||||
|
@ -302,7 +141,7 @@ describe('Batch sending tests', function () {
|
||||||
|
|
||||||
it('Can send a scheduled post email', async function () {
|
it('Can send a scheduled post email', async function () {
|
||||||
// Prepare a post and email model
|
// Prepare a post and email model
|
||||||
const emailModel = await createPublishedPostEmail();
|
const emailModel = await createPublishedPostEmail(agent);
|
||||||
|
|
||||||
assert.equal(emailModel.get('source_type'), 'mobiledoc');
|
assert.equal(emailModel.get('source_type'), 'mobiledoc');
|
||||||
assert(emailModel.get('subject'));
|
assert(emailModel.get('subject'));
|
||||||
|
@ -346,7 +185,7 @@ describe('Batch sending tests', function () {
|
||||||
it('Protects the email job from being run multiple times at the same time', async function () {
|
it('Protects the email job from being run multiple times at the same time', async function () {
|
||||||
this.retries(1);
|
this.retries(1);
|
||||||
// Prepare a post and email model
|
// Prepare a post and email model
|
||||||
const emailModel = await createPublishedPostEmail();
|
const emailModel = await createPublishedPostEmail(agent);
|
||||||
|
|
||||||
assert.equal(emailModel.get('source_type'), 'mobiledoc');
|
assert.equal(emailModel.get('source_type'), 'mobiledoc');
|
||||||
assert(emailModel.get('subject'));
|
assert(emailModel.get('subject'));
|
||||||
|
@ -393,7 +232,7 @@ describe('Batch sending tests', function () {
|
||||||
|
|
||||||
// Prepare a post and email model
|
// Prepare a post and email model
|
||||||
const completedPromise = jobManager.awaitCompletion('batch-sending-service-job');
|
const completedPromise = jobManager.awaitCompletion('batch-sending-service-job');
|
||||||
const emailModel = await createPublishedPostEmail();
|
const emailModel = await createPublishedPostEmail(agent);
|
||||||
|
|
||||||
// Await sending job
|
// Await sending job
|
||||||
await completedPromise;
|
await completedPromise;
|
||||||
|
@ -420,7 +259,7 @@ describe('Batch sending tests', function () {
|
||||||
|
|
||||||
// Create a new email and see if it is included now
|
// Create a new email and see if it is included now
|
||||||
const completedPromise2 = jobManager.awaitCompletion('batch-sending-service-job');
|
const completedPromise2 = jobManager.awaitCompletion('batch-sending-service-job');
|
||||||
const emailModel2 = await createPublishedPostEmail();
|
const emailModel2 = await createPublishedPostEmail(agent);
|
||||||
await completedPromise2;
|
await completedPromise2;
|
||||||
await emailModel2.refresh();
|
await emailModel2.refresh();
|
||||||
assert.equal(emailModel2.get('email_count'), 5);
|
assert.equal(emailModel2.get('email_count'), 5);
|
||||||
|
@ -593,7 +432,7 @@ describe('Batch sending tests', function () {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Prepare a post and email model
|
// Prepare a post and email model
|
||||||
const emailModel = await createPublishedPostEmail();
|
const emailModel = await createPublishedPostEmail(agent);
|
||||||
|
|
||||||
assert.equal(emailModel.get('source_type'), 'mobiledoc');
|
assert.equal(emailModel.get('source_type'), 'mobiledoc');
|
||||||
assert(emailModel.get('subject'));
|
assert(emailModel.get('subject'));
|
||||||
|
@ -655,7 +494,7 @@ describe('Batch sending tests', function () {
|
||||||
let memberIds = emailRecipients.map(recipient => recipient.get('member_id'));
|
let memberIds = emailRecipients.map(recipient => recipient.get('member_id'));
|
||||||
assert.equal(memberIds.length, _.uniq(memberIds).length);
|
assert.equal(memberIds.length, _.uniq(memberIds).length);
|
||||||
|
|
||||||
await retryEmail(emailModel.id);
|
await retryEmail(agent, emailModel.id);
|
||||||
await jobManager.allSettled();
|
await jobManager.allSettled();
|
||||||
|
|
||||||
await emailModel.refresh();
|
await emailModel.refresh();
|
||||||
|
@ -756,7 +595,7 @@ describe('Batch sending tests', function () {
|
||||||
|
|
||||||
describe('Analytics', function () {
|
describe('Analytics', function () {
|
||||||
it('Adds link tracking to all links in a post', async function () {
|
it('Adds link tracking to all links in a post', async function () {
|
||||||
const {emailModel, html, plaintext, recipientData} = await sendEmail();
|
const {emailModel, html, plaintext, recipientData} = await sendEmail(agent);
|
||||||
const memberUuid = recipientData.uuid;
|
const memberUuid = recipientData.uuid;
|
||||||
const member = await models.Member.findOne({uuid: memberUuid});
|
const member = await models.Member.findOne({uuid: memberUuid});
|
||||||
|
|
||||||
|
@ -806,7 +645,7 @@ describe('Batch sending tests', function () {
|
||||||
it('Does not add outbound refs if disabled', async function () {
|
it('Does not add outbound refs if disabled', async function () {
|
||||||
mockManager.mockSetting('outbound_link_tagging', false);
|
mockManager.mockSetting('outbound_link_tagging', false);
|
||||||
|
|
||||||
const {emailModel, html} = await sendEmail();
|
const {emailModel, html} = await sendEmail(agent);
|
||||||
assert.match(html, /\m=/);
|
assert.match(html, /\m=/);
|
||||||
const links = await linkRedirectRepository.getAll({filter: 'post_id:' + emailModel.get('post_id')});
|
const links = await linkRedirectRepository.getAll({filter: 'post_id:' + emailModel.get('post_id')});
|
||||||
|
|
||||||
|
@ -821,7 +660,7 @@ describe('Batch sending tests', function () {
|
||||||
mockManager.mockLabsDisabled('outboundLinkTagging');
|
mockManager.mockLabsDisabled('outboundLinkTagging');
|
||||||
mockManager.mockSetting('outbound_link_tagging', false);
|
mockManager.mockSetting('outbound_link_tagging', false);
|
||||||
|
|
||||||
const {emailModel, html} = await sendEmail();
|
const {emailModel, html} = await sendEmail(agent);
|
||||||
assert.match(html, /\m=/);
|
assert.match(html, /\m=/);
|
||||||
const links = await linkRedirectRepository.getAll({filter: 'post_id:' + emailModel.get('post_id')});
|
const links = await linkRedirectRepository.getAll({filter: 'post_id:' + emailModel.get('post_id')});
|
||||||
|
|
||||||
|
@ -834,7 +673,7 @@ describe('Batch sending tests', function () {
|
||||||
it('Does not add link tracking if disabled', async function () {
|
it('Does not add link tracking if disabled', async function () {
|
||||||
mockManager.mockSetting('email_track_clicks', false);
|
mockManager.mockSetting('email_track_clicks', false);
|
||||||
|
|
||||||
const {emailModel, html} = await sendEmail();
|
const {emailModel, html} = await sendEmail(agent);
|
||||||
assert.doesNotMatch(html, /\m=/);
|
assert.doesNotMatch(html, /\m=/);
|
||||||
const links = await linkRedirectRepository.getAll({filter: 'post_id:' + emailModel.get('post_id')});
|
const links = await linkRedirectRepository.getAll({filter: 'post_id:' + emailModel.get('post_id')});
|
||||||
assert.equal(links.length, 0);
|
assert.equal(links.length, 0);
|
||||||
|
@ -852,7 +691,7 @@ describe('Batch sending tests', function () {
|
||||||
}]
|
}]
|
||||||
});
|
});
|
||||||
|
|
||||||
const {html, plaintext} = await sendEmail({
|
const {html, plaintext} = await sendEmail(agent, {
|
||||||
mobiledoc: mobileDocWithReplacements
|
mobiledoc: mobileDocWithReplacements
|
||||||
}, 'label:replacements-tests');
|
}, 'label:replacements-tests');
|
||||||
|
|
||||||
|
@ -885,7 +724,7 @@ describe('Batch sending tests', function () {
|
||||||
}]
|
}]
|
||||||
});
|
});
|
||||||
|
|
||||||
const {html, plaintext} = await sendEmail({
|
const {html, plaintext} = await sendEmail(agent, {
|
||||||
mobiledoc: mobileDocWithReplacements
|
mobiledoc: mobileDocWithReplacements
|
||||||
}, 'label:replacements-tests-2');
|
}, 'label:replacements-tests-2');
|
||||||
|
|
||||||
|
@ -909,7 +748,7 @@ describe('Batch sending tests', function () {
|
||||||
|
|
||||||
describe('HTML-content', function () {
|
describe('HTML-content', function () {
|
||||||
it('Does not HTML escape feature_image_caption', async function () {
|
it('Does not HTML escape feature_image_caption', async function () {
|
||||||
const {html, plaintext} = await sendEmail({
|
const {html, plaintext} = await sendEmail(agent, {
|
||||||
feature_image: 'https://example.com/image.jpg',
|
feature_image: 'https://example.com/image.jpg',
|
||||||
feature_image_caption: 'Testing <b>feature image caption</b>'
|
feature_image_caption: 'Testing <b>feature image caption</b>'
|
||||||
});
|
});
|
||||||
|
@ -928,7 +767,7 @@ describe('Batch sending tests', function () {
|
||||||
const defaultNewsletter = await getDefaultNewsletter();
|
const defaultNewsletter = await getDefaultNewsletter();
|
||||||
await models.Newsletter.edit({show_post_title_section: false}, {id: defaultNewsletter.id});
|
await models.Newsletter.edit({show_post_title_section: false}, {id: defaultNewsletter.id});
|
||||||
|
|
||||||
const {html, plaintext} = await sendEmail({
|
const {html, plaintext} = await sendEmail(agent, {
|
||||||
title: 'This is a test post title',
|
title: 'This is a test post title',
|
||||||
mobiledoc: mobileDocExample
|
mobiledoc: mobileDocExample
|
||||||
});
|
});
|
||||||
|
@ -943,7 +782,7 @@ describe('Batch sending tests', function () {
|
||||||
await models.Newsletter.edit({show_post_title_section: true}, {id: defaultNewsletter.id});
|
await models.Newsletter.edit({show_post_title_section: true}, {id: defaultNewsletter.id});
|
||||||
|
|
||||||
// Check does contain post title section
|
// Check does contain post title section
|
||||||
const {html: html2, plaintext: plaintext2} = await sendEmail({
|
const {html: html2, plaintext: plaintext2} = await sendEmail(agent, {
|
||||||
title: 'This is a test post title',
|
title: 'This is a test post title',
|
||||||
mobiledoc: mobileDocExample
|
mobiledoc: mobileDocExample
|
||||||
});
|
});
|
||||||
|
@ -962,7 +801,7 @@ describe('Batch sending tests', function () {
|
||||||
assert(defaultNewsletter.get('show_comment_cta'), 'show_comment_cta should be true for this test');
|
assert(defaultNewsletter.get('show_comment_cta'), 'show_comment_cta should be true for this test');
|
||||||
assert(!defaultNewsletter.get('feedback_enabled'), 'feedback_enabled should be off for this test');
|
assert(!defaultNewsletter.get('feedback_enabled'), 'feedback_enabled should be off for this test');
|
||||||
|
|
||||||
const {html} = await sendEmail({
|
const {html} = await sendEmail(agent, {
|
||||||
title: 'This is a test post title',
|
title: 'This is a test post title',
|
||||||
mobiledoc: mobileDocExample
|
mobiledoc: mobileDocExample
|
||||||
});
|
});
|
||||||
|
@ -980,7 +819,7 @@ describe('Batch sending tests', function () {
|
||||||
assert(defaultNewsletter.get('show_comment_cta'), 'show_comment_cta should be true for this test');
|
assert(defaultNewsletter.get('show_comment_cta'), 'show_comment_cta should be true for this test');
|
||||||
await models.Newsletter.edit({feedback_enabled: true}, {id: defaultNewsletter.id});
|
await models.Newsletter.edit({feedback_enabled: true}, {id: defaultNewsletter.id});
|
||||||
|
|
||||||
const {html} = await sendEmail({
|
const {html} = await sendEmail(agent, {
|
||||||
title: 'This is a test post title',
|
title: 'This is a test post title',
|
||||||
mobiledoc: mobileDocExample
|
mobiledoc: mobileDocExample
|
||||||
});
|
});
|
||||||
|
@ -1000,7 +839,7 @@ describe('Batch sending tests', function () {
|
||||||
const defaultNewsletter = await getDefaultNewsletter();
|
const defaultNewsletter = await getDefaultNewsletter();
|
||||||
assert(defaultNewsletter.get('show_comment_cta'), 'show_comment_cta should be true for this test');
|
assert(defaultNewsletter.get('show_comment_cta'), 'show_comment_cta should be true for this test');
|
||||||
|
|
||||||
const {html} = await sendEmail({
|
const {html} = await sendEmail(agent, {
|
||||||
title: 'This is a test post title',
|
title: 'This is a test post title',
|
||||||
mobiledoc: mobileDocExample,
|
mobiledoc: mobileDocExample,
|
||||||
email_only: true
|
email_only: true
|
||||||
|
@ -1018,7 +857,7 @@ describe('Batch sending tests', function () {
|
||||||
const defaultNewsletter = await getDefaultNewsletter();
|
const defaultNewsletter = await getDefaultNewsletter();
|
||||||
assert(defaultNewsletter.get('show_comment_cta'), 'show_comment_cta should be true for this test');
|
assert(defaultNewsletter.get('show_comment_cta'), 'show_comment_cta should be true for this test');
|
||||||
|
|
||||||
const {html} = await sendEmail({
|
const {html} = await sendEmail(agent, {
|
||||||
title: 'This is a test post title',
|
title: 'This is a test post title',
|
||||||
mobiledoc: mobileDocExample
|
mobiledoc: mobileDocExample
|
||||||
});
|
});
|
||||||
|
@ -1034,7 +873,7 @@ describe('Batch sending tests', function () {
|
||||||
const defaultNewsletter = await getDefaultNewsletter();
|
const defaultNewsletter = await getDefaultNewsletter();
|
||||||
await models.Newsletter.edit({show_comment_cta: false}, {id: defaultNewsletter.id});
|
await models.Newsletter.edit({show_comment_cta: false}, {id: defaultNewsletter.id});
|
||||||
|
|
||||||
const {html} = await sendEmail({
|
const {html} = await sendEmail(agent, {
|
||||||
title: 'This is a test post title',
|
title: 'This is a test post title',
|
||||||
mobiledoc: mobileDocExample
|
mobiledoc: mobileDocExample
|
||||||
});
|
});
|
||||||
|
@ -1062,7 +901,7 @@ describe('Batch sending tests', function () {
|
||||||
const defaultNewsletter = await getDefaultNewsletter();
|
const defaultNewsletter = await getDefaultNewsletter();
|
||||||
await models.Newsletter.edit({show_subscription_details: true}, {id: defaultNewsletter.id});
|
await models.Newsletter.edit({show_subscription_details: true}, {id: defaultNewsletter.id});
|
||||||
|
|
||||||
const {html, plaintext} = await sendEmail({
|
const {html, plaintext} = await sendEmail(agent, {
|
||||||
title: 'This is a test post title',
|
title: 'This is a test post title',
|
||||||
mobiledoc: mobileDocExample
|
mobiledoc: mobileDocExample
|
||||||
}, 'label:subscription-box-tests');
|
}, 'label:subscription-box-tests');
|
||||||
|
@ -1095,7 +934,7 @@ describe('Batch sending tests', function () {
|
||||||
const defaultNewsletter = await getDefaultNewsletter();
|
const defaultNewsletter = await getDefaultNewsletter();
|
||||||
await models.Newsletter.edit({show_subscription_details: true}, {id: defaultNewsletter.id});
|
await models.Newsletter.edit({show_subscription_details: true}, {id: defaultNewsletter.id});
|
||||||
|
|
||||||
const {html, plaintext} = await sendEmail({
|
const {html, plaintext} = await sendEmail(agent, {
|
||||||
title: 'This is a test post title',
|
title: 'This is a test post title',
|
||||||
mobiledoc: mobileDocExample
|
mobiledoc: mobileDocExample
|
||||||
}, 'label:subscription-box-comped-tests');
|
}, 'label:subscription-box-comped-tests');
|
||||||
|
@ -1136,7 +975,7 @@ describe('Batch sending tests', function () {
|
||||||
const defaultNewsletter = await getDefaultNewsletter();
|
const defaultNewsletter = await getDefaultNewsletter();
|
||||||
await models.Newsletter.edit({show_subscription_details: true}, {id: defaultNewsletter.id});
|
await models.Newsletter.edit({show_subscription_details: true}, {id: defaultNewsletter.id});
|
||||||
|
|
||||||
const {html, plaintext} = await sendEmail({
|
const {html, plaintext} = await sendEmail(agent, {
|
||||||
title: 'This is a test post title',
|
title: 'This is a test post title',
|
||||||
mobiledoc: mobileDocExample
|
mobiledoc: mobileDocExample
|
||||||
}, 'label:subscription-box-trialing-tests');
|
}, 'label:subscription-box-trialing-tests');
|
||||||
|
@ -1177,7 +1016,7 @@ describe('Batch sending tests', function () {
|
||||||
const defaultNewsletter = await getDefaultNewsletter();
|
const defaultNewsletter = await getDefaultNewsletter();
|
||||||
await models.Newsletter.edit({show_subscription_details: true}, {id: defaultNewsletter.id});
|
await models.Newsletter.edit({show_subscription_details: true}, {id: defaultNewsletter.id});
|
||||||
|
|
||||||
const {html, plaintext} = await sendEmail({
|
const {html, plaintext} = await sendEmail(agent, {
|
||||||
title: 'This is a test post title',
|
title: 'This is a test post title',
|
||||||
mobiledoc: mobileDocExample
|
mobiledoc: mobileDocExample
|
||||||
}, 'label:subscription-box-paid-tests');
|
}, 'label:subscription-box-paid-tests');
|
||||||
|
@ -1219,7 +1058,7 @@ describe('Batch sending tests', function () {
|
||||||
const defaultNewsletter = await getDefaultNewsletter();
|
const defaultNewsletter = await getDefaultNewsletter();
|
||||||
await models.Newsletter.edit({show_subscription_details: true}, {id: defaultNewsletter.id});
|
await models.Newsletter.edit({show_subscription_details: true}, {id: defaultNewsletter.id});
|
||||||
|
|
||||||
const {html, plaintext} = await sendEmail({
|
const {html, plaintext} = await sendEmail(agent, {
|
||||||
title: 'This is a test post title',
|
title: 'This is a test post title',
|
||||||
mobiledoc: mobileDocExample
|
mobiledoc: mobileDocExample
|
||||||
}, 'label:subscription-box-canceled-tests');
|
}, 'label:subscription-box-canceled-tests');
|
||||||
|
@ -1240,7 +1079,7 @@ describe('Batch sending tests', function () {
|
||||||
const defaultNewsletter = await getDefaultNewsletter();
|
const defaultNewsletter = await getDefaultNewsletter();
|
||||||
await models.Newsletter.edit({show_latest_posts: true}, {id: defaultNewsletter.id});
|
await models.Newsletter.edit({show_latest_posts: true}, {id: defaultNewsletter.id});
|
||||||
|
|
||||||
const {html} = await sendEmail({
|
const {html} = await sendEmail(agent, {
|
||||||
title: 'This is the main post title',
|
title: 'This is the main post title',
|
||||||
mobiledoc: mobileDocExample
|
mobiledoc: mobileDocExample
|
||||||
});
|
});
|
||||||
|
|
132
ghost/core/test/integration/services/email-service/cards.test.js
Normal file
132
ghost/core/test/integration/services/email-service/cards.test.js
Normal file
|
@ -0,0 +1,132 @@
|
||||||
|
const {agentProvider, fixtureManager, mockManager} = require('../../../utils/e2e-framework');
|
||||||
|
const models = require('../../../../core/server/models');
|
||||||
|
const assert = require('assert/strict');
|
||||||
|
const configUtils = require('../../../utils/configUtils');
|
||||||
|
const {sendEmail} = require('./utils');
|
||||||
|
const cheerio = require('cheerio');
|
||||||
|
|
||||||
|
function splitPreheader(data) {
|
||||||
|
// Remove the preheader span from the email using cheerio
|
||||||
|
const $ = cheerio.load(data.html);
|
||||||
|
const preheader = $('.preheader');
|
||||||
|
data.preheader = preheader.html();
|
||||||
|
preheader.remove();
|
||||||
|
data.html = $.html();
|
||||||
|
}
|
||||||
|
|
||||||
|
function createParagraphCard(text = 'Hello world.') {
|
||||||
|
return {
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
detail: 0,
|
||||||
|
format: 0,
|
||||||
|
mode: 'normal',
|
||||||
|
style: '',
|
||||||
|
text,
|
||||||
|
type: 'text',
|
||||||
|
version: 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
direction: 'ltr',
|
||||||
|
format: '',
|
||||||
|
indent: 0,
|
||||||
|
type: 'paragraph',
|
||||||
|
version: 1
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function createLexicalJson(cards = []) {
|
||||||
|
return JSON.stringify({
|
||||||
|
root: {
|
||||||
|
children: [
|
||||||
|
...cards
|
||||||
|
],
|
||||||
|
direction: 'ltr',
|
||||||
|
format: '',
|
||||||
|
indent: 0,
|
||||||
|
type: 'root',
|
||||||
|
version: 1
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let agent;
|
||||||
|
|
||||||
|
describe('Can send cards via email', function () {
|
||||||
|
beforeEach(function () {
|
||||||
|
mockManager.mockMail();
|
||||||
|
mockManager.mockMailgun();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async function () {
|
||||||
|
await configUtils.restore();
|
||||||
|
await models.Settings.edit([{
|
||||||
|
key: 'email_verification_required',
|
||||||
|
value: false
|
||||||
|
}], {context: {internal: true}});
|
||||||
|
mockManager.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
before(async function () {
|
||||||
|
agent = await agentProvider.getAdminAPIAgent();
|
||||||
|
|
||||||
|
await fixtureManager.init('newsletters', 'members:newsletters');
|
||||||
|
await agent.loginAsOwner();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Paragraphs', async function () {
|
||||||
|
const data = await sendEmail(agent, {
|
||||||
|
lexical: createLexicalJson([
|
||||||
|
createParagraphCard('Hello world.')
|
||||||
|
])
|
||||||
|
});
|
||||||
|
|
||||||
|
// Remove the preheader span from the email using cheerio
|
||||||
|
splitPreheader(data);
|
||||||
|
|
||||||
|
// Check our html and plaintexct contain the paragraph
|
||||||
|
assert.ok(data.html.includes('Hello world.'));
|
||||||
|
assert.ok(data.plaintext.includes('Hello world.'));
|
||||||
|
assert.ok(data.preheader.includes('Hello world.'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Signup Card', async function () {
|
||||||
|
const data = await sendEmail(agent, {
|
||||||
|
lexical: createLexicalJson([
|
||||||
|
createParagraphCard('This is a paragraph'),
|
||||||
|
{
|
||||||
|
type: 'signup',
|
||||||
|
version: 1,
|
||||||
|
alignment: 'left',
|
||||||
|
backgroundColor: '#F0F0F0',
|
||||||
|
backgroundImageSrc: '',
|
||||||
|
backgroundSize: 'cover',
|
||||||
|
textColor: '#000000',
|
||||||
|
buttonColor: 'accent',
|
||||||
|
buttonTextColor: '#FFFFFF',
|
||||||
|
buttonText: 'Subscribe',
|
||||||
|
disclaimer: '<span>No spam. Unsubscribe anytime.</span>',
|
||||||
|
header: '<span>Sign up for Koenig Lexical</span>',
|
||||||
|
labels: [],
|
||||||
|
layout: 'wide',
|
||||||
|
subheader: '<span>There\'s a whole lot to discover in this editor. Let us help you settle in.</span>',
|
||||||
|
successMessage: 'Email sent! Check your inbox to complete your signup.',
|
||||||
|
swapped: false
|
||||||
|
}
|
||||||
|
])
|
||||||
|
});
|
||||||
|
|
||||||
|
splitPreheader(data);
|
||||||
|
|
||||||
|
// Check the plaintext does contain the paragraph, but doesn't contain the signup card
|
||||||
|
assert.ok(!data.html.includes('Sign up for Koenig Lexical'));
|
||||||
|
|
||||||
|
// This is a bug! The plaintext and preheader should not contain the signup card
|
||||||
|
//assert.ok(!data.plaintext.includes('Sign up for Koenig Lexical'));
|
||||||
|
//assert.ok(!data.preheader.includes('Sign up for Koenig Lexical'));
|
||||||
|
|
||||||
|
assert.ok(data.html.includes('This is a paragraph'));
|
||||||
|
assert.ok(data.plaintext.includes('This is a paragraph'));
|
||||||
|
assert.ok(data.preheader.includes('This is a paragraph'));
|
||||||
|
});
|
||||||
|
});
|
179
ghost/core/test/integration/services/email-service/utils.js
Normal file
179
ghost/core/test/integration/services/email-service/utils.js
Normal file
|
@ -0,0 +1,179 @@
|
||||||
|
const {fixtureManager, mockManager} = require('../../../utils/e2e-framework');
|
||||||
|
const moment = require('moment');
|
||||||
|
const ObjectId = require('bson-objectid').default;
|
||||||
|
const models = require('../../../../core/server/models');
|
||||||
|
const sinon = require('sinon');
|
||||||
|
const jobManager = require('../../../../core/server/services/jobs/job-service');
|
||||||
|
const escapeRegExp = require('lodash/escapeRegExp');
|
||||||
|
const should = require('should');
|
||||||
|
const assert = require('assert/strict');
|
||||||
|
|
||||||
|
const getDefaultNewsletter = async function () {
|
||||||
|
const newsletterSlug = fixtureManager.get('newsletters', 0).slug;
|
||||||
|
return await models.Newsletter.findOne({slug: newsletterSlug});
|
||||||
|
};
|
||||||
|
|
||||||
|
let postCounter = 0;
|
||||||
|
|
||||||
|
async function createPublishedPostEmail(agent, settings = {}, email_recipient_filter) {
|
||||||
|
const post = {
|
||||||
|
title: 'A random test post',
|
||||||
|
status: 'draft',
|
||||||
|
feature_image_alt: 'Testing sending',
|
||||||
|
feature_image_caption: 'Testing <b>feature image caption</b>',
|
||||||
|
created_at: moment().subtract(2, 'days').toISOString(),
|
||||||
|
updated_at: moment().subtract(2, 'days').toISOString(),
|
||||||
|
created_by: ObjectId().toHexString(),
|
||||||
|
updated_by: ObjectId().toHexString(),
|
||||||
|
...settings
|
||||||
|
};
|
||||||
|
|
||||||
|
const res = await agent.post('posts/')
|
||||||
|
.body({posts: [post]})
|
||||||
|
.expectStatus(201);
|
||||||
|
|
||||||
|
const id = res.body.posts[0].id;
|
||||||
|
|
||||||
|
// Make sure all posts are published in the samre order, with minimum 1s difference (to have consistent ordering when including latests posts)
|
||||||
|
postCounter += 1;
|
||||||
|
|
||||||
|
const updatedPost = {
|
||||||
|
status: 'published',
|
||||||
|
updated_at: res.body.posts[0].updated_at,
|
||||||
|
// Fixed publish date to make sure snapshots are consistent
|
||||||
|
published_at: moment(new Date(2050, 0, 1, 12, 0, postCounter)).toISOString()
|
||||||
|
};
|
||||||
|
|
||||||
|
const newsletterSlug = fixtureManager.get('newsletters', 0).slug;
|
||||||
|
await agent.put(`posts/${id}/?newsletter=${newsletterSlug}${email_recipient_filter ? `&email_segment=${email_recipient_filter}` : ''}`)
|
||||||
|
.body({posts: [updatedPost]})
|
||||||
|
.expectStatus(200);
|
||||||
|
|
||||||
|
const emailModel = await models.Email.findOne({
|
||||||
|
post_id: id
|
||||||
|
});
|
||||||
|
assert(!!emailModel);
|
||||||
|
|
||||||
|
return emailModel;
|
||||||
|
}
|
||||||
|
let lastEmailModel;
|
||||||
|
|
||||||
|
async function sendEmail(agent, settings, email_recipient_filter) {
|
||||||
|
// Prepare a post and email model
|
||||||
|
const completedPromise = jobManager.awaitCompletion('batch-sending-service-job');
|
||||||
|
const emailModel = await createPublishedPostEmail(agent, settings, email_recipient_filter);
|
||||||
|
|
||||||
|
// Await sending job
|
||||||
|
await completedPromise;
|
||||||
|
|
||||||
|
await emailModel.refresh();
|
||||||
|
assert.equal(emailModel.get('status'), 'submitted');
|
||||||
|
|
||||||
|
lastEmailModel = emailModel;
|
||||||
|
|
||||||
|
// Get the email that was sent
|
||||||
|
return {emailModel, ...(await getLastEmail())};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function retryEmail(agent, emailId) {
|
||||||
|
await agent.put(`emails/${emailId}/retry`)
|
||||||
|
.expectStatus(200);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the last email that was sent via the stub, with all recipient variables replaced
|
||||||
|
*/
|
||||||
|
async function getLastEmail() {
|
||||||
|
const mailgunCreateMessageStub = mockManager.getMailgunCreateMessageStub();
|
||||||
|
assert.ok(mailgunCreateMessageStub);
|
||||||
|
sinon.assert.called(mailgunCreateMessageStub);
|
||||||
|
|
||||||
|
const messageData = mailgunCreateMessageStub.lastCall.lastArg;
|
||||||
|
let html = messageData.html;
|
||||||
|
let plaintext = messageData.text;
|
||||||
|
const recipientVariables = JSON.parse(messageData['recipient-variables']);
|
||||||
|
const recipientData = recipientVariables[Object.keys(recipientVariables)[0]];
|
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(recipientData)) {
|
||||||
|
html = html.replace(new RegExp(`%recipient.${key}%`, 'g'), value);
|
||||||
|
plaintext = plaintext.replace(new RegExp(`%recipient.${key}%`, 'g'), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
emailModel: lastEmailModel,
|
||||||
|
...messageData,
|
||||||
|
html,
|
||||||
|
plaintext,
|
||||||
|
recipientData
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function testCleanedSnapshot(html, ignoreReplacements) {
|
||||||
|
for (const {match, replacement} of ignoreReplacements) {
|
||||||
|
if (match instanceof RegExp) {
|
||||||
|
html = html.replace(match, replacement);
|
||||||
|
} else {
|
||||||
|
html = html.replace(new RegExp(escapeRegExp(match), 'g'), replacement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
should({html}).matchSnapshot();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function lastEmailMatchSnapshot() {
|
||||||
|
const lastEmail = await getLastEmail();
|
||||||
|
const defaultNewsletter = await lastEmail.emailModel.getLazyRelation('newsletter');
|
||||||
|
const linkRegexp = /http:\/\/127\.0\.0\.1:2369\/r\/\w+/g;
|
||||||
|
|
||||||
|
const ignoreReplacements = [
|
||||||
|
{
|
||||||
|
match: /\d{1,2}\s\w+\s\d{4}/g,
|
||||||
|
replacement: 'date'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
match: defaultNewsletter.get('uuid'),
|
||||||
|
replacement: 'requested-newsletter-uuid'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
match: lastEmail.emailModel.get('post_id'),
|
||||||
|
replacement: 'post-id'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
match: (await lastEmail.emailModel.getLazyRelation('post')).get('uuid'),
|
||||||
|
replacement: 'post-uuid'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
match: linkRegexp,
|
||||||
|
replacement: 'http://127.0.0.1:2369/r/xxxxxx'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
match: linkRegexp,
|
||||||
|
replacement: 'http://127.0.0.1:2369/r/xxxxxx'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
if (lastEmail.recipientData.uuid) {
|
||||||
|
ignoreReplacements.push({
|
||||||
|
match: lastEmail.recipientData.uuid,
|
||||||
|
replacement: 'member-uuid'
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Sometimes uuid is not used if link tracking is disabled
|
||||||
|
// Need to replace unsubscribe url instead (uuid is missing but it is inside the usubscribe url, causing snapshot updates)
|
||||||
|
// Need to use unshift to make replacement work before newsletter uuid
|
||||||
|
ignoreReplacements.unshift({
|
||||||
|
match: lastEmail.recipientData.unsubscribe_url,
|
||||||
|
replacement: 'unsubscribe_url'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
testCleanedSnapshot(lastEmail.html, ignoreReplacements);
|
||||||
|
testCleanedSnapshot(lastEmail.plaintext, ignoreReplacements);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
getDefaultNewsletter,
|
||||||
|
createPublishedPostEmail,
|
||||||
|
sendEmail,
|
||||||
|
retryEmail,
|
||||||
|
lastEmailMatchSnapshot
|
||||||
|
};
|
|
@ -112,12 +112,17 @@ const mockMail = (response = 'Mail is disabled') => {
|
||||||
return mockMailReceiver;
|
return mockMailReceiver;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A reference to the send method when MailGun is mocked (required for some tests)
|
||||||
|
*/
|
||||||
|
let mailgunCreateMessageStub;
|
||||||
|
|
||||||
const mockMailgun = (customStubbedSend) => {
|
const mockMailgun = (customStubbedSend) => {
|
||||||
mockSetting('mailgun_api_key', 'test');
|
mockSetting('mailgun_api_key', 'test');
|
||||||
mockSetting('mailgun_domain', 'example.com');
|
mockSetting('mailgun_domain', 'example.com');
|
||||||
mockSetting('mailgun_base_url', 'test');
|
mockSetting('mailgun_base_url', 'test');
|
||||||
|
|
||||||
const stubbedSend = customStubbedSend ?? sinon.fake.resolves({
|
mailgunCreateMessageStub = customStubbedSend ? sinon.stub().callsFake(customStubbedSend) : sinon.fake.resolves({
|
||||||
id: `<${new Date().getTime()}.${0}.5817@samples.mailgun.org>`
|
id: `<${new Date().getTime()}.${0}.5817@samples.mailgun.org>`
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -126,7 +131,7 @@ const mockMailgun = (customStubbedSend) => {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
messages: {
|
messages: {
|
||||||
create: async function () {
|
create: async function () {
|
||||||
return await stubbedSend.call(this, ...arguments);
|
return await mailgunCreateMessageStub.call(this, ...arguments);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -300,5 +305,6 @@ module.exports = {
|
||||||
sentEmailCount,
|
sentEmailCount,
|
||||||
sentEmail,
|
sentEmail,
|
||||||
emittedEvent
|
emittedEvent
|
||||||
}
|
},
|
||||||
|
getMailgunCreateMessageStub: () => mailgunCreateMessageStub
|
||||||
};
|
};
|
||||||
|
|
Loading…
Add table
Reference in a new issue