Added newsletter from address verification (#14491)
refs https://github.com/TryGhost/Team/issues/1498 refs https://github.com/TryGhost/Team/issues/584 - Added newsletter `from` address verification Co-authored-by: Hannah Wolfe <github.erisds@gmail.com>
This commit is contained in:
parent
298599ce91
commit
a8687b35b9
@ -5,6 +5,7 @@ const errors = require('@tryghost/errors');
|
||||
const messages = {
|
||||
newsletterNotFound: 'Newsletter not found.'
|
||||
};
|
||||
const newslettersService = require('../../services/newsletters');
|
||||
|
||||
module.exports = {
|
||||
docName: 'newsletters',
|
||||
@ -53,7 +54,7 @@ module.exports = {
|
||||
statusCode: 201,
|
||||
permissions: true,
|
||||
async query(frame) {
|
||||
return models.Newsletter.add(frame.data.newsletters[0], frame.options);
|
||||
return newslettersService.add(frame.data.newsletters[0], frame.options);
|
||||
}
|
||||
},
|
||||
|
||||
@ -71,7 +72,19 @@ module.exports = {
|
||||
},
|
||||
permissions: true,
|
||||
async query(frame) {
|
||||
return models.Newsletter.edit(frame.data.newsletters[0], frame.options);
|
||||
return newslettersService.edit(frame.data.newsletters[0], frame.options);
|
||||
}
|
||||
},
|
||||
|
||||
verifyPropertyUpdate: {
|
||||
permissions: {
|
||||
method: 'edit'
|
||||
},
|
||||
data: [
|
||||
'token'
|
||||
],
|
||||
async query(frame) {
|
||||
return newslettersService.verifyPropertyUpdate(frame.data.token);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -13,7 +13,7 @@ const SingleUseTokenProvider = require('./SingleUseTokenProvider');
|
||||
const urlUtils = require('../../../shared/url-utils');
|
||||
const labsService = require('../../../shared/labs');
|
||||
const offersService = require('../offers');
|
||||
const getNewslettersServiceInstance = require('../newsletters');
|
||||
const newslettersService = require('../newsletters');
|
||||
|
||||
const MAGIC_LINK_TOKEN_VALIDITY = 24 * 60 * 60 * 1000;
|
||||
|
||||
@ -197,7 +197,7 @@ function createApiInstance(config) {
|
||||
stripeAPIService: stripeService.api,
|
||||
offersAPI: offersService.api,
|
||||
labsService: labsService,
|
||||
newslettersService: getNewslettersServiceInstance({NewsletterModel: models.Newsletter})
|
||||
newslettersService: newslettersService
|
||||
});
|
||||
|
||||
return membersApiInstance;
|
||||
|
166
core/server/services/newsletters/emails/verify-email.js
Normal file
166
core/server/services/newsletters/emails/verify-email.js
Normal file
@ -0,0 +1,166 @@
|
||||
module.exports = ({email, url}) => `
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<title>Confirm your email address</title>
|
||||
<style>
|
||||
/* -------------------------------------
|
||||
RESPONSIVE AND MOBILE FRIENDLY STYLES
|
||||
------------------------------------- */
|
||||
@media only screen and (max-width: 620px) {
|
||||
table[class=body] h1 {
|
||||
font-size: 28px !important;
|
||||
margin-bottom: 10px !important;
|
||||
}
|
||||
table[class=body] p,
|
||||
table[class=body] ul,
|
||||
table[class=body] ol,
|
||||
table[class=body] td,
|
||||
table[class=body] span,
|
||||
table[class=body] a {
|
||||
font-size: 16px !important;
|
||||
}
|
||||
table[class=body] .wrapper,
|
||||
table[class=body] .article {
|
||||
padding: 10px !important;
|
||||
}
|
||||
table[class=body] .content {
|
||||
padding: 0 !important;
|
||||
}
|
||||
table[class=body] .container {
|
||||
padding: 0 !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
table[class=body] .main {
|
||||
border-left-width: 0 !important;
|
||||
border-radius: 0 !important;
|
||||
border-right-width: 0 !important;
|
||||
}
|
||||
table[class=body] .btn table {
|
||||
width: 100% !important;
|
||||
}
|
||||
table[class=body] .btn a {
|
||||
width: 100% !important;
|
||||
}
|
||||
table[class=body] .img-responsive {
|
||||
height: auto !important;
|
||||
max-width: 100% !important;
|
||||
width: auto !important;
|
||||
}
|
||||
}
|
||||
/* -------------------------------------
|
||||
PRESERVE THESE STYLES IN THE HEAD
|
||||
------------------------------------- */
|
||||
@media all {
|
||||
.ExternalClass {
|
||||
width: 100%;
|
||||
}
|
||||
.ExternalClass,
|
||||
.ExternalClass p,
|
||||
.ExternalClass span,
|
||||
.ExternalClass font,
|
||||
.ExternalClass td,
|
||||
.ExternalClass div {
|
||||
line-height: 100%;
|
||||
}
|
||||
.recipient-link a {
|
||||
color: inherit !important;
|
||||
font-family: inherit !important;
|
||||
font-size: inherit !important;
|
||||
font-weight: inherit !important;
|
||||
line-height: inherit !important;
|
||||
text-decoration: none !important;
|
||||
}
|
||||
#MessageViewBody a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
font-size: inherit;
|
||||
font-family: inherit;
|
||||
font-weight: inherit;
|
||||
line-height: inherit;
|
||||
}
|
||||
}
|
||||
hr {
|
||||
border-width: 0;
|
||||
height: 0;
|
||||
margin-top: 34px;
|
||||
margin-bottom: 34px;
|
||||
border-bottom-width: 1px;
|
||||
border-bottom-color: #EEF5F8;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body style="background-color: #F4F8FB; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; -webkit-font-smoothing: antialiased; font-size: 14px; line-height: 1.4; margin: 0; padding: 0; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" class="body" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; background-color: #F4F8FB;">
|
||||
<tr>
|
||||
<td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 14px; vertical-align: top;"> </td>
|
||||
<td class="container" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 14px; vertical-align: top; display: block; Margin: 0 auto; max-width: 600px; padding: 10px; width: 600px;">
|
||||
<div class="content" style="box-sizing: border-box; display: block; Margin: 0 auto; max-width: 600px; padding: 30px 20px;">
|
||||
|
||||
<!-- START CENTERED WHITE CONTAINER -->
|
||||
<table class="main" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; background: #ffffff; border-radius: 8px;">
|
||||
|
||||
<!-- START MAIN CONTENT AREA -->
|
||||
<tr>
|
||||
<td class="wrapper" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 14px; vertical-align: top; box-sizing: border-box; padding: 40px 50px;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;">
|
||||
<tr>
|
||||
<td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 14px; vertical-align: top;">
|
||||
<p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 20px; color: #15212A; font-weight: bold; line-height: 25px; margin: 0; margin-bottom: 15px;">Hey there,</p>
|
||||
<p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 16px; color: #3A464C; font-weight: normal; margin: 0; line-height: 25px; margin-bottom: 32px;">Please confirm your email address with this link:</p>
|
||||
<table border="0" cellpadding="0" cellspacing="0" class="btn btn-primary" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; box-sizing: border-box;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="left" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 16px; vertical-align: top; padding-bottom: 35px;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: auto;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 16px; vertical-align: top; background-color: #15212A; border-radius: 5px; text-align: center;"> <a href="${url}" target="_blank" style="display: inline-block; color: #ffffff; background-color: #15212A; border: solid 1px #15212A; border-radius: 5px; box-sizing: border-box; cursor: pointer; text-decoration: none; font-size: 16px; font-weight: normal; margin: 0; padding: 9px 22px 10px; border-color: #15212A;" data-test-verify-link>Confirm email address</a> </td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 16px; color: #3A464C; font-weight: normal; margin: 0; line-height: 25px; margin-bottom: 25px;">For your security, the link will expire in 24 hours time.</p>
|
||||
<hr/>
|
||||
<p style="word-break: break-all; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 15px; color: #3A464C; font-weight: normal; margin: 0; line-height: 25px; margin-bottom: 5px;">You can also copy & paste this URL into your browser:</p>
|
||||
<p style="word-break: break-all; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 14px; line-height: 21px; margin-top: 0; color: #738A94;">${url}</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- END MAIN CONTENT AREA -->
|
||||
</table>
|
||||
|
||||
<!-- START FOOTER -->
|
||||
<div class="footer" style="clear: both; Margin-top: 10px; text-align: center; width: 100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;">
|
||||
<tr>
|
||||
<td class="content-block" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; vertical-align: top; padding-bottom: 5px; padding-top: 15px; font-size: 13px; line-height: 21px; color: #738A94; text-align: center;">
|
||||
If you did not make this request, you can simply delete this message.<br/>This email address will not be used.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="content-block" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; vertical-align: top; padding-bottom: 10px; padding-top: 10px; font-size: 13px; color: #738A94; text-align: center;">
|
||||
<span class="recipient-link" style="color: #738A94; font-size: 13px; text-align: center;">Sent to <a href="mailto:${email}" style="text-decoration: underline; color: #738A94; font-size: 13px; text-align: center;">${email}</a></span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<!-- END FOOTER -->
|
||||
|
||||
<!-- END CENTERED WHITE CONTAINER -->
|
||||
</div>
|
||||
</td>
|
||||
<td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 14px; vertical-align: top;"> </td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
@ -1,10 +1,14 @@
|
||||
const NewslettersService = require('./service.js');
|
||||
const SingleUseTokenProvider = require('../members/SingleUseTokenProvider');
|
||||
const mail = require('../mail');
|
||||
const models = require('../../models');
|
||||
const urlUtils = require('../../../shared/url-utils');
|
||||
|
||||
/**
|
||||
* @returns {NewslettersService} instance of the NewslettersService
|
||||
*/
|
||||
const getNewslettersServiceInstance = ({NewsletterModel}) => {
|
||||
return new NewslettersService({NewsletterModel});
|
||||
};
|
||||
const MAGIC_LINK_TOKEN_VALIDITY = 24 * 60 * 60 * 1000;
|
||||
|
||||
module.exports = getNewslettersServiceInstance;
|
||||
module.exports = new NewslettersService({
|
||||
NewsletterModel: models.Newsletter,
|
||||
mail,
|
||||
singleUseTokenProvider: new SingleUseTokenProvider(models.SingleUseToken, MAGIC_LINK_TOKEN_VALIDITY),
|
||||
urlUtils
|
||||
});
|
||||
|
@ -1,11 +1,70 @@
|
||||
const _ = require('lodash');
|
||||
const MagicLink = require('@tryghost/magic-link');
|
||||
const logging = require('@tryghost/logging');
|
||||
const verifyEmailTemplate = require('./emails/verify-email');
|
||||
|
||||
class NewslettersService {
|
||||
/**
|
||||
*
|
||||
* @param {Object} options
|
||||
* @param {Object} options.NewsletterModel
|
||||
* @param {Object} options.mail
|
||||
* @param {Object} options.singleUseTokenProvider
|
||||
* @param {Object} options.urlUtils
|
||||
*/
|
||||
constructor({NewsletterModel}) {
|
||||
constructor({NewsletterModel, mail, singleUseTokenProvider, urlUtils}) {
|
||||
this.NewsletterModel = NewsletterModel;
|
||||
this.urlUtils = urlUtils;
|
||||
|
||||
/* email verification setup */
|
||||
|
||||
this.ghostMailer = new mail.GhostMailer();
|
||||
|
||||
const {transporter, getSubject, getText, getHTML, getSigninURL} = {
|
||||
transporter: {
|
||||
sendMail() {
|
||||
// noop - overridden in `sendEmailVerificationMagicLink`
|
||||
}
|
||||
},
|
||||
getSubject() {
|
||||
// not used - overridden in `sendEmailVerificationMagicLink`
|
||||
return `Verify email address`;
|
||||
},
|
||||
getText(url, type, email) {
|
||||
return `
|
||||
Hey there,
|
||||
|
||||
Please confirm your email address with this link:
|
||||
|
||||
${url}
|
||||
|
||||
For your security, the link will expire in 24 hours time.
|
||||
|
||||
---
|
||||
|
||||
Sent to ${email}
|
||||
If you did not make this request, you can simply delete this message. This email address will not be used.
|
||||
`;
|
||||
},
|
||||
getHTML(url, type, email) {
|
||||
return verifyEmailTemplate({url, email});
|
||||
},
|
||||
getSigninURL(token) {
|
||||
const adminUrl = urlUtils.urlFor('admin', true);
|
||||
const signinURL = new URL(adminUrl);
|
||||
signinURL.hash = `/settings/members-email-labs/?verifyEmail=${token}`;
|
||||
return signinURL.href;
|
||||
}
|
||||
};
|
||||
|
||||
this.magicLinkService = new MagicLink({
|
||||
transporter,
|
||||
tokenProvider: singleUseTokenProvider,
|
||||
getSigninURL,
|
||||
getText,
|
||||
getHTML,
|
||||
getSubject
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -18,7 +77,109 @@ class NewslettersService {
|
||||
|
||||
return newsletters.toJSON();
|
||||
}
|
||||
|
||||
async add(attrs, options) {
|
||||
// remove any email properties that are not allowed to be set without verification
|
||||
const {cleanedAttrs, emailsToVerify} = await this.prepAttrsForEmailVerification(attrs);
|
||||
|
||||
// add the model now because we need the ID for sending verification emails
|
||||
const newsletter = await this.NewsletterModel.add(cleanedAttrs, options);
|
||||
|
||||
// send any verification emails and respond with the appropriate meta added
|
||||
return this.respondWithEmailVerification(newsletter, emailsToVerify);
|
||||
}
|
||||
|
||||
async edit(attrs, options) {
|
||||
// fetch newsletter first so we can compare changed emails
|
||||
const originalNewsletter = await this.NewsletterModel.findOne(options, {require: true});
|
||||
|
||||
const {cleanedAttrs, emailsToVerify} = await this.prepAttrsForEmailVerification(attrs, originalNewsletter);
|
||||
|
||||
const updatedNewsletter = await this.NewsletterModel.edit(cleanedAttrs, options);
|
||||
|
||||
return this.respondWithEmailVerification(updatedNewsletter, emailsToVerify);
|
||||
}
|
||||
|
||||
async verifyPropertyUpdate(token) {
|
||||
const data = await this.magicLinkService.getDataFromToken(token);
|
||||
const {id, property, value} = data;
|
||||
|
||||
const attrs = {};
|
||||
attrs[property] = value;
|
||||
|
||||
return this.NewsletterModel.edit(attrs, {id});
|
||||
}
|
||||
|
||||
/* Email verification (private) */
|
||||
|
||||
async prepAttrsForEmailVerification(attrs, newsletter) {
|
||||
const cleanedAttrs = _.cloneDeep(attrs);
|
||||
const emailsToVerify = [];
|
||||
|
||||
for (const property of ['sender_email']) {
|
||||
const email = cleanedAttrs[property];
|
||||
const hasChanged = !newsletter || newsletter.get(property) !== email;
|
||||
|
||||
if (await this.requiresEmailVerification({email, hasChanged})) {
|
||||
delete cleanedAttrs[property];
|
||||
emailsToVerify.push({email, property});
|
||||
}
|
||||
}
|
||||
|
||||
return {cleanedAttrs, emailsToVerify};
|
||||
}
|
||||
|
||||
async requiresEmailVerification({email, hasChanged}) {
|
||||
if (!email || !hasChanged) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: check other newsletters for known/verified email
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async respondWithEmailVerification(newsletter, emailsToVerify) {
|
||||
if (emailsToVerify.length > 0) {
|
||||
for (const {email, property} of emailsToVerify) {
|
||||
await this.sendEmailVerificationMagicLink({id: newsletter.get('id'), email, property});
|
||||
}
|
||||
|
||||
newsletter.meta = {
|
||||
sent_email_verification: emailsToVerify.map(v => v.property)
|
||||
};
|
||||
}
|
||||
|
||||
return newsletter;
|
||||
}
|
||||
|
||||
async sendEmailVerificationMagicLink({id, email, property = 'sender_from'}) {
|
||||
const [,toDomain] = email.split('@');
|
||||
|
||||
let fromEmail = `noreply@${toDomain}`;
|
||||
if (fromEmail === email) {
|
||||
fromEmail = `no-reply@${toDomain}`;
|
||||
}
|
||||
|
||||
const {ghostMailer} = this;
|
||||
|
||||
this.magicLinkService.transporter = {
|
||||
sendMail(message) {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
logging.warn(message.text);
|
||||
}
|
||||
let msg = Object.assign({
|
||||
from: fromEmail,
|
||||
subject: 'Verify email address',
|
||||
forceTextContent: true
|
||||
}, message);
|
||||
|
||||
return ghostMailer.send(msg);
|
||||
}
|
||||
};
|
||||
|
||||
return this.magicLinkService.sendMagicLink({email, tokenData: {id, property, value: email}});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = NewslettersService;
|
||||
|
||||
|
@ -315,6 +315,7 @@ module.exports = function apiRoutes() {
|
||||
router.get('/newsletters', mw.authAdminApi, http(api.newsletters.browse));
|
||||
router.get('/newsletters/:id', mw.authAdminApi, http(api.newsletters.read));
|
||||
router.post('/newsletters', mw.authAdminApi, http(api.newsletters.add));
|
||||
router.put('/newsletters/verifications/', mw.authAdminApi, http(api.newsletters.verifyPropertyUpdate));
|
||||
router.put('/newsletters/:id', mw.authAdminApi, http(api.newsletters.edit));
|
||||
|
||||
return router;
|
||||
|
@ -1,5 +1,197 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Newsletters API Can add a newsletter - with custom sender_email 1: [body] 1`] = `
|
||||
Object {
|
||||
"meta": Object {
|
||||
"sent_email_verification": Array [
|
||||
"sender_email",
|
||||
],
|
||||
},
|
||||
"newsletters": Array [
|
||||
Object {
|
||||
"body_font_category": "serif",
|
||||
"description": null,
|
||||
"footer_content": null,
|
||||
"header_image": null,
|
||||
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
|
||||
"name": "My test newsletter with custom sender_email",
|
||||
"sender_email": null,
|
||||
"sender_name": "Test",
|
||||
"sender_reply_to": "newsletter",
|
||||
"show_badge": true,
|
||||
"show_feature_image": true,
|
||||
"show_header_icon": true,
|
||||
"show_header_name": true,
|
||||
"show_header_title": true,
|
||||
"slug": "my-test-newsletter-with-custom-sender_email",
|
||||
"sort_order": 0,
|
||||
"status": "active",
|
||||
"subscribe_on_signup": true,
|
||||
"title_alignment": "center",
|
||||
"title_font_category": "serif",
|
||||
"visibility": "members",
|
||||
},
|
||||
],
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Newsletters API Can add a newsletter - with custom sender_email 2: [headers] 1`] = `
|
||||
Object {
|
||||
"access-control-allow-origin": "http://127.0.0.1:2369",
|
||||
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
|
||||
"content-length": "628",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
"location": Any<String>,
|
||||
"vary": "Origin, Accept-Encoding",
|
||||
"x-powered-by": "Express",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Newsletters API Can add a newsletter - with custom sender_email 3: [body] 1`] = `
|
||||
Object {
|
||||
"meta": Object {
|
||||
"pagination": Object {
|
||||
"limit": 15,
|
||||
"next": null,
|
||||
"page": 1,
|
||||
"pages": 1,
|
||||
"prev": null,
|
||||
"total": 5,
|
||||
},
|
||||
},
|
||||
"newsletters": Array [
|
||||
Object {
|
||||
"body_font_category": "sans_serif",
|
||||
"description": null,
|
||||
"footer_content": null,
|
||||
"header_image": null,
|
||||
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
|
||||
"name": "Default Newsletter",
|
||||
"sender_email": null,
|
||||
"sender_name": null,
|
||||
"sender_reply_to": "newsletter",
|
||||
"show_badge": true,
|
||||
"show_feature_image": true,
|
||||
"show_header_icon": true,
|
||||
"show_header_name": true,
|
||||
"show_header_title": true,
|
||||
"slug": "default-newsletter",
|
||||
"sort_order": 0,
|
||||
"status": "active",
|
||||
"subscribe_on_signup": true,
|
||||
"title_alignment": "center",
|
||||
"title_font_category": "sans_serif",
|
||||
"visibility": "members",
|
||||
},
|
||||
Object {
|
||||
"body_font_category": "serif",
|
||||
"description": null,
|
||||
"footer_content": null,
|
||||
"header_image": null,
|
||||
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
|
||||
"name": "My test newsletter",
|
||||
"sender_email": null,
|
||||
"sender_name": "Test",
|
||||
"sender_reply_to": "newsletter",
|
||||
"show_badge": true,
|
||||
"show_feature_image": true,
|
||||
"show_header_icon": true,
|
||||
"show_header_name": true,
|
||||
"show_header_title": true,
|
||||
"slug": "my-test-newsletter",
|
||||
"sort_order": 0,
|
||||
"status": "active",
|
||||
"subscribe_on_signup": true,
|
||||
"title_alignment": "center",
|
||||
"title_font_category": "serif",
|
||||
"visibility": "members",
|
||||
},
|
||||
Object {
|
||||
"body_font_category": "serif",
|
||||
"description": null,
|
||||
"footer_content": null,
|
||||
"header_image": null,
|
||||
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
|
||||
"name": "My test newsletter with custom sender_email",
|
||||
"sender_email": null,
|
||||
"sender_name": "Test",
|
||||
"sender_reply_to": "newsletter",
|
||||
"show_badge": true,
|
||||
"show_feature_image": true,
|
||||
"show_header_icon": true,
|
||||
"show_header_name": true,
|
||||
"show_header_title": true,
|
||||
"slug": "my-test-newsletter-with-custom-sender_email",
|
||||
"sort_order": 0,
|
||||
"status": "active",
|
||||
"subscribe_on_signup": true,
|
||||
"title_alignment": "center",
|
||||
"title_font_category": "serif",
|
||||
"visibility": "members",
|
||||
},
|
||||
Object {
|
||||
"body_font_category": "serif",
|
||||
"description": null,
|
||||
"footer_content": null,
|
||||
"header_image": null,
|
||||
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
|
||||
"name": "Daily newsletter",
|
||||
"sender_email": "jamie@example.com",
|
||||
"sender_name": "Jamie",
|
||||
"sender_reply_to": "newsletter",
|
||||
"show_badge": true,
|
||||
"show_feature_image": true,
|
||||
"show_header_icon": true,
|
||||
"show_header_name": true,
|
||||
"show_header_title": true,
|
||||
"slug": "daily-newsletter",
|
||||
"sort_order": 1,
|
||||
"status": "active",
|
||||
"subscribe_on_signup": false,
|
||||
"title_alignment": "center",
|
||||
"title_font_category": "serif",
|
||||
"visibility": "members",
|
||||
},
|
||||
Object {
|
||||
"body_font_category": "serif",
|
||||
"description": null,
|
||||
"footer_content": null,
|
||||
"header_image": null,
|
||||
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
|
||||
"name": "Weekly newsletter",
|
||||
"sender_email": "jamie@example.com",
|
||||
"sender_name": "Jamie",
|
||||
"sender_reply_to": "newsletter",
|
||||
"show_badge": true,
|
||||
"show_feature_image": true,
|
||||
"show_header_icon": true,
|
||||
"show_header_name": true,
|
||||
"show_header_title": true,
|
||||
"slug": "weekly-newsletter",
|
||||
"sort_order": 2,
|
||||
"status": "active",
|
||||
"subscribe_on_signup": true,
|
||||
"title_alignment": "center",
|
||||
"title_font_category": "serif",
|
||||
"visibility": "members",
|
||||
},
|
||||
],
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Newsletters API Can add a newsletter - with custom sender_email 4: [headers] 1`] = `
|
||||
Object {
|
||||
"access-control-allow-origin": "http://127.0.0.1:2369",
|
||||
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
|
||||
"content-length": "2735",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
"vary": "Origin, Accept-Encoding",
|
||||
"x-powered-by": "Express",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Newsletters API Can add a newsletter 1: [body] 1`] = `
|
||||
Object {
|
||||
"newsletters": Array [
|
||||
@ -10,7 +202,7 @@ Object {
|
||||
"header_image": null,
|
||||
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
|
||||
"name": "My test newsletter",
|
||||
"sender_email": "test@example.com",
|
||||
"sender_email": null,
|
||||
"sender_name": "Test",
|
||||
"sender_reply_to": "newsletter",
|
||||
"show_badge": true,
|
||||
@ -34,7 +226,7 @@ exports[`Newsletters API Can add a newsletter 2: [headers] 1`] = `
|
||||
Object {
|
||||
"access-control-allow-origin": "http://127.0.0.1:2369",
|
||||
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
|
||||
"content-length": "540",
|
||||
"content-length": "526",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
"location": Any<String>,
|
||||
@ -86,7 +278,7 @@ Object {
|
||||
"header_image": null,
|
||||
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
|
||||
"name": "My test newsletter",
|
||||
"sender_email": "test@example.com",
|
||||
"sender_email": null,
|
||||
"sender_name": "Test",
|
||||
"sender_reply_to": "newsletter",
|
||||
"show_badge": true,
|
||||
@ -156,7 +348,7 @@ exports[`Newsletters API Can add a newsletter 4: [headers] 1`] = `
|
||||
Object {
|
||||
"access-control-allow-origin": "http://127.0.0.1:2369",
|
||||
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
|
||||
"content-length": "2190",
|
||||
"content-length": "2176",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
"vary": "Origin, Accept-Encoding",
|
||||
@ -173,7 +365,7 @@ Object {
|
||||
"page": 1,
|
||||
"pages": 1,
|
||||
"prev": null,
|
||||
"total": 4,
|
||||
"total": 5,
|
||||
},
|
||||
},
|
||||
"newsletters": Array [
|
||||
@ -207,7 +399,7 @@ Object {
|
||||
"header_image": null,
|
||||
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
|
||||
"name": "My test newsletter",
|
||||
"sender_email": "test@example.com",
|
||||
"sender_email": null,
|
||||
"sender_name": "Test",
|
||||
"sender_reply_to": "newsletter",
|
||||
"show_badge": true,
|
||||
@ -223,6 +415,29 @@ Object {
|
||||
"title_font_category": "serif",
|
||||
"visibility": "members",
|
||||
},
|
||||
Object {
|
||||
"body_font_category": "serif",
|
||||
"description": null,
|
||||
"footer_content": null,
|
||||
"header_image": null,
|
||||
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
|
||||
"name": "My test newsletter with custom sender_email",
|
||||
"sender_email": null,
|
||||
"sender_name": "Test",
|
||||
"sender_reply_to": "newsletter",
|
||||
"show_badge": true,
|
||||
"show_feature_image": true,
|
||||
"show_header_icon": true,
|
||||
"show_header_name": true,
|
||||
"show_header_title": true,
|
||||
"slug": "my-test-newsletter-with-custom-sender_email",
|
||||
"sort_order": 0,
|
||||
"status": "active",
|
||||
"subscribe_on_signup": true,
|
||||
"title_alignment": "center",
|
||||
"title_font_category": "serif",
|
||||
"visibility": "members",
|
||||
},
|
||||
Object {
|
||||
"body_font_category": "serif",
|
||||
"description": null,
|
||||
@ -277,7 +492,7 @@ exports[`Newsletters API Can browse newsletters 2: [headers] 1`] = `
|
||||
Object {
|
||||
"access-control-allow-origin": "http://127.0.0.1:2369",
|
||||
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
|
||||
"content-length": "2190",
|
||||
"content-length": "2735",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
"vary": "Origin, Accept-Encoding",
|
||||
@ -292,9 +507,9 @@ Object {
|
||||
"limit": 1,
|
||||
"next": 2,
|
||||
"page": 1,
|
||||
"pages": 4,
|
||||
"pages": 5,
|
||||
"prev": null,
|
||||
"total": 4,
|
||||
"total": 5,
|
||||
},
|
||||
},
|
||||
"newsletters": Array [
|
||||
@ -379,6 +594,105 @@ Object {
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Newsletters API Can edit newsletters with updated sender_email 1: [body] 1`] = `
|
||||
Object {
|
||||
"meta": Object {
|
||||
"pagination": Object {
|
||||
"limit": 1,
|
||||
"next": 2,
|
||||
"page": 1,
|
||||
"pages": 5,
|
||||
"prev": null,
|
||||
"total": 5,
|
||||
},
|
||||
},
|
||||
"newsletters": Array [
|
||||
Object {
|
||||
"body_font_category": "sans_serif",
|
||||
"description": null,
|
||||
"footer_content": null,
|
||||
"header_image": null,
|
||||
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
|
||||
"name": "Updated newsletter name",
|
||||
"sender_email": null,
|
||||
"sender_name": null,
|
||||
"sender_reply_to": "newsletter",
|
||||
"show_badge": true,
|
||||
"show_feature_image": true,
|
||||
"show_header_icon": true,
|
||||
"show_header_name": true,
|
||||
"show_header_title": true,
|
||||
"slug": "default-newsletter",
|
||||
"sort_order": 0,
|
||||
"status": "active",
|
||||
"subscribe_on_signup": true,
|
||||
"title_alignment": "center",
|
||||
"title_font_category": "sans_serif",
|
||||
"visibility": "members",
|
||||
},
|
||||
],
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Newsletters API Can edit newsletters with updated sender_email 2: [headers] 1`] = `
|
||||
Object {
|
||||
"access-control-allow-origin": "http://127.0.0.1:2369",
|
||||
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
|
||||
"content-length": "623",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
"vary": "Origin, Accept-Encoding",
|
||||
"x-powered-by": "Express",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Newsletters API Can edit newsletters with updated sender_email 3: [body] 1`] = `
|
||||
Object {
|
||||
"meta": Object {
|
||||
"sent_email_verification": Array [
|
||||
"sender_email",
|
||||
],
|
||||
},
|
||||
"newsletters": Array [
|
||||
Object {
|
||||
"body_font_category": "sans_serif",
|
||||
"description": null,
|
||||
"footer_content": null,
|
||||
"header_image": null,
|
||||
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
|
||||
"name": "Updated newsletter name",
|
||||
"sender_email": null,
|
||||
"sender_name": null,
|
||||
"sender_reply_to": "newsletter",
|
||||
"show_badge": true,
|
||||
"show_feature_image": true,
|
||||
"show_header_icon": true,
|
||||
"show_header_name": true,
|
||||
"show_header_title": true,
|
||||
"slug": "default-newsletter",
|
||||
"sort_order": 0,
|
||||
"status": "active",
|
||||
"subscribe_on_signup": true,
|
||||
"title_alignment": "center",
|
||||
"title_font_category": "sans_serif",
|
||||
"visibility": "members",
|
||||
},
|
||||
],
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Newsletters API Can edit newsletters with updated sender_email 4: [headers] 1`] = `
|
||||
Object {
|
||||
"access-control-allow-origin": "http://127.0.0.1:2369",
|
||||
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
|
||||
"content-length": "591",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
"vary": "Origin, Accept-Encoding",
|
||||
"x-powered-by": "Express",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Newsletters API Can read a newsletter 1: [body] 1`] = `
|
||||
Object {
|
||||
"newsletters": Array [
|
||||
@ -421,57 +735,61 @@ Object {
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Newsletters API Cannot add newsletter with same name 1: [body] 1`] = `
|
||||
exports[`Newsletters API Can verify property updates 1: [body] 1`] = `
|
||||
Object {
|
||||
"newsletters": Array [
|
||||
Object {
|
||||
"body_font_category": "serif",
|
||||
"body_font_category": "sans_serif",
|
||||
"description": null,
|
||||
"footer_content": null,
|
||||
"header_image": null,
|
||||
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
|
||||
"name": "My test newsletter",
|
||||
"recipient_filter": "members",
|
||||
"sender_email": "test@example.com",
|
||||
"sender_name": "Test",
|
||||
"name": "Updated newsletter name",
|
||||
"sender_email": "verify@example.com",
|
||||
"sender_name": null,
|
||||
"sender_reply_to": "newsletter",
|
||||
"show_badge": true,
|
||||
"show_feature_image": true,
|
||||
"show_header_icon": true,
|
||||
"show_header_name": true,
|
||||
"show_header_title": true,
|
||||
"slug": "my-test-newsletter-2",
|
||||
"slug": "default-newsletter",
|
||||
"sort_order": 0,
|
||||
"status": "active",
|
||||
"subscribe_on_signup": true,
|
||||
"title_alignment": "center",
|
||||
"title_font_category": "serif",
|
||||
"title_font_category": "sans_serif",
|
||||
"visibility": "members",
|
||||
},
|
||||
],
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Newsletters API Cannot add newsletter with same name 1: [headers] 1`] = `
|
||||
exports[`Newsletters API Can verify restricted property updates 1: [body] 1`] = `
|
||||
Object {
|
||||
"access-control-allow-origin": "http://127.0.0.1:2369",
|
||||
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
|
||||
"content-length": "524",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
"location": Any<String>,
|
||||
"vary": "Origin, Accept-Encoding",
|
||||
"x-powered-by": "Express",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Newsletters API Cannot add newsletter with same name 2: [headers] 1`] = `
|
||||
Object {
|
||||
"access-control-allow-origin": "http://127.0.0.1:2369",
|
||||
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
|
||||
"content-length": "524",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
"location": Any<String>,
|
||||
"vary": "Origin, Accept-Encoding",
|
||||
"x-powered-by": "Express",
|
||||
"newsletters": Array [
|
||||
Object {
|
||||
"body_font_category": "sans_serif",
|
||||
"description": null,
|
||||
"footer_content": null,
|
||||
"header_image": null,
|
||||
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
|
||||
"name": "Updated newsletter name",
|
||||
"sender_email": "verify@example.com",
|
||||
"sender_name": null,
|
||||
"sender_reply_to": "newsletter",
|
||||
"show_badge": true,
|
||||
"show_feature_image": true,
|
||||
"show_header_icon": true,
|
||||
"show_header_title": true,
|
||||
"slug": "default-newsletter",
|
||||
"sort_order": 0,
|
||||
"status": "active",
|
||||
"subscribe_on_signup": true,
|
||||
"title_alignment": "center",
|
||||
"title_font_category": "sans_serif",
|
||||
"visibility": "members",
|
||||
},
|
||||
],
|
||||
}
|
||||
`;
|
||||
|
@ -9,6 +9,8 @@ const newsletterSnapshot = {
|
||||
let agent;
|
||||
|
||||
describe('Newsletters API', function () {
|
||||
let mailMocks;
|
||||
|
||||
before(async function () {
|
||||
agent = await agentProvider.getAdminAPIAgent();
|
||||
await fixtureManager.init('newsletters');
|
||||
@ -16,6 +18,7 @@ describe('Newsletters API', function () {
|
||||
});
|
||||
|
||||
beforeEach(function () {
|
||||
mailMocks = mockManager.mockMail();
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
@ -26,7 +29,7 @@ describe('Newsletters API', function () {
|
||||
const newsletter = {
|
||||
name: 'My test newsletter',
|
||||
sender_name: 'Test',
|
||||
sender_email: 'test@example.com',
|
||||
sender_email: null,
|
||||
sender_reply_to: 'newsletter',
|
||||
status: 'active',
|
||||
subscribe_on_signup: true,
|
||||
@ -60,11 +63,57 @@ describe('Newsletters API', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('Can add a newsletter - with custom sender_email', async function () {
|
||||
const newsletter = {
|
||||
name: 'My test newsletter with custom sender_email',
|
||||
sender_name: 'Test',
|
||||
sender_email: 'test@example.com',
|
||||
sender_reply_to: 'newsletter',
|
||||
status: 'active',
|
||||
subscribe_on_signup: true,
|
||||
title_font_category: 'serif',
|
||||
body_font_category: 'serif',
|
||||
show_header_icon: true,
|
||||
show_header_title: true,
|
||||
show_badge: true,
|
||||
sort_order: 0
|
||||
};
|
||||
|
||||
await agent
|
||||
.post(`newsletters/`)
|
||||
.body({newsletters: [newsletter]})
|
||||
.expectStatus(201)
|
||||
.matchBodySnapshot({
|
||||
newsletters: new Array(1).fill(newsletterSnapshot),
|
||||
meta: {
|
||||
sent_email_verification: ['sender_email']
|
||||
}
|
||||
})
|
||||
.matchHeaderSnapshot({
|
||||
etag: anyEtag,
|
||||
location: anyString
|
||||
});
|
||||
|
||||
mockManager.assert.sentEmail({
|
||||
subject: 'Verify email address',
|
||||
to: 'test@example.com'
|
||||
});
|
||||
|
||||
await agent.get('newsletters/')
|
||||
.expectStatus(200)
|
||||
.matchBodySnapshot({
|
||||
newsletters: new Array(5).fill(newsletterSnapshot)
|
||||
})
|
||||
.matchHeaderSnapshot({
|
||||
etag: anyEtag
|
||||
});
|
||||
});
|
||||
|
||||
it('Can browse newsletters', async function () {
|
||||
await agent.get('newsletters/')
|
||||
.expectStatus(200)
|
||||
.matchBodySnapshot({
|
||||
newsletters: new Array(4).fill(newsletterSnapshot)
|
||||
newsletters: new Array(5).fill(newsletterSnapshot)
|
||||
})
|
||||
.matchHeaderSnapshot({
|
||||
etag: anyEtag
|
||||
@ -76,7 +125,8 @@ describe('Newsletters API', function () {
|
||||
.get(`newsletters/${testUtils.DataGenerator.Content.newsletters[0].id}/`)
|
||||
.expectStatus(200)
|
||||
.matchBodySnapshot({
|
||||
newsletters: new Array(1).fill(newsletterSnapshot)
|
||||
newsletters: [newsletterSnapshot]
|
||||
|
||||
})
|
||||
.matchHeaderSnapshot({
|
||||
etag: anyEtag
|
||||
@ -109,4 +159,74 @@ describe('Newsletters API', function () {
|
||||
etag: anyEtag
|
||||
});
|
||||
});
|
||||
|
||||
it('Can edit newsletters with updated sender_email', async function () {
|
||||
const res = await agent.get('newsletters?limit=1')
|
||||
.expectStatus(200)
|
||||
.matchBodySnapshot({
|
||||
newsletters: [newsletterSnapshot]
|
||||
})
|
||||
.matchHeaderSnapshot({
|
||||
etag: anyEtag
|
||||
});
|
||||
|
||||
const id = res.body.newsletters[0].id;
|
||||
|
||||
await agent.put(`newsletters/${id}`)
|
||||
.body({
|
||||
newsletters: [{
|
||||
name: 'Updated newsletter name',
|
||||
sender_email: 'updated@example.com'
|
||||
}]
|
||||
})
|
||||
.expectStatus(200)
|
||||
.matchBodySnapshot({
|
||||
newsletters: [newsletterSnapshot],
|
||||
meta: {
|
||||
sent_email_verification: ['sender_email']
|
||||
}
|
||||
})
|
||||
.matchHeaderSnapshot({
|
||||
etag: anyEtag
|
||||
});
|
||||
|
||||
mockManager.assert.sentEmail({
|
||||
subject: 'Verify email address',
|
||||
to: 'updated@example.com'
|
||||
});
|
||||
});
|
||||
|
||||
it('Can verify property updates', async function () {
|
||||
const cheerio = require('cheerio');
|
||||
|
||||
const res = await agent.get('newsletters?limit=1')
|
||||
.expectStatus(200);
|
||||
|
||||
const id = res.body.newsletters[0].id;
|
||||
|
||||
await agent.put(`newsletters/${id}`)
|
||||
.body({
|
||||
newsletters: [{
|
||||
name: 'Updated newsletter name',
|
||||
sender_email: 'verify@example.com'
|
||||
}]
|
||||
})
|
||||
.expectStatus(200);
|
||||
|
||||
const mailHtml = mailMocks.getCall(0).args[0].html;
|
||||
const $mailHtml = cheerio.load(mailHtml);
|
||||
|
||||
const verifyUrl = new URL($mailHtml('[data-test-verify-link]').attr('href'));
|
||||
// convert Admin URL hash to native URL for easier token param extraction
|
||||
const token = (new URL(verifyUrl.hash.replace('#', ''), 'http://example.com')).searchParams.get('verifyEmail');
|
||||
|
||||
await agent.put(`newsletters/verifications`)
|
||||
.body({
|
||||
token
|
||||
})
|
||||
.expectStatus(200)
|
||||
.matchBodySnapshot({
|
||||
newsletters: [newsletterSnapshot]
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1,43 +1,21 @@
|
||||
const should = require('should');
|
||||
const sinon = require('sinon');
|
||||
const getNewslettersServiceInstance = require('../../../../../core/server/services/newsletters');
|
||||
const models = require('../../../../../core/server/models');
|
||||
const assert = require('assert');
|
||||
|
||||
describe('Newsletters Service', function () {
|
||||
let newslettersService;
|
||||
|
||||
before(function () {
|
||||
models.init();
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
sinon.restore();
|
||||
});
|
||||
describe('Newsletter Service', function () {
|
||||
it('Provides expected public API', async function () {
|
||||
newslettersService = require('../../../../../core/server/services/newsletters');
|
||||
|
||||
describe('browse', function () {
|
||||
it('lists all newsletters', async function () {
|
||||
const findAllStub = {
|
||||
toJSON: function () {
|
||||
return [
|
||||
{
|
||||
id: 'newsletter-1'
|
||||
},
|
||||
{
|
||||
id: 'newsletter-2'
|
||||
}
|
||||
];
|
||||
}
|
||||
};
|
||||
sinon.stub(models.Newsletter, 'findAll').returns(Promise.resolve(findAllStub));
|
||||
|
||||
const NewslettersService = getNewslettersServiceInstance({NewsletterModel: models.Newsletter});
|
||||
const newsletters = await NewslettersService.browse({});
|
||||
should(newsletters).deepEqual([
|
||||
{
|
||||
id: 'newsletter-1'
|
||||
},
|
||||
{
|
||||
id: 'newsletter-2'
|
||||
}
|
||||
]);
|
||||
assert.ok(newslettersService.browse);
|
||||
assert.ok(newslettersService.edit);
|
||||
assert.ok(newslettersService.add);
|
||||
assert.ok(newslettersService.verifyPropertyUpdate);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
194
test/unit/server/services/newsletters/service.test.js
Normal file
194
test/unit/server/services/newsletters/service.test.js
Normal file
@ -0,0 +1,194 @@
|
||||
const sinon = require('sinon');
|
||||
const assert = require('assert');
|
||||
|
||||
// DI requirements
|
||||
const models = require('../../../../../core/server/models');
|
||||
const mail = require('../../../../../core/server/services/mail');
|
||||
|
||||
// Mocked utilities
|
||||
const urlUtils = require('../../../../utils/urlUtils');
|
||||
const {mockManager} = require('../../../../utils/e2e-framework');
|
||||
|
||||
const NewslettersService = require('../../../../../core/server/services/newsletters/service');
|
||||
|
||||
class TestTokenProvider {
|
||||
async create(data) {
|
||||
return JSON.stringify(data);
|
||||
}
|
||||
|
||||
async validate(token) {
|
||||
return JSON.parse(token);
|
||||
}
|
||||
}
|
||||
|
||||
describe('NewslettersService', function () {
|
||||
let newsletterService, getStub, tokenProvider;
|
||||
|
||||
before(function () {
|
||||
models.init();
|
||||
|
||||
tokenProvider = new TestTokenProvider();
|
||||
|
||||
newsletterService = new NewslettersService({
|
||||
NewsletterModel: models.Newsletter,
|
||||
mail,
|
||||
singleUseTokenProvider: tokenProvider,
|
||||
urlUtils: urlUtils.stubUrlUtilsFromConfig()
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(function () {
|
||||
getStub = sinon.stub();
|
||||
sinon.spy(tokenProvider, 'create');
|
||||
sinon.spy(tokenProvider, 'validate');
|
||||
mockManager.mockMail();
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
mockManager.restore();
|
||||
});
|
||||
|
||||
// @TODO replace this with a specific function for fetching all available newsletters
|
||||
describe('browse', function () {
|
||||
it('lists all newsletters by calling findAll and toJSON', async function () {
|
||||
const toJSONStub = sinon.stub();
|
||||
const findAllStub = sinon.stub(models.Newsletter, 'findAll').returns({toJSON: toJSONStub});
|
||||
|
||||
await newsletterService.browse({});
|
||||
|
||||
sinon.assert.calledOnce(findAllStub);
|
||||
sinon.assert.calledOnce(toJSONStub);
|
||||
});
|
||||
});
|
||||
|
||||
describe('add', function () {
|
||||
let addStub;
|
||||
beforeEach(function () {
|
||||
// Stub add as a function that returns a get
|
||||
addStub = sinon.stub(models.Newsletter, 'add').returns({get: getStub});
|
||||
});
|
||||
|
||||
it('rejects if called with no data', async function () {
|
||||
assert.rejects(await newsletterService.add, {name: 'TypeError'});
|
||||
sinon.assert.notCalled(addStub);
|
||||
});
|
||||
|
||||
it('will attempt to add empty object without verification', async function () {
|
||||
const result = await newsletterService.add({});
|
||||
|
||||
assert.equal(result.meta, undefined); // meta property has not been added
|
||||
sinon.assert.calledOnceWithExactly(addStub, {}, undefined);
|
||||
});
|
||||
|
||||
it('will pass object and options through to model when there are no fields needing verification', async function () {
|
||||
const data = {name: 'hello world'};
|
||||
const options = {foo: 'bar'};
|
||||
|
||||
const result = await newsletterService.add(data, options);
|
||||
|
||||
assert.equal(result.meta, undefined); // meta property has not been added
|
||||
sinon.assert.calledOnceWithExactly(addStub, data, options);
|
||||
});
|
||||
|
||||
it('will trigger verification when sender_email is provided', async function () {
|
||||
const data = {name: 'hello world', sender_email: 'test@example.com'};
|
||||
const options = {foo: 'bar'};
|
||||
|
||||
const result = await newsletterService.add(data, options);
|
||||
|
||||
assert.deepEqual(result.meta, {
|
||||
sent_email_verification: [
|
||||
'sender_email'
|
||||
]
|
||||
});
|
||||
sinon.assert.calledOnceWithExactly(addStub, {name: 'hello world'}, options);
|
||||
mockManager.assert.sentEmail({to: 'test@example.com'});
|
||||
sinon.assert.calledOnceWithExactly(tokenProvider.create, {id: undefined, property: 'sender_email', value: 'test@example.com'});
|
||||
});
|
||||
});
|
||||
|
||||
describe('edit', function () {
|
||||
let editStub, findOneStub;
|
||||
beforeEach(function () {
|
||||
// Stub edit as a function that returns its first argument
|
||||
editStub = sinon.stub(models.Newsletter, 'edit').returns({get: getStub});
|
||||
findOneStub = sinon.stub(models.Newsletter, 'findOne').returns({get: getStub});
|
||||
});
|
||||
|
||||
it('rejects if called with no data', async function () {
|
||||
assert.rejects(await newsletterService.add, {name: 'TypeError'});
|
||||
sinon.assert.notCalled(editStub);
|
||||
});
|
||||
|
||||
it('will attempt to add empty object without verification', async function () {
|
||||
const result = await newsletterService.edit({});
|
||||
|
||||
assert.equal(result.meta, undefined); // meta property has not been added
|
||||
sinon.assert.calledOnceWithExactly(editStub, {}, undefined);
|
||||
});
|
||||
|
||||
it('will pass object and options through to model when there are no fields needing verification', async function () {
|
||||
const data = {name: 'hello world'};
|
||||
const options = {foo: 'bar'};
|
||||
|
||||
const result = await newsletterService.edit(data, options);
|
||||
|
||||
assert.equal(result.meta, undefined); // meta property has not been added
|
||||
sinon.assert.calledOnceWithExactly(editStub, data, options);
|
||||
sinon.assert.calledOnceWithExactly(findOneStub, options, {require: true});
|
||||
});
|
||||
|
||||
it('will trigger verification when sender_email is provided', async function () {
|
||||
const data = {name: 'hello world', sender_email: 'test@example.com'};
|
||||
const options = {foo: 'bar'};
|
||||
|
||||
const result = await newsletterService.edit(data, options);
|
||||
|
||||
assert.deepEqual(result.meta, {
|
||||
sent_email_verification: [
|
||||
'sender_email'
|
||||
]
|
||||
});
|
||||
sinon.assert.calledOnceWithExactly(editStub, {name: 'hello world'}, options);
|
||||
sinon.assert.calledOnceWithExactly(findOneStub, options, {require: true});
|
||||
mockManager.assert.sentEmail({to: 'test@example.com'});
|
||||
sinon.assert.calledOnceWithExactly(tokenProvider.create, {id: undefined, property: 'sender_email', value: 'test@example.com'});
|
||||
});
|
||||
|
||||
it('will NOT trigger verification when sender_email is provided but is already verified', async function () {
|
||||
const data = {name: 'hello world', sender_email: 'test@example.com'};
|
||||
const options = {foo: 'bar'};
|
||||
|
||||
// The model says this is already verified
|
||||
getStub.withArgs('sender_email').returns('test@example.com');
|
||||
|
||||
const result = await newsletterService.edit(data, options);
|
||||
|
||||
assert.deepEqual(result.meta, undefined);
|
||||
sinon.assert.calledOnceWithExactly(editStub, {name: 'hello world', sender_email: 'test@example.com'}, options);
|
||||
sinon.assert.calledOnceWithExactly(findOneStub, options, {require: true});
|
||||
mockManager.assert.sentEmailCount(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('verifyPropertyUpdate', function () {
|
||||
let editStub;
|
||||
|
||||
beforeEach(function () {
|
||||
editStub = sinon.stub(models.Newsletter, 'edit').returns({get: getStub});
|
||||
sinon.assert.notCalled(editStub);
|
||||
});
|
||||
|
||||
it('rejects if called with no data', async function () {
|
||||
assert.rejects(await newsletterService.verifyPropertyUpdate, {name: 'TypeError'});
|
||||
});
|
||||
|
||||
it('Updates model with values from token', async function () {
|
||||
const token = JSON.stringify({id: 'abc123', property: 'sender_email', value: 'test@example.com'});
|
||||
|
||||
await newsletterService.verifyPropertyUpdate(token);
|
||||
|
||||
sinon.assert.calledOnceWithExactly(editStub, {sender_email: 'test@example.com'}, {id: 'abc123'});
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue
Block a user