EmailSenderTests.js 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. const SandboxedModule = require('sandboxed-module')
  2. const path = require('path')
  3. const sinon = require('sinon')
  4. const { expect } = require('chai')
  5. const MODULE_PATH = path.join(
  6. __dirname,
  7. '../../../../app/src/Features/Email/EmailSender.js'
  8. )
  9. describe('EmailSender', function () {
  10. beforeEach(function () {
  11. this.rateLimiter = {
  12. consume: sinon.stub().resolves(),
  13. }
  14. this.RateLimiter = {
  15. RateLimiter: sinon.stub().returns(this.rateLimiter),
  16. }
  17. this.Settings = {
  18. email: {
  19. transport: 'ses',
  20. parameters: {
  21. AWSAccessKeyID: 'key',
  22. AWSSecretKey: 'secret',
  23. },
  24. fromAddress: 'bob@bob.com',
  25. replyToAddress: 'sally@gmail.com',
  26. },
  27. }
  28. this.sesClient = { sendMail: sinon.stub().resolves() }
  29. this.ses = { createTransport: () => this.sesClient }
  30. this.SESClient = sinon.stub()
  31. this.EmailSender = SandboxedModule.require(MODULE_PATH, {
  32. requires: {
  33. nodemailer: this.ses,
  34. '@aws-sdk/client-ses': { SESClient: this.SESClient },
  35. '@overleaf/settings': this.Settings,
  36. '../../infrastructure/RateLimiter': this.RateLimiter,
  37. '@overleaf/metrics': {
  38. inc() {},
  39. },
  40. },
  41. })
  42. this.opts = {
  43. to: 'bob@bob.com',
  44. subject: 'new email',
  45. html: '<hello></hello>',
  46. }
  47. })
  48. describe('sendEmail', function () {
  49. it('should set the properties on the email to send', async function () {
  50. await this.EmailSender.promises.sendEmail(this.opts)
  51. expect(this.sesClient.sendMail).to.have.been.calledWithMatch({
  52. html: this.opts.html,
  53. to: this.opts.to,
  54. subject: this.opts.subject,
  55. })
  56. })
  57. it('should return a non-specific error', async function () {
  58. this.sesClient.sendMail.rejects(new Error('boom'))
  59. await expect(this.EmailSender.promises.sendEmail({})).to.be.rejectedWith(
  60. 'error sending message'
  61. )
  62. })
  63. it('should use the from address from settings', async function () {
  64. await this.EmailSender.promises.sendEmail(this.opts)
  65. expect(this.sesClient.sendMail).to.have.been.calledWithMatch({
  66. from: this.Settings.email.fromAddress,
  67. })
  68. })
  69. it('should use the reply to address from settings', async function () {
  70. await this.EmailSender.promises.sendEmail(this.opts)
  71. expect(this.sesClient.sendMail).to.have.been.calledWithMatch({
  72. replyTo: this.Settings.email.replyToAddress,
  73. })
  74. })
  75. it('should use the reply to address in options as an override', async function () {
  76. this.opts.replyTo = 'someone@else.com'
  77. await this.EmailSender.promises.sendEmail(this.opts)
  78. expect(this.sesClient.sendMail).to.have.been.calledWithMatch({
  79. replyTo: this.opts.replyTo,
  80. })
  81. })
  82. it('should not send an email when the rate limiter says no', async function () {
  83. this.opts.sendingUser_id = '12321312321'
  84. this.rateLimiter.consume.rejects({ remainingPoints: 0 })
  85. await expect(this.EmailSender.promises.sendEmail(this.opts)).to.be
  86. .rejected
  87. expect(this.sesClient.sendMail).not.to.have.been.called
  88. })
  89. it('should send the email when the rate limtier says continue', async function () {
  90. this.opts.sendingUser_id = '12321312321'
  91. await this.EmailSender.promises.sendEmail(this.opts)
  92. expect(this.sesClient.sendMail).to.have.been.called
  93. })
  94. it('should not check the rate limiter when there is no sendingUser_id', async function () {
  95. this.EmailSender.sendEmail(this.opts, () => {
  96. expect(this.sesClient.sendMail).to.have.been.called
  97. expect(this.rateLimiter.consume).not.to.have.been.called
  98. })
  99. })
  100. describe('with plain-text email content', function () {
  101. beforeEach(function () {
  102. this.opts.text = 'hello there'
  103. })
  104. it('should set the text property on the email to send', async function () {
  105. await this.EmailSender.promises.sendEmail(this.opts)
  106. expect(this.sesClient.sendMail).to.have.been.calledWithMatch({
  107. html: this.opts.html,
  108. text: this.opts.text,
  109. to: this.opts.to,
  110. subject: this.opts.subject,
  111. })
  112. })
  113. })
  114. })
  115. })