Merge pull request #2669 from mozilla/MNTOR-249-email-coverage

MNTOR-249: Improve test coverage of EmailUtils
This commit is contained in:
John Whitlock 2022-10-10 10:56:36 -05:00 коммит произвёл GitHub
Родитель 73fcadc9e7 844d973a75
Коммит 0ae5f8b061
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
2 изменённых файлов: 154 добавлений и 21 удалений

Просмотреть файл

@ -99,6 +99,7 @@ const EmailUtils = {
},
getVerificationUrl (subscriber) {
if (!subscriber.verification_token) throw new Error('subscriber has no verification_token')
let url = new URL(`${AppConstants.SERVER_URL}/user/verify`)
url = this.appendUtmParams(url, 'verified-subscribers', 'account-verification-email')
url.searchParams.append('token', encodeURIComponent(subscriber.verification_token))
@ -108,8 +109,8 @@ const EmailUtils = {
getUnsubscribeUrl (subscriber, emailType) {
// TODO: email unsubscribe is broken for most emails
let url = new URL(`${AppConstants.SERVER_URL}/user/unsubscribe`)
const token = (subscriber.hasOwnProperty('verification_token')) ? subscriber.verification_token : subscriber.primary_verification_token
const hash = (subscriber.hasOwnProperty('sha1')) ? subscriber.sha1 : subscriber.primary_sha1
const token = (Object.prototype.hasOwnProperty.call(subscriber, 'verification_token')) ? subscriber.verification_token : subscriber.primary_verification_token
const hash = (Object.prototype.hasOwnProperty.call(subscriber, 'sha1')) ? subscriber.sha1 : subscriber.primary_sha1
url.searchParams.append('token', encodeURIComponent(token))
url.searchParams.append('hash', encodeURIComponent(hash))
url = this.appendUtmParams(url, 'unsubscribe', emailType)
@ -118,6 +119,7 @@ const EmailUtils = {
getMonthlyUnsubscribeUrl (subscriber, campaign, content) {
// TODO: create new subscriptions section in settings to manage all emails and avoid one-off routes like this
if (!subscriber.primary_verification_token) throw new Error('subscriber has no primary verification_token')
let url = new URL('user/unsubscribe-monthly/', AppConstants.SERVER_URL)
url = this.appendUtmParams(url, campaign, content)

Просмотреть файл

@ -3,40 +3,149 @@
const nodemailer = require('nodemailer')
const EmailUtils = require('../email-utils')
const { EMAIL_FROM, SERVER_URL, SES_CONFIG_SET } = require('../app-constants')
const { TEST_SUBSCRIBERS, TEST_EMAIL_ADDRESSES } = require('../db/seeds/test_subscribers')
jest.mock('nodemailer')
test('EmailUtils.init with empty host doesnt invoke nodemailer', () => {
nodemailer.createTransport = jest.fn()
EmailUtils.init('')
const mockCreateTransport = nodemailer.createTransport.mock
expect(mockCreateTransport.calls.length).toBe(1)
expect(mockCreateTransport.calls[0][0]).toEqual({ jsonTransport: true })
test('EmailUtils.sendEmail before .init() fails', async () => {
const sendMailArgs = ['test@example.com', 'subject', 'template.hbs', { breach: 'Test' }]
const expectedError = new Error('SMTP transport not initialized')
await expect(EmailUtils.sendEmail(...sendMailArgs)).rejects.toEqual(expectedError)
})
test.skip('EmailUtils.init with user, pass, host, port invokes nodemailer.createTransport', () => {
test('EmailUtils.init with empty host uses jsonTransport', async () => {
nodemailer.createTransport = jest.fn()
await expect(EmailUtils.init('')).resolves.toBe(true)
expect(nodemailer.createTransport).toHaveBeenCalledWith({ jsonTransport: true })
})
test('EmailUtils.init with SMTP URL invokes nodemailer.createTransport', async () => {
const testSmtpUrl = 'smtps://test:test@test:1'
nodemailer.createTransport = jest.fn()
const mockTransporter = {
verify: jest.fn().mockReturnValueOnce("✓"),
use: jest.fn(),
}
nodemailer.createTransport = jest.fn().mockReturnValueOnce(mockTransporter)
EmailUtils.init(testSmtpUrl)
await expect(EmailUtils.init(testSmtpUrl)).resolves.toBe("✓")
const mockCreateTransport = nodemailer.createTransport.mock
expect(mockCreateTransport.calls.length).toBe(1)
expect(mockCreateTransport.calls[0][0]).toBe(testSmtpUrl)
expect(nodemailer.createTransport).toHaveBeenCalledWith(testSmtpUrl)
expect(mockTransporter.verify).toHaveBeenCalledWith()
expect(mockTransporter.use.mock.calls.length).toBe(1)
expect(mockTransporter.use.mock.calls[0].length).toEqual(2)
expect(mockTransporter.use.mock.calls[0][0]).toBe('compile')
})
test.skip('EmailUtils.sendEmail with recipient, subject, template, context calls gTransporter.sendMail', () => {
test('EmailUtils.sendEmail with recipient, subject, template, context calls gTransporter.sendMail', async () => {
const testSmtpUrl = 'smtps://test:test@test:1'
const sendMailArgs = ['test@example.com', 'subject', 'template.hbs', { breach: 'Test' }]
nodemailer.createTransport = jest.fn()
const mockTransporter = {
verify: jest.fn().mockReturnValueOnce("verified"),
use: jest.fn(),
sendMail: jest.fn((options, cb) => cb(null, "sent")),
transporter: {name: 'MockTransporter'}
}
nodemailer.createTransport = jest.fn().mockReturnValueOnce(mockTransporter)
EmailUtils.init(testSmtpUrl)
EmailUtils.sendEmail(...sendMailArgs)
await expect(EmailUtils.init(testSmtpUrl)).resolves.toBe("verified")
await expect(EmailUtils.sendEmail(...sendMailArgs)).resolves.toBe("sent")
// TODO: find a way to expect gTransporter.sendMail
expect(mockTransporter.sendMail.mock.calls.length).toBe(1)
expect(mockTransporter.sendMail.mock.calls[0].length).toBe(2)
expect(mockTransporter.sendMail.mock.calls[0][0]).toEqual(
{
context: {
SERVER_URL,
layout: 'template.hbs',
breach: 'Test',
},
from: EMAIL_FROM,
headers: {"x-ses-configuration-set": SES_CONFIG_SET},
subject: "subject",
template: "template.hbs",
to: "test@example.com",
})
})
test('EmailUtils.sendEmail rejects with error', async () => {
const testSmtpUrl = 'smtps://test:test@test:1'
const sendMailArgs = ['test@example.com', 'subject', 'template.hbs', { breach: 'Test' }]
const mockTransporter = {
verify: jest.fn().mockReturnValueOnce("verified"),
use: jest.fn(),
sendMail: jest.fn((options, cb) => cb("error", null)),
transporter: {name: 'MockTransporter'}
}
nodemailer.createTransport = jest.fn().mockReturnValueOnce(mockTransporter)
await expect(EmailUtils.init('smtps://test:test@test:1')).resolves.toBe("verified")
await expect(EmailUtils.sendEmail(...sendMailArgs)).rejects.toBe("error")
})
test('EmailUtils.init with empty host uses jsonTransport. logs messages', async () => {
const sendMailArgs = ['test@example.com', 'subject', 'template.hbs', { breach: 'Test' }]
const sendMailInfo = {message: "sent"}
const mockTransporter = {
sendMail: jest.fn((options, cb) => cb(null, sendMailInfo)),
transporter: {name: 'JSONTransport'}
}
nodemailer.createTransport = jest.fn().mockReturnValueOnce(mockTransporter)
await expect(EmailUtils.init('')).resolves.toBe(true)
expect(nodemailer.createTransport).toHaveBeenCalledWith({ jsonTransport: true })
await expect(EmailUtils.sendEmail(...sendMailArgs)).resolves.toEqual(sendMailInfo)
})
test('EmailUtils.getEmailCtaHref works without a subscriber ID', () => {
const emailCtaHref = EmailUtils.getEmailCtaHref('email-type', 'content')
expect(emailCtaHref.pathname).toBe('/')
emailCtaHref.searchParams.sort()
expect(Array.from(emailCtaHref.searchParams.entries())).toEqual(
[
['utm_campaign', 'email-type'],
['utm_content', 'content'],
['utm_medium', 'email'],
['utm_source', 'fx-monitor'],
])
})
test('EmailUtils.getEmailCtaHref works with a subscriber ID', () => {
const emailCtaHref = EmailUtils.getEmailCtaHref('email-type-2', 'content-2', 1234)
expect(emailCtaHref.pathname).toBe('/')
emailCtaHref.searchParams.sort()
expect(Array.from(emailCtaHref.searchParams.entries())).toEqual(
[
['subscriber_id', '1234'],
['utm_campaign', 'email-type-2'],
['utm_content', 'content-2'],
['utm_medium', 'email'],
['utm_source', 'fx-monitor'],
])
})
test('EmailUtils.getVerificationUrl returns a URL', () => {
const fakeSubscriber = {"verification_token": "SubscriberVerificationToken"}
const verificationUrl = EmailUtils.getVerificationUrl(fakeSubscriber)
expect(verificationUrl.pathname).toBe('/user/verify')
verificationUrl.searchParams.sort()
expect(Array.from(verificationUrl.searchParams.entries())).toEqual(
[
['token', 'SubscriberVerificationToken'],
['utm_campaign', 'verified-subscribers'],
['utm_content', 'account-verification-email'],
['utm_medium', 'email'],
['utm_source', 'fx-monitor'],
])
})
test('EmailUtils.getVerificationUrl throws when subscriber has no token', () => {
const fakeSubscriber = {"verification_token": null}
const expected = 'subscriber has no verification_token'
expect(() => EmailUtils.getVerificationUrl(fakeSubscriber)).toThrow(expected)
})
test('EmailUtils.getUnsubscribeUrl works with subscriber record', () => {
@ -56,3 +165,25 @@ test('EmailUtils.getUnsubscribeUrl works with email_address record', () => {
expect(unsubUrl).toMatch(emailAddressRecord.sha1)
expect(unsubUrl).toMatch(emailAddressRecord.verification_token)
})
test('EmailUtils.getMonthlyUnsubscribeUrl returns unsubscribe URL', () => {
const fakeSubscriber = {'primary_verification_token': 'PrimaryVerificationToken'}
const unsubUrl = EmailUtils.getMonthlyUnsubscribeUrl(fakeSubscriber, 'campaign', 'content')
expect(unsubUrl.pathname).toBe('/user/unsubscribe-monthly/')
unsubUrl.searchParams.sort()
expect(Array.from(unsubUrl.searchParams.entries())).toEqual(
[
['token', 'PrimaryVerificationToken'],
['utm_campaign', 'campaign'],
['utm_content', 'content'],
['utm_medium', 'email'],
['utm_source', 'fx-monitor'],
])
})
test('EmailUtils.getMonthlyUnsubscribeUrl throws when subscriber has no token', () => {
const fakeSubscriber = {'primary_verification_token': null}
const expected = 'subscriber has no primary verification_token'
expect(() => EmailUtils.getMonthlyUnsubscribeUrl(fakeSubscriber, 'campaign', 'content')).toThrow(expected)
})