mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-11-27 10:42:45 +03: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
@ -6,13 +6,12 @@ const sinon = require('sinon');
|
||||
const assert = require('assert/strict');
|
||||
const jobManager = require('../../../../core/server/services/jobs/job-service');
|
||||
const _ = require('lodash');
|
||||
const escapeRegExp = require('lodash/escapeRegExp');
|
||||
const configUtils = require('../../../utils/configUtils');
|
||||
const {settingsCache} = require('../../../../core/server/services/settings-helpers');
|
||||
const DomainEvents = require('@tryghost/domain-events');
|
||||
const emailService = require('../../../../core/server/services/email-service');
|
||||
const should = require('should');
|
||||
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 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 stubbedSend;
|
||||
let frontendAgent;
|
||||
let lastEmailModel;
|
||||
|
||||
function sortBatches(a, b) {
|
||||
const aId = a.get('provider_id');
|
||||
@ -39,165 +37,6 @@ function sortBatches(a, b) {
|
||||
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
|
||||
*
|
||||
@ -207,7 +46,7 @@ async function lastEmailMatchSnapshot() {
|
||||
*/
|
||||
async function testEmailBatches(settings, email_recipient_filter, expectedBatches) {
|
||||
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(emailModel.get('subject'));
|
||||
@ -302,7 +141,7 @@ describe('Batch sending tests', function () {
|
||||
|
||||
it('Can send a scheduled post email', async function () {
|
||||
// Prepare a post and email model
|
||||
const emailModel = await createPublishedPostEmail();
|
||||
const emailModel = await createPublishedPostEmail(agent);
|
||||
|
||||
assert.equal(emailModel.get('source_type'), 'mobiledoc');
|
||||
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 () {
|
||||
this.retries(1);
|
||||
// Prepare a post and email model
|
||||
const emailModel = await createPublishedPostEmail();
|
||||
const emailModel = await createPublishedPostEmail(agent);
|
||||
|
||||
assert.equal(emailModel.get('source_type'), 'mobiledoc');
|
||||
assert(emailModel.get('subject'));
|
||||
@ -393,7 +232,7 @@ describe('Batch sending tests', function () {
|
||||
|
||||
// Prepare a post and email model
|
||||
const completedPromise = jobManager.awaitCompletion('batch-sending-service-job');
|
||||
const emailModel = await createPublishedPostEmail();
|
||||
const emailModel = await createPublishedPostEmail(agent);
|
||||
|
||||
// Await sending job
|
||||
await completedPromise;
|
||||
@ -420,7 +259,7 @@ describe('Batch sending tests', function () {
|
||||
|
||||
// Create a new email and see if it is included now
|
||||
const completedPromise2 = jobManager.awaitCompletion('batch-sending-service-job');
|
||||
const emailModel2 = await createPublishedPostEmail();
|
||||
const emailModel2 = await createPublishedPostEmail(agent);
|
||||
await completedPromise2;
|
||||
await emailModel2.refresh();
|
||||
assert.equal(emailModel2.get('email_count'), 5);
|
||||
@ -593,7 +432,7 @@ describe('Batch sending tests', function () {
|
||||
};
|
||||
|
||||
// Prepare a post and email model
|
||||
const emailModel = await createPublishedPostEmail();
|
||||
const emailModel = await createPublishedPostEmail(agent);
|
||||
|
||||
assert.equal(emailModel.get('source_type'), 'mobiledoc');
|
||||
assert(emailModel.get('subject'));
|
||||
@ -655,7 +494,7 @@ describe('Batch sending tests', function () {
|
||||
let memberIds = emailRecipients.map(recipient => recipient.get('member_id'));
|
||||
assert.equal(memberIds.length, _.uniq(memberIds).length);
|
||||
|
||||
await retryEmail(emailModel.id);
|
||||
await retryEmail(agent, emailModel.id);
|
||||
await jobManager.allSettled();
|
||||
|
||||
await emailModel.refresh();
|
||||
@ -756,7 +595,7 @@ describe('Batch sending tests', function () {
|
||||
|
||||
describe('Analytics', 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 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 () {
|
||||
mockManager.mockSetting('outbound_link_tagging', false);
|
||||
|
||||
const {emailModel, html} = await sendEmail();
|
||||
const {emailModel, html} = await sendEmail(agent);
|
||||
assert.match(html, /\m=/);
|
||||
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.mockSetting('outbound_link_tagging', false);
|
||||
|
||||
const {emailModel, html} = await sendEmail();
|
||||
const {emailModel, html} = await sendEmail(agent);
|
||||
assert.match(html, /\m=/);
|
||||
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 () {
|
||||
mockManager.mockSetting('email_track_clicks', false);
|
||||
|
||||
const {emailModel, html} = await sendEmail();
|
||||
const {emailModel, html} = await sendEmail(agent);
|
||||
assert.doesNotMatch(html, /\m=/);
|
||||
const links = await linkRedirectRepository.getAll({filter: 'post_id:' + emailModel.get('post_id')});
|
||||
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
|
||||
}, '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
|
||||
}, 'label:replacements-tests-2');
|
||||
|
||||
@ -909,7 +748,7 @@ describe('Batch sending tests', function () {
|
||||
|
||||
describe('HTML-content', 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_caption: 'Testing <b>feature image caption</b>'
|
||||
});
|
||||
@ -928,7 +767,7 @@ describe('Batch sending tests', function () {
|
||||
const defaultNewsletter = await getDefaultNewsletter();
|
||||
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',
|
||||
mobiledoc: mobileDocExample
|
||||
});
|
||||
@ -943,7 +782,7 @@ describe('Batch sending tests', function () {
|
||||
await models.Newsletter.edit({show_post_title_section: true}, {id: defaultNewsletter.id});
|
||||
|
||||
// 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',
|
||||
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('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',
|
||||
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');
|
||||
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',
|
||||
mobiledoc: mobileDocExample
|
||||
});
|
||||
@ -1000,7 +839,7 @@ describe('Batch sending tests', function () {
|
||||
const defaultNewsletter = await getDefaultNewsletter();
|
||||
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',
|
||||
mobiledoc: mobileDocExample,
|
||||
email_only: true
|
||||
@ -1018,7 +857,7 @@ describe('Batch sending tests', function () {
|
||||
const defaultNewsletter = await getDefaultNewsletter();
|
||||
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',
|
||||
mobiledoc: mobileDocExample
|
||||
});
|
||||
@ -1034,7 +873,7 @@ describe('Batch sending tests', function () {
|
||||
const defaultNewsletter = await getDefaultNewsletter();
|
||||
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',
|
||||
mobiledoc: mobileDocExample
|
||||
});
|
||||
@ -1062,7 +901,7 @@ describe('Batch sending tests', function () {
|
||||
const defaultNewsletter = await getDefaultNewsletter();
|
||||
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',
|
||||
mobiledoc: mobileDocExample
|
||||
}, 'label:subscription-box-tests');
|
||||
@ -1095,7 +934,7 @@ describe('Batch sending tests', function () {
|
||||
const defaultNewsletter = await getDefaultNewsletter();
|
||||
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',
|
||||
mobiledoc: mobileDocExample
|
||||
}, 'label:subscription-box-comped-tests');
|
||||
@ -1136,7 +975,7 @@ describe('Batch sending tests', function () {
|
||||
const defaultNewsletter = await getDefaultNewsletter();
|
||||
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',
|
||||
mobiledoc: mobileDocExample
|
||||
}, 'label:subscription-box-trialing-tests');
|
||||
@ -1177,7 +1016,7 @@ describe('Batch sending tests', function () {
|
||||
const defaultNewsletter = await getDefaultNewsletter();
|
||||
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',
|
||||
mobiledoc: mobileDocExample
|
||||
}, 'label:subscription-box-paid-tests');
|
||||
@ -1219,7 +1058,7 @@ describe('Batch sending tests', function () {
|
||||
const defaultNewsletter = await getDefaultNewsletter();
|
||||
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',
|
||||
mobiledoc: mobileDocExample
|
||||
}, 'label:subscription-box-canceled-tests');
|
||||
@ -1240,7 +1079,7 @@ describe('Batch sending tests', function () {
|
||||
const defaultNewsletter = await getDefaultNewsletter();
|
||||
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',
|
||||
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;
|
||||
};
|
||||
|
||||
/**
|
||||
* A reference to the send method when MailGun is mocked (required for some tests)
|
||||
*/
|
||||
let mailgunCreateMessageStub;
|
||||
|
||||
const mockMailgun = (customStubbedSend) => {
|
||||
mockSetting('mailgun_api_key', 'test');
|
||||
mockSetting('mailgun_domain', 'example.com');
|
||||
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>`
|
||||
});
|
||||
|
||||
@ -126,7 +131,7 @@ const mockMailgun = (customStubbedSend) => {
|
||||
// @ts-ignore
|
||||
messages: {
|
||||
create: async function () {
|
||||
return await stubbedSend.call(this, ...arguments);
|
||||
return await mailgunCreateMessageStub.call(this, ...arguments);
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -300,5 +305,6 @@ module.exports = {
|
||||
sentEmailCount,
|
||||
sentEmail,
|
||||
emittedEvent
|
||||
}
|
||||
},
|
||||
getMailgunCreateMessageStub: () => mailgunCreateMessageStub
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user