EmailBuilderTests.js 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823
  1. const SandboxedModule = require('sandboxed-module')
  2. const cheerio = require('cheerio')
  3. const path = require('path')
  4. const { expect } = require('chai')
  5. const MODULE_PATH = path.join(
  6. __dirname,
  7. '../../../../app/src/Features/Email/EmailBuilder'
  8. )
  9. const EmailMessageHelper = require('../../../../app/src/Features/Email/EmailMessageHelper')
  10. const ctaEmailBody = require('../../../../app/src/Features/Email/Bodies/cta-email')
  11. const NoCTAEmailBody = require('../../../../app/src/Features/Email/Bodies/NoCTAEmailBody')
  12. const BaseWithHeaderEmailLayout = require('../../../../app/src/Features/Email/Layouts/BaseWithHeaderEmailLayout')
  13. describe('EmailBuilder', function () {
  14. before(function () {
  15. this.settings = {
  16. appName: 'testApp',
  17. siteUrl: 'https://www.overleaf.com',
  18. }
  19. this.EmailBuilder = SandboxedModule.require(MODULE_PATH, {
  20. requires: {
  21. './EmailMessageHelper': EmailMessageHelper,
  22. './Bodies/cta-email': ctaEmailBody,
  23. './Bodies/NoCTAEmailBody': NoCTAEmailBody,
  24. './Layouts/BaseWithHeaderEmailLayout': BaseWithHeaderEmailLayout,
  25. '@overleaf/settings': this.settings,
  26. },
  27. })
  28. })
  29. describe('projectInvite', function () {
  30. beforeEach(function () {
  31. this.opts = {
  32. to: 'bob@bob.com',
  33. first_name: 'bob',
  34. owner: {
  35. email: 'sally@hally.com',
  36. },
  37. inviteUrl: 'http://example.com/invite',
  38. project: {
  39. url: 'http://www.project.com',
  40. name: 'standard project',
  41. },
  42. }
  43. })
  44. describe('when sending a normal email', function () {
  45. beforeEach(function () {
  46. this.email = this.EmailBuilder.buildEmail('projectInvite', this.opts)
  47. })
  48. it('should have html and text properties', function () {
  49. expect(this.email.html != null).to.equal(true)
  50. expect(this.email.text != null).to.equal(true)
  51. })
  52. it('should not have undefined in it', function () {
  53. this.email.html.indexOf('undefined').should.equal(-1)
  54. this.email.subject.indexOf('undefined').should.equal(-1)
  55. })
  56. })
  57. describe('when someone is up to no good', function () {
  58. it('should not contain the project name at all if unsafe', function () {
  59. this.opts.project.name = "<img src='http://evilsite.com/evil.php'>"
  60. this.email = this.EmailBuilder.buildEmail('projectInvite', this.opts)
  61. expect(this.email.html).to.not.contain('evilsite.com')
  62. expect(this.email.subject).to.not.contain('evilsite.com')
  63. // but email should appear
  64. expect(this.email.html).to.contain(this.opts.owner.email)
  65. expect(this.email.subject).to.contain(this.opts.owner.email)
  66. })
  67. it('should not contain the inviter email at all if unsafe', function () {
  68. this.opts.owner.email =
  69. 'verylongemailaddressthatwillfailthecheck@longdomain.domain'
  70. this.email = this.EmailBuilder.buildEmail('projectInvite', this.opts)
  71. expect(this.email.html).to.not.contain(this.opts.owner.email)
  72. expect(this.email.subject).to.not.contain(this.opts.owner.email)
  73. // but title should appear
  74. expect(this.email.html).to.contain(this.opts.project.name)
  75. expect(this.email.subject).to.contain(this.opts.project.name)
  76. })
  77. it('should handle both email and title being unsafe', function () {
  78. this.opts.project.name = "<img src='http://evilsite.com/evil.php'>"
  79. this.opts.owner.email =
  80. 'verylongemailaddressthatwillfailthecheck@longdomain.domain'
  81. this.email = this.EmailBuilder.buildEmail('projectInvite', this.opts)
  82. expect(this.email.html).to.not.contain('evilsite.com')
  83. expect(this.email.subject).to.not.contain('evilsite.com')
  84. expect(this.email.html).to.not.contain(this.opts.owner.email)
  85. expect(this.email.subject).to.not.contain(this.opts.owner.email)
  86. expect(this.email.html).to.contain(
  87. 'Please view the project to find out more'
  88. )
  89. })
  90. })
  91. })
  92. describe('SpamSafe', function () {
  93. beforeEach(function () {
  94. this.opts = {
  95. to: 'bob@joe.com',
  96. first_name: 'bob',
  97. newOwner: {
  98. email: 'sally@hally.com',
  99. },
  100. inviteUrl: 'http://example.com/invite',
  101. project: {
  102. url: 'http://www.project.com',
  103. name: 'come buy my product at http://notascam.com',
  104. },
  105. }
  106. this.email = this.EmailBuilder.buildEmail(
  107. 'ownershipTransferConfirmationPreviousOwner',
  108. this.opts
  109. )
  110. })
  111. it('should replace spammy project name', function () {
  112. this.email.html.indexOf('your project').should.not.equal(-1)
  113. })
  114. })
  115. describe('ctaTemplate', function () {
  116. describe('missing required content', function () {
  117. const content = {
  118. title: () => {},
  119. greeting: () => {},
  120. message: () => {},
  121. secondaryMessage: () => {},
  122. ctaText: () => {},
  123. ctaURL: () => {},
  124. gmailGoToAction: () => {},
  125. }
  126. it('should throw an error when missing title', function () {
  127. const { title, ...missing } = content
  128. expect(() => {
  129. this.EmailBuilder.ctaTemplate(missing)
  130. }).to.throw(Error)
  131. })
  132. it('should throw an error when missing message', function () {
  133. const { message, ...missing } = content
  134. expect(() => {
  135. this.EmailBuilder.ctaTemplate(missing)
  136. }).to.throw(Error)
  137. })
  138. it('should throw an error when missing ctaText', function () {
  139. const { ctaText, ...missing } = content
  140. expect(() => {
  141. this.EmailBuilder.ctaTemplate(missing)
  142. }).to.throw(Error)
  143. })
  144. it('should throw an error when missing ctaURL', function () {
  145. const { ctaURL, ...missing } = content
  146. expect(() => {
  147. this.EmailBuilder.ctaTemplate(missing)
  148. }).to.throw(Error)
  149. })
  150. })
  151. })
  152. describe('templates', function () {
  153. describe('CTA', function () {
  154. describe('canceledSubscription', function () {
  155. beforeEach(function () {
  156. this.emailAddress = 'example@overleaf.com'
  157. this.opts = {
  158. to: this.emailAddress,
  159. }
  160. this.email = this.EmailBuilder.buildEmail(
  161. 'canceledSubscription',
  162. this.opts
  163. )
  164. this.expectedUrl =
  165. 'https://docs.google.com/forms/d/e/1FAIpQLSfa7z_s-cucRRXm70N4jEcSbFsZeb0yuKThHGQL8ySEaQzF0Q/viewform?usp=sf_link'
  166. })
  167. it('should build the email', function () {
  168. expect(this.email.html).to.exist
  169. expect(this.email.text).to.exist
  170. })
  171. describe('HTML email', function () {
  172. it('should include a CTA button and a fallback CTA link', function () {
  173. const dom = cheerio.load(this.email.html)
  174. const buttonLink = dom('a:contains("Leave Feedback")')
  175. expect(buttonLink.length).to.equal(1)
  176. expect(buttonLink.attr('href')).to.equal(this.expectedUrl)
  177. const fallback = dom('.force-overleaf-style').last()
  178. expect(fallback.length).to.equal(1)
  179. const fallbackLink = fallback.html()
  180. expect(fallbackLink).to.contain(this.expectedUrl)
  181. })
  182. })
  183. describe('plain text email', function () {
  184. it('should contain the CTA link', function () {
  185. expect(this.email.text).to.contain(this.expectedUrl)
  186. })
  187. })
  188. })
  189. describe('confirmEmail', function () {
  190. before(function () {
  191. this.emailAddress = 'example@overleaf.com'
  192. this.userId = 'abc123'
  193. this.opts = {
  194. to: this.emailAddress,
  195. confirmEmailUrl: `${this.settings.siteUrl}/user/emails/confirm?token=aToken123`,
  196. sendingUser_id: this.userId,
  197. }
  198. this.email = this.EmailBuilder.buildEmail('confirmEmail', this.opts)
  199. })
  200. it('should build the email', function () {
  201. expect(this.email.html).to.exist
  202. expect(this.email.text).to.exist
  203. })
  204. describe('HTML email', function () {
  205. it('should include a CTA button and a fallback CTA link', function () {
  206. const dom = cheerio.load(this.email.html)
  207. const buttonLink = dom('a:contains("Confirm Email")')
  208. expect(buttonLink.length).to.equal(1)
  209. expect(buttonLink.attr('href')).to.equal(this.opts.confirmEmailUrl)
  210. const fallback = dom('.force-overleaf-style').last()
  211. expect(fallback.length).to.equal(1)
  212. const fallbackLink = fallback.html()
  213. expect(fallbackLink).to.contain(this.opts.confirmEmailUrl)
  214. })
  215. })
  216. describe('plain text email', function () {
  217. it('should contain the CTA link', function () {
  218. expect(this.email.text).to.contain(this.opts.confirmEmailUrl)
  219. })
  220. })
  221. })
  222. describe('ownershipTransferConfirmationNewOwner', function () {
  223. before(function () {
  224. this.emailAddress = 'example@overleaf.com'
  225. this.opts = {
  226. to: this.emailAddress,
  227. previousOwner: {},
  228. project: {
  229. _id: 'abc123',
  230. name: 'example project',
  231. },
  232. }
  233. this.email = this.EmailBuilder.buildEmail(
  234. 'ownershipTransferConfirmationNewOwner',
  235. this.opts
  236. )
  237. this.expectedUrl = `${
  238. this.settings.siteUrl
  239. }/project/${this.opts.project._id.toString()}`
  240. })
  241. it('should build the email', function () {
  242. expect(this.email.html).to.exist
  243. expect(this.email.text).to.exist
  244. })
  245. describe('HTML email', function () {
  246. it('should include a CTA button and a fallback CTA link', function () {
  247. const dom = cheerio.load(this.email.html)
  248. const buttonLink = dom('td a')
  249. expect(buttonLink).to.exist
  250. expect(buttonLink.attr('href')).to.equal(this.expectedUrl)
  251. const fallback = dom('.force-overleaf-style').last()
  252. expect(fallback).to.exist
  253. const fallbackLink = fallback.html().replace(/&amp;/g, '&')
  254. expect(fallbackLink).to.contain(this.expectedUrl)
  255. })
  256. })
  257. describe('plain text email', function () {
  258. it('should contain the CTA link', function () {
  259. expect(this.email.text).to.contain(this.expectedUrl)
  260. })
  261. })
  262. })
  263. describe('passwordResetRequested', function () {
  264. before(function () {
  265. this.emailAddress = 'example@overleaf.com'
  266. this.opts = {
  267. to: this.emailAddress,
  268. setNewPasswordUrl: `${
  269. this.settings.siteUrl
  270. }/user/password/set?passwordResetToken=aToken&email=${encodeURIComponent(
  271. this.emailAddress
  272. )}`,
  273. }
  274. this.email = this.EmailBuilder.buildEmail(
  275. 'passwordResetRequested',
  276. this.opts
  277. )
  278. })
  279. it('should build the email', function () {
  280. expect(this.email.html).to.exist
  281. expect(this.email.text).to.exist
  282. })
  283. describe('HTML email', function () {
  284. it('should include a CTA button and a fallback CTA link', function () {
  285. const dom = cheerio.load(this.email.html)
  286. const buttonLink = dom('td a')
  287. expect(buttonLink).to.exist
  288. expect(buttonLink.attr('href')).to.equal(
  289. this.opts.setNewPasswordUrl
  290. )
  291. const fallback = dom('.force-overleaf-style').last()
  292. expect(fallback).to.exist
  293. const fallbackLink = fallback.html().replace(/&amp;/g, '&')
  294. expect(fallbackLink).to.contain(this.opts.setNewPasswordUrl)
  295. })
  296. })
  297. describe('plain text email', function () {
  298. it('should contain the CTA link', function () {
  299. expect(this.email.text).to.contain(this.opts.setNewPasswordUrl)
  300. })
  301. })
  302. })
  303. describe('reconfirmEmail', function () {
  304. before(function () {
  305. this.emailAddress = 'example@overleaf.com'
  306. this.userId = 'abc123'
  307. this.opts = {
  308. to: this.emailAddress,
  309. confirmEmailUrl: `${this.settings.siteUrl}/user/emails/confirm?token=aToken123`,
  310. sendingUser_id: this.userId,
  311. }
  312. this.email = this.EmailBuilder.buildEmail('reconfirmEmail', this.opts)
  313. })
  314. it('should build the email', function () {
  315. expect(this.email.html).to.exist
  316. expect(this.email.text).to.exist
  317. })
  318. describe('HTML email', function () {
  319. it('should include a CTA button and a fallback CTA link', function () {
  320. const dom = cheerio.load(this.email.html)
  321. const buttonLink = dom('a:contains("Reconfirm Email")')
  322. expect(buttonLink.length).to.equal(1)
  323. expect(buttonLink.attr('href')).to.equal(this.opts.confirmEmailUrl)
  324. const fallback = dom('.force-overleaf-style').last()
  325. expect(fallback.length).to.equal(1)
  326. const fallbackLink = fallback.html()
  327. expect(fallbackLink).to.contain(this.opts.confirmEmailUrl)
  328. })
  329. })
  330. describe('plain text email', function () {
  331. it('should contain the CTA link', function () {
  332. expect(this.email.text).to.contain(this.opts.confirmEmailUrl)
  333. })
  334. })
  335. })
  336. describe('verifyEmailToJoinTeam', function () {
  337. before(function () {
  338. this.emailAddress = 'example@overleaf.com'
  339. this.opts = {
  340. to: this.emailAddress,
  341. acceptInviteUrl: `${this.settings.siteUrl}/subscription/invites/aToken123/`,
  342. inviter: {
  343. email: 'deanna@overleaf.com',
  344. first_name: 'Deanna',
  345. last_name: 'Troi',
  346. },
  347. }
  348. this.email = this.EmailBuilder.buildEmail(
  349. 'verifyEmailToJoinTeam',
  350. this.opts
  351. )
  352. })
  353. it('should build the email', function () {
  354. expect(this.email.html).to.exist
  355. expect(this.email.text).to.exist
  356. })
  357. describe('HTML email', function () {
  358. it('should include a CTA button and a fallback CTA link', function () {
  359. const dom = cheerio.load(this.email.html)
  360. const buttonLink = dom('a:contains("Join now")')
  361. expect(buttonLink.length).to.equal(1)
  362. expect(buttonLink.attr('href')).to.equal(this.opts.acceptInviteUrl)
  363. const fallback = dom('.force-overleaf-style').last()
  364. expect(fallback.length).to.equal(1)
  365. const fallbackLink = fallback.html()
  366. expect(fallbackLink).to.contain(this.opts.acceptInviteUrl)
  367. })
  368. })
  369. describe('plain text email', function () {
  370. it('should contain the CTA link', function () {
  371. expect(this.email.text).to.contain(this.opts.acceptInviteUrl)
  372. })
  373. })
  374. })
  375. describe('reactivatedSubscription', function () {
  376. before(function () {
  377. this.emailAddress = 'example@overleaf.com'
  378. this.opts = {
  379. to: this.emailAddress,
  380. }
  381. this.email = this.EmailBuilder.buildEmail(
  382. 'reactivatedSubscription',
  383. this.opts
  384. )
  385. this.expectedUrl = `${this.settings.siteUrl}/user/subscription`
  386. })
  387. it('should build the email', function () {
  388. expect(this.email.html).to.exist
  389. expect(this.email.text).to.exist
  390. })
  391. describe('HTML email', function () {
  392. it('should include a CTA button and a fallback CTA link', function () {
  393. const dom = cheerio.load(this.email.html)
  394. const buttonLink = dom('a:contains("View Subscription Dashboard")')
  395. expect(buttonLink.length).to.equal(1)
  396. expect(buttonLink.attr('href')).to.equal(this.expectedUrl)
  397. const fallback = dom('.force-overleaf-style').last()
  398. expect(fallback.length).to.equal(1)
  399. const fallbackLink = fallback.html()
  400. expect(fallbackLink).to.contain(this.expectedUrl)
  401. })
  402. })
  403. describe('plain text email', function () {
  404. it('should contain the CTA link', function () {
  405. expect(this.email.text).to.contain(this.expectedUrl)
  406. })
  407. })
  408. })
  409. describe('testEmail', function () {
  410. before(function () {
  411. this.emailAddress = 'example@overleaf.com'
  412. this.opts = {
  413. to: this.emailAddress,
  414. }
  415. this.email = this.EmailBuilder.buildEmail('testEmail', this.opts)
  416. })
  417. it('should build the email', function () {
  418. expect(this.email.html).to.exist
  419. expect(this.email.text).to.exist
  420. })
  421. describe('HTML email', function () {
  422. it('should include a CTA button and a fallback CTA link', function () {
  423. const dom = cheerio.load(this.email.html)
  424. const buttonLink = dom(
  425. `a:contains("Open ${this.settings.appName}")`
  426. )
  427. expect(buttonLink.length).to.equal(1)
  428. expect(buttonLink.attr('href')).to.equal(this.settings.siteUrl)
  429. const fallback = dom('.force-overleaf-style').last()
  430. expect(fallback.length).to.equal(1)
  431. const fallbackLink = fallback.html()
  432. expect(fallbackLink).to.contain(this.settings.siteUrl)
  433. })
  434. })
  435. describe('plain text email', function () {
  436. it('should contain the CTA link', function () {
  437. expect(this.email.text).to.contain(
  438. `Open ${this.settings.appName}: ${this.settings.siteUrl}`
  439. )
  440. })
  441. })
  442. })
  443. describe('registered', function () {
  444. before(function () {
  445. this.emailAddress = 'example@overleaf.com'
  446. this.opts = {
  447. to: this.emailAddress,
  448. setNewPasswordUrl: `${this.settings.siteUrl}/user/activate?token=aToken123&user_id=aUserId123`,
  449. }
  450. this.email = this.EmailBuilder.buildEmail('registered', this.opts)
  451. })
  452. it('should build the email', function () {
  453. expect(this.email.html).to.exist
  454. expect(this.email.text).to.exist
  455. })
  456. describe('HTML email', function () {
  457. it('should include a CTA button and a fallback CTA link', function () {
  458. const dom = cheerio.load(this.email.html)
  459. const buttonLink = dom('a:contains("Set password")')
  460. expect(buttonLink.length).to.equal(1)
  461. expect(buttonLink.attr('href')).to.equal(
  462. this.opts.setNewPasswordUrl
  463. )
  464. const fallback = dom('.force-overleaf-style').last()
  465. expect(fallback.length).to.equal(1)
  466. const fallbackLink = fallback.html().replace(/&amp;/, '&')
  467. expect(fallbackLink).to.contain(this.opts.setNewPasswordUrl)
  468. })
  469. })
  470. describe('plain text email', function () {
  471. it('should contain the CTA link', function () {
  472. expect(this.email.text).to.contain(this.opts.setNewPasswordUrl)
  473. })
  474. })
  475. })
  476. describe('projectInvite', function () {
  477. before(function () {
  478. this.emailAddress = 'example@overleaf.com'
  479. this.owner = {
  480. email: 'owner@example.com',
  481. name: 'Bailey',
  482. }
  483. this.projectName = 'Top Secret'
  484. this.opts = {
  485. inviteUrl: `${this.settings.siteUrl}/project/projectId123/invite/token/aToken123`,
  486. owner: {
  487. email: this.owner.email,
  488. },
  489. project: {
  490. name: this.projectName,
  491. },
  492. to: this.emailAddress,
  493. }
  494. this.email = this.EmailBuilder.buildEmail('projectInvite', this.opts)
  495. })
  496. it('should build the email', function () {
  497. expect(this.email.html).to.exist
  498. expect(this.email.text).to.exist
  499. })
  500. describe('HTML email', function () {
  501. it('should include a CTA button and a fallback CTA link', function () {
  502. const dom = cheerio.load(this.email.html)
  503. const buttonLink = dom('a:contains("View project")')
  504. expect(buttonLink.length).to.equal(1)
  505. expect(buttonLink.attr('href')).to.equal(this.opts.inviteUrl)
  506. const fallback = dom('.force-overleaf-style').last()
  507. expect(fallback.length).to.equal(1)
  508. const fallbackLink = fallback.html().replace(/&amp;/g, '&')
  509. expect(fallbackLink).to.contain(this.opts.inviteUrl)
  510. })
  511. })
  512. describe('plain text email', function () {
  513. it('should contain the CTA link', function () {
  514. expect(this.email.text).to.contain(this.opts.inviteUrl)
  515. })
  516. })
  517. })
  518. describe('welcome', function () {
  519. beforeEach(function () {
  520. this.emailAddress = 'example@overleaf.com'
  521. this.opts = {
  522. to: this.emailAddress,
  523. confirmEmailUrl: `${this.settings.siteUrl}/user/emails/confirm?token=token123`,
  524. }
  525. this.email = this.EmailBuilder.buildEmail('welcome', this.opts)
  526. this.dom = cheerio.load(this.email.html)
  527. })
  528. it('should build the email', function () {
  529. expect(this.email.html).to.exist
  530. expect(this.email.text).to.exist
  531. })
  532. describe('HTML email', function () {
  533. it('should include a CTA button and a fallback CTA link', function () {
  534. const buttonLink = this.dom('a:contains("Confirm Email")')
  535. expect(buttonLink.length).to.equal(1)
  536. expect(buttonLink.attr('href')).to.equal(this.opts.confirmEmailUrl)
  537. const fallback = this.dom('.force-overleaf-style').last()
  538. expect(fallback.length).to.equal(1)
  539. expect(fallback.html()).to.contain(this.opts.confirmEmailUrl)
  540. })
  541. it('should include help links', function () {
  542. const helpGuidesLink = this.dom('a:contains("Help Guides")')
  543. const templatesLink = this.dom('a:contains("Templates")')
  544. const logInLink = this.dom('a:contains("log in")')
  545. expect(helpGuidesLink.length).to.equal(1)
  546. expect(templatesLink.length).to.equal(1)
  547. expect(logInLink.length).to.equal(1)
  548. })
  549. })
  550. describe('plain text email', function () {
  551. it('should contain the CTA URL', function () {
  552. expect(this.email.text).to.contain(this.opts.confirmEmailUrl)
  553. })
  554. it('should include help URL', function () {
  555. expect(this.email.text).to.contain('/learn')
  556. expect(this.email.text).to.contain('/login')
  557. expect(this.email.text).to.contain('/templates')
  558. })
  559. it('should contain HTML links', function () {
  560. expect(this.email.text).to.not.contain('<a')
  561. })
  562. })
  563. })
  564. describe('groupSSODisabled', function () {
  565. it('should build the email for non managed and linked users', function () {
  566. const setNewPasswordUrl = `${this.settings.siteUrl}/user/password/reset`
  567. const emailAddress = 'example@overleaf.com'
  568. const opts = {
  569. to: emailAddress,
  570. setNewPasswordUrl,
  571. userIsManaged: false,
  572. }
  573. const email = this.EmailBuilder.buildEmail('groupSSODisabled', opts)
  574. expect(email.subject).to.equal(
  575. 'A change to your Overleaf login options'
  576. )
  577. const dom = cheerio.load(email.html)
  578. expect(email.html).to.exist
  579. expect(email.html).to.contain(
  580. 'Your group administrator has disabled single sign-on for your group.'
  581. )
  582. expect(email.html).to.contain(
  583. 'You can still log in to Overleaf using one of our other'
  584. )
  585. const links = dom('a')
  586. expect(links[0].attribs.href).to.equal(
  587. `${this.settings.siteUrl}/login`
  588. )
  589. expect(links[1].attribs.href).to.equal(setNewPasswordUrl)
  590. expect(email.html).to.contain(
  591. "If you don't have a password, you can set one now."
  592. )
  593. expect(email.text).to.exist
  594. const expectedPlainText = [
  595. 'Hi,',
  596. '',
  597. 'Your group administrator has disabled single sign-on for your group.',
  598. '',
  599. '',
  600. '',
  601. 'What does this mean for you?',
  602. '',
  603. 'You can still log in to Overleaf using one of our other login options or with your email address and password.',
  604. '',
  605. "If you don't have a password, you can set one now.",
  606. '',
  607. `Set your new password: ${setNewPasswordUrl}`,
  608. '',
  609. '',
  610. '',
  611. 'Regards,',
  612. `The ${this.settings.appName} Team - ${this.settings.siteUrl}`,
  613. ]
  614. expect(email.text.split(/\r?\n/)).to.deep.equal(expectedPlainText)
  615. })
  616. it('should build the email for managed and linked users', function () {
  617. const emailAddress = 'example@overleaf.com'
  618. const setNewPasswordUrl = `${this.settings.siteUrl}/user/password/reset`
  619. const opts = {
  620. to: emailAddress,
  621. setNewPasswordUrl,
  622. userIsManaged: true,
  623. }
  624. const email = this.EmailBuilder.buildEmail('groupSSODisabled', opts)
  625. expect(email.subject).to.equal(
  626. 'Action required: Set your Overleaf password'
  627. )
  628. const dom = cheerio.load(email.html)
  629. expect(email.html).to.exist
  630. expect(email.html).to.contain(
  631. 'Your group administrator has disabled single sign-on for your group.'
  632. )
  633. expect(email.html).to.contain(
  634. 'You now need an email address and password to sign in to your Overleaf account.'
  635. )
  636. const links = dom('a')
  637. expect(links[0].attribs.href).to.equal(
  638. `${this.settings.siteUrl}/user/password/reset`
  639. )
  640. expect(email.text).to.exist
  641. const expectedPlainText = [
  642. 'Hi,',
  643. '',
  644. 'Your group administrator has disabled single sign-on for your group.',
  645. '',
  646. '',
  647. '',
  648. 'What does this mean for you?',
  649. '',
  650. 'You now need an email address and password to sign in to your Overleaf account.',
  651. '',
  652. `Set your new password: ${setNewPasswordUrl}`,
  653. '',
  654. '',
  655. '',
  656. 'Regards,',
  657. `The ${this.settings.appName} Team - ${this.settings.siteUrl}`,
  658. ]
  659. expect(email.text.split(/\r?\n/)).to.deep.equal(expectedPlainText)
  660. })
  661. })
  662. })
  663. describe('no CTA', function () {
  664. describe('securityAlert', function () {
  665. before(function () {
  666. this.message = 'more details about the action'
  667. this.messageHTML = `<br /><span style="text-align:center" class="a-class"><b><i>${this.message}</i></b></span>`
  668. this.messageNotAllowedHTML = `<div></div>${this.messageHTML}`
  669. this.actionDescribed = 'an action described'
  670. this.actionDescribedHTML = `<br /><span style="text-align:center" class="a-class"><b><i>${this.actionDescribed}</i></b>`
  671. this.actionDescribedNotAllowedHTML = `<div></div>${this.actionDescribedHTML}`
  672. this.opts = {
  673. to: this.email,
  674. actionDescribed: this.actionDescribedNotAllowedHTML,
  675. action: 'an action',
  676. message: [this.messageNotAllowedHTML],
  677. }
  678. this.email = this.EmailBuilder.buildEmail('securityAlert', this.opts)
  679. })
  680. it('should build the email', function () {
  681. expect(this.email.html != null).to.equal(true)
  682. expect(this.email.text != null).to.equal(true)
  683. })
  684. describe('HTML email', function () {
  685. it('should clean HTML in opts.actionDescribed', function () {
  686. expect(this.email.html).to.not.contain(
  687. this.actionDescribedNotAllowedHTML
  688. )
  689. expect(this.email.html).to.contain(this.actionDescribedHTML)
  690. })
  691. it('should clean HTML in opts.message', function () {
  692. expect(this.email.html).to.not.contain(this.messageNotAllowedHTML)
  693. expect(this.email.html).to.contain(this.messageHTML)
  694. })
  695. })
  696. describe('plain text email', function () {
  697. it('should remove all HTML in opts.actionDescribed', function () {
  698. expect(this.email.text).to.not.contain(this.actionDescribedHTML)
  699. expect(this.email.text).to.contain(this.actionDescribed)
  700. })
  701. it('should remove all HTML in opts.message', function () {
  702. expect(this.email.text).to.not.contain(this.messageHTML)
  703. expect(this.email.text).to.contain(this.message)
  704. })
  705. })
  706. })
  707. describe('welcomeWithoutCTA', function () {
  708. beforeEach(function () {
  709. this.emailAddress = 'example@overleaf.com'
  710. this.opts = {
  711. to: this.emailAddress,
  712. }
  713. this.email = this.EmailBuilder.buildEmail(
  714. 'welcomeWithoutCTA',
  715. this.opts
  716. )
  717. this.dom = cheerio.load(this.email.html)
  718. })
  719. it('should build the email', function () {
  720. expect(this.email.html).to.exist
  721. expect(this.email.text).to.exist
  722. })
  723. describe('HTML email', function () {
  724. it('should include help links', function () {
  725. const helpGuidesLink = this.dom('a:contains("Help Guides")')
  726. const templatesLink = this.dom('a:contains("Templates")')
  727. const logInLink = this.dom('a:contains("log in")')
  728. expect(helpGuidesLink.length).to.equal(1)
  729. expect(templatesLink.length).to.equal(1)
  730. expect(logInLink.length).to.equal(1)
  731. })
  732. })
  733. describe('plain text email', function () {
  734. it('should include help URL', function () {
  735. expect(this.email.text).to.contain('/learn')
  736. expect(this.email.text).to.contain('/login')
  737. expect(this.email.text).to.contain('/templates')
  738. })
  739. it('should contain HTML links', function () {
  740. expect(this.email.text).to.not.contain('<a')
  741. })
  742. })
  743. })
  744. })
  745. })
  746. })