| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568 |
- const SandboxedModule = require('sandboxed-module')
- const cheerio = require('cheerio')
- const path = require('path')
- const { expect } = require('chai')
- const MODULE_PATH = path.join(
- __dirname,
- '../../../../app/src/Features/Email/EmailBuilder'
- )
- const EmailMessageHelper = require('../../../../app/src/Features/Email/EmailMessageHelper')
- const ctaEmailBody = require('../../../../app/src/Features/Email/Bodies/cta-email')
- const NoCTAEmailBody = require('../../../../app/src/Features/Email/Bodies/NoCTAEmailBody')
- const BaseWithHeaderEmailLayout = require('../../../../app/src/Features/Email/Layouts/BaseWithHeaderEmailLayout')
- describe('EmailBuilder', function() {
- before(function() {
- this.settings = {
- appName: 'testApp',
- siteUrl: 'https://www.overleaf.com'
- }
- this.EmailBuilder = SandboxedModule.require(MODULE_PATH, {
- requires: {
- './EmailMessageHelper': EmailMessageHelper,
- './Bodies/cta-email': ctaEmailBody,
- './Bodies/NoCTAEmailBody': NoCTAEmailBody,
- './Layouts/BaseWithHeaderEmailLayout': BaseWithHeaderEmailLayout,
- 'settings-sharelatex': this.settings
- }
- })
- })
- describe('projectInvite', function() {
- beforeEach(function() {
- this.opts = {
- to: 'bob@bob.com',
- first_name: 'bob',
- owner: {
- email: 'sally@hally.com'
- },
- inviteUrl: 'http://example.com/invite',
- project: {
- url: 'http://www.project.com',
- name: 'standard project'
- }
- }
- })
- describe('when sending a normal email', function() {
- beforeEach(function() {
- this.email = this.EmailBuilder.buildEmail('projectInvite', this.opts)
- })
- it('should have html and text properties', function() {
- expect(this.email.html != null).to.equal(true)
- expect(this.email.text != null).to.equal(true)
- })
- it('should not have undefined in it', function() {
- this.email.html.indexOf('undefined').should.equal(-1)
- this.email.subject.indexOf('undefined').should.equal(-1)
- })
- })
- describe('when someone is up to no good', function() {
- beforeEach(function() {
- this.opts.project.name = "<img src='http://evilsite.com/evil.php'>"
- this.email = this.EmailBuilder.buildEmail('projectInvite', this.opts)
- })
- it('should not contain unescaped html in the html part', function() {
- expect(this.email.html).to.contain('New Project')
- })
- it('should not have undefined in it', function() {
- this.email.html.indexOf('undefined').should.equal(-1)
- this.email.subject.indexOf('undefined').should.equal(-1)
- })
- })
- })
- describe('SpamSafe', function() {
- beforeEach(function() {
- this.opts = {
- to: 'bob@joe.com',
- first_name: 'bob',
- owner: {
- email: 'sally@hally.com'
- },
- inviteUrl: 'http://example.com/invite',
- project: {
- url: 'http://www.project.com',
- name: 'come buy my product at http://notascam.com'
- }
- }
- this.email = this.EmailBuilder.buildEmail('projectInvite', this.opts)
- })
- it('should replace spammy project name', function() {
- this.email.html.indexOf('a new project').should.not.equal(-1)
- this.email.subject.indexOf('New Project').should.not.equal(-1)
- })
- })
- describe('ctaTemplate', function() {
- describe('missing required content', function() {
- const content = {
- title: () => {},
- greeting: () => {},
- message: () => {},
- secondaryMessage: () => {},
- ctaText: () => {},
- ctaURL: () => {},
- gmailGoToAction: () => {}
- }
- it('should throw an error when missing title', function() {
- let { title, ...missing } = content
- expect(() => {
- this.EmailBuilder.ctaTemplate(missing)
- }).to.throw(Error)
- })
- it('should throw an error when missing message', function() {
- let { message, ...missing } = content
- expect(() => {
- this.EmailBuilder.ctaTemplate(missing)
- }).to.throw(Error)
- })
- it('should throw an error when missing ctaText', function() {
- let { ctaText, ...missing } = content
- expect(() => {
- this.EmailBuilder.ctaTemplate(missing)
- }).to.throw(Error)
- })
- it('should throw an error when missing ctaURL', function() {
- let { ctaURL, ...missing } = content
- expect(() => {
- this.EmailBuilder.ctaTemplate(missing)
- }).to.throw(Error)
- })
- })
- })
- describe('templates', function() {
- describe('CTA', function() {
- describe('canceledSubscription', function() {
- beforeEach(function() {
- this.emailAddress = 'example@overleaf.com'
- this.opts = {
- to: this.emailAddress
- }
- this.email = this.EmailBuilder.buildEmail(
- 'canceledSubscription',
- this.opts
- )
- this.expectedUrl =
- 'https://docs.google.com/forms/d/e/1FAIpQLSfa7z_s-cucRRXm70N4jEcSbFsZeb0yuKThHGQL8ySEaQzF0Q/viewform?usp=sf_link'
- })
- it('should build the email', function() {
- expect(this.email.html).to.exist
- expect(this.email.text).to.exist
- })
- describe('HTML email', function() {
- it('should include a CTA button and a fallback CTA link', function() {
- const dom = cheerio.load(this.email.html)
- const buttonLink = dom('a:contains("Leave Feedback")')
- expect(buttonLink.length).to.equal(1)
- expect(buttonLink.attr('href')).to.equal(this.expectedUrl)
- const fallback = dom('.force-overleaf-style').last()
- expect(fallback.length).to.equal(1)
- const fallbackLink = fallback.html()
- expect(fallbackLink).to.contain(this.expectedUrl)
- })
- })
- describe('plain text email', function() {
- it('should contain the CTA link', function() {
- expect(this.email.text).to.contain(this.expectedUrl)
- })
- })
- })
- describe('confirmEmail', function() {
- before(function() {
- this.emailAddress = 'example@overleaf.com'
- this.userId = 'abc123'
- this.opts = {
- to: this.emailAddress,
- confirmEmailUrl: `${this.settings.siteUrl}/user/emails/confirm?token=aToken123`,
- sendingUser_id: this.userId
- }
- this.email = this.EmailBuilder.buildEmail('confirmEmail', this.opts)
- })
- it('should build the email', function() {
- expect(this.email.html).to.exist
- expect(this.email.text).to.exist
- })
- describe('HTML email', function() {
- it('should include a CTA button and a fallback CTA link', function() {
- const dom = cheerio.load(this.email.html)
- const buttonLink = dom('a:contains("Confirm Email")')
- expect(buttonLink.length).to.equal(1)
- expect(buttonLink.attr('href')).to.equal(this.opts.confirmEmailUrl)
- const fallback = dom('.force-overleaf-style').last()
- expect(fallback.length).to.equal(1)
- const fallbackLink = fallback.html()
- expect(fallbackLink).to.contain(this.opts.confirmEmailUrl)
- })
- })
- describe('plain text email', function() {
- it('should contain the CTA link', function() {
- expect(this.email.text).to.contain(this.opts.confirmEmailUrl)
- })
- })
- })
- describe('ownershipTransferConfirmationNewOwner', function() {
- before(function() {
- this.emailAddress = 'example@overleaf.com'
- this.opts = {
- to: this.emailAddress,
- previousOwner: {},
- project: {
- _id: 'abc123',
- name: 'example project'
- }
- }
- this.email = this.EmailBuilder.buildEmail(
- 'ownershipTransferConfirmationNewOwner',
- this.opts
- )
- this.expectedUrl = `${
- this.settings.siteUrl
- }/project/${this.opts.project._id.toString()}`
- })
- it('should build the email', function() {
- expect(this.email.html).to.exist
- expect(this.email.text).to.exist
- })
- describe('HTML email', function() {
- it('should include a CTA button and a fallback CTA link', function() {
- const dom = cheerio.load(this.email.html)
- const buttonLink = dom('td a')
- expect(buttonLink).to.exist
- expect(buttonLink.attr('href')).to.equal(this.expectedUrl)
- const fallback = dom('.force-overleaf-style').last()
- expect(fallback).to.exist
- const fallbackLink = fallback.html().replace(/&/g, '&')
- expect(fallbackLink).to.contain(this.expectedUrl)
- })
- })
- describe('plain text email', function() {
- it('should contain the CTA link', function() {
- expect(this.email.text).to.contain(this.expectedUrl)
- })
- })
- })
- describe('passwordResetRequested', function() {
- before(function() {
- this.emailAddress = 'example@overleaf.com'
- this.opts = {
- to: this.emailAddress,
- setNewPasswordUrl: `${
- this.settings.siteUrl
- }/user/password/set?passwordResetToken=aToken&email=${encodeURIComponent(
- this.emailAddress
- )}`
- }
- this.email = this.EmailBuilder.buildEmail(
- 'passwordResetRequested',
- this.opts
- )
- })
- it('should build the email', function() {
- expect(this.email.html).to.exist
- expect(this.email.text).to.exist
- })
- describe('HTML email', function() {
- it('should include a CTA button and a fallback CTA link', function() {
- const dom = cheerio.load(this.email.html)
- const buttonLink = dom('td a')
- expect(buttonLink).to.exist
- expect(buttonLink.attr('href')).to.equal(
- this.opts.setNewPasswordUrl
- )
- const fallback = dom('.force-overleaf-style').last()
- expect(fallback).to.exist
- const fallbackLink = fallback.html().replace(/&/g, '&')
- expect(fallbackLink).to.contain(this.opts.setNewPasswordUrl)
- })
- })
- describe('plain text email', function() {
- it('should contain the CTA link', function() {
- expect(this.email.text).to.contain(this.opts.setNewPasswordUrl)
- })
- })
- })
- describe('verifyEmailToJoinTeam', function() {
- before(function() {
- this.emailAddress = 'example@overleaf.com'
- this.opts = {
- to: this.emailAddress,
- acceptInviteUrl: `${this.settings.siteUrl}/subscription/invites/aToken123/`,
- inviter: {
- email: 'deanna@overleaf.com',
- first_name: 'Deanna',
- last_name: 'Troi'
- }
- }
- this.email = this.EmailBuilder.buildEmail(
- 'verifyEmailToJoinTeam',
- this.opts
- )
- })
- it('should build the email', function() {
- expect(this.email.html).to.exist
- expect(this.email.text).to.exist
- })
- describe('HTML email', function() {
- it('should include a CTA button and a fallback CTA link', function() {
- const dom = cheerio.load(this.email.html)
- const buttonLink = dom('a:contains("Join now")')
- expect(buttonLink.length).to.equal(1)
- expect(buttonLink.attr('href')).to.equal(this.opts.acceptInviteUrl)
- const fallback = dom('.force-overleaf-style').last()
- expect(fallback.length).to.equal(1)
- const fallbackLink = fallback.html()
- expect(fallbackLink).to.contain(this.opts.acceptInviteUrl)
- })
- })
- describe('plain text email', function() {
- it('should contain the CTA link', function() {
- expect(this.email.text).to.contain(this.opts.acceptInviteUrl)
- })
- })
- })
- describe('reactivatedSubscription', function() {
- before(function() {
- this.emailAddress = 'example@overleaf.com'
- this.opts = {
- to: this.emailAddress
- }
- this.email = this.EmailBuilder.buildEmail(
- 'reactivatedSubscription',
- this.opts
- )
- this.expectedUrl = `${this.settings.siteUrl}/user/subscription`
- })
- it('should build the email', function() {
- expect(this.email.html).to.exist
- expect(this.email.text).to.exist
- })
- describe('HTML email', function() {
- it('should include a CTA button and a fallback CTA link', function() {
- const dom = cheerio.load(this.email.html)
- const buttonLink = dom('a:contains("View Subscription Dashboard")')
- expect(buttonLink.length).to.equal(1)
- expect(buttonLink.attr('href')).to.equal(this.expectedUrl)
- const fallback = dom('.force-overleaf-style').last()
- expect(fallback.length).to.equal(1)
- const fallbackLink = fallback.html()
- expect(fallbackLink).to.contain(this.expectedUrl)
- })
- })
- describe('plain text email', function() {
- it('should contain the CTA link', function() {
- expect(this.email.text).to.contain(this.expectedUrl)
- })
- })
- })
- describe('testEmail', function() {
- before(function() {
- this.emailAddress = 'example@overleaf.com'
- this.opts = {
- to: this.emailAddress
- }
- this.email = this.EmailBuilder.buildEmail('testEmail', this.opts)
- })
- it('should build the email', function() {
- expect(this.email.html).to.exist
- expect(this.email.text).to.exist
- })
- describe('HTML email', function() {
- it('should include a CTA button and a fallback CTA link', function() {
- const dom = cheerio.load(this.email.html)
- const buttonLink = dom(
- `a:contains("Open ${this.settings.appName}")`
- )
- expect(buttonLink.length).to.equal(1)
- expect(buttonLink.attr('href')).to.equal(this.settings.siteUrl)
- const fallback = dom('.force-overleaf-style').last()
- expect(fallback.length).to.equal(1)
- const fallbackLink = fallback.html()
- expect(fallbackLink).to.contain(this.settings.siteUrl)
- })
- })
- describe('plain text email', function() {
- it('should contain the CTA link', function() {
- expect(this.email.text).to.contain(
- `Open ${this.settings.appName}: ${this.settings.siteUrl}`
- )
- })
- })
- })
- describe('registered', function() {
- before(function() {
- this.emailAddress = 'example@overleaf.com'
- this.opts = {
- to: this.emailAddress,
- setNewPasswordUrl: `${this.settings.siteUrl}/user/activate?token=aToken123&user_id=aUserId123`
- }
- this.email = this.EmailBuilder.buildEmail('registered', this.opts)
- })
- it('should build the email', function() {
- expect(this.email.html).to.exist
- expect(this.email.text).to.exist
- })
- describe('HTML email', function() {
- it('should include a CTA button and a fallback CTA link', function() {
- const dom = cheerio.load(this.email.html)
- const buttonLink = dom('a:contains("Set password")')
- expect(buttonLink.length).to.equal(1)
- expect(buttonLink.attr('href')).to.equal(
- this.opts.setNewPasswordUrl
- )
- const fallback = dom('.force-overleaf-style').last()
- expect(fallback.length).to.equal(1)
- const fallbackLink = fallback.html().replace(/&/, '&')
- expect(fallbackLink).to.contain(this.opts.setNewPasswordUrl)
- })
- })
- describe('plain text email', function() {
- it('should contain the CTA link', function() {
- expect(this.email.text).to.contain(this.opts.setNewPasswordUrl)
- })
- })
- })
- describe('projectInvite', function() {
- before(function() {
- this.emailAddress = 'example@overleaf.com'
- this.owner = {
- email: 'owner@example.com',
- name: 'Bailey'
- }
- this.projectName = 'Top Secret'
- this.opts = {
- inviteUrl:
- `${this.settings.siteUrl}/project/projectId123/invite/token/aToken123?` +
- [
- `project_name=${encodeURIComponent(this.projectName)}`,
- `user_first_name=${encodeURIComponent(this.owner.name)}`
- ].join('&'),
- owner: {
- email: this.owner.email
- },
- project: {
- name: this.projectName
- },
- to: this.emailAddress
- }
- this.email = this.EmailBuilder.buildEmail('projectInvite', this.opts)
- })
- it('should build the email', function() {
- expect(this.email.html).to.exist
- expect(this.email.text).to.exist
- })
- describe('HTML email', function() {
- it('should include a CTA button and a fallback CTA link', function() {
- const dom = cheerio.load(this.email.html)
- const buttonLink = dom('a:contains("View project")')
- expect(buttonLink.length).to.equal(1)
- expect(buttonLink.attr('href')).to.equal(this.opts.inviteUrl)
- const fallback = dom('.force-overleaf-style').last()
- expect(fallback.length).to.equal(1)
- const fallbackLink = fallback.html().replace(/&/g, '&')
- expect(fallbackLink).to.contain(this.opts.inviteUrl)
- })
- })
- describe('plain text email', function() {
- it('should contain the CTA link', function() {
- expect(this.email.text).to.contain(this.opts.inviteUrl)
- })
- })
- })
- })
- describe('no CTA', function() {
- describe('securityAlert', function() {
- before(function() {
- this.message = 'more details about the action'
- this.messageHTML = `<br /><span style="text-align:center" class="a-class"><b><i>${this.message}</i></b></span>`
- this.messageNotAllowedHTML = `<div></div>${this.messageHTML}`
- this.actionDescribed = 'an action described'
- this.actionDescribedHTML = `<br /><span style="text-align:center" class="a-class"><b><i>${this.actionDescribed}</i></b>`
- this.actionDescribedNotAllowedHTML = `<div></div>${this.actionDescribedHTML}`
- this.opts = {
- to: this.email,
- actionDescribed: this.actionDescribedNotAllowedHTML,
- action: 'an action',
- message: [this.messageNotAllowedHTML]
- }
- this.email = this.EmailBuilder.buildEmail('securityAlert', this.opts)
- })
- it('should build the email', function() {
- expect(this.email.html != null).to.equal(true)
- expect(this.email.text != null).to.equal(true)
- })
- describe('HTML email', function() {
- it('should clean HTML in opts.actionDescribed', function() {
- expect(this.email.html).to.not.contain(
- this.actionDescribedNotAllowedHTML
- )
- expect(this.email.html).to.contain(this.actionDescribedHTML)
- })
- it('should clean HTML in opts.message', function() {
- expect(this.email.html).to.not.contain(this.messageNotAllowedHTML)
- expect(this.email.html).to.contain(this.messageHTML)
- })
- })
- describe('plain text email', function() {
- it('should remove all HTML in opts.actionDescribed', function() {
- expect(this.email.text).to.not.contain(this.actionDescribedHTML)
- expect(this.email.text).to.contain(this.actionDescribed)
- })
- it('should remove all HTML in opts.message', function() {
- expect(this.email.text).to.not.contain(this.messageHTML)
- expect(this.email.text).to.contain(this.message)
- })
- })
- })
- })
- })
- })
|