NewsletterManagerTests.js 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. const { expect } = require('chai')
  2. const sinon = require('sinon')
  3. const { RequestFailedError } = require('@overleaf/fetch-utils')
  4. const SandboxedModule = require('sandboxed-module')
  5. const MODULE_PATH = '../../../../app/src/Features/Newsletter/NewsletterManager'
  6. describe('NewsletterManager', function () {
  7. beforeEach('setup mocks', function () {
  8. this.Settings = {
  9. mailchimp: {
  10. api_key: 'api_key',
  11. list_id: 'list_id',
  12. },
  13. }
  14. this.mailchimp = {
  15. get: sinon.stub(),
  16. put: sinon.stub(),
  17. patch: sinon.stub(),
  18. delete: sinon.stub(),
  19. }
  20. this.Mailchimp = sinon.stub().returns(this.mailchimp)
  21. this.mergeFields = {
  22. FNAME: 'Overleaf',
  23. LNAME: 'Duck',
  24. MONGO_ID: 'user_id',
  25. }
  26. this.NewsletterManager = SandboxedModule.require(MODULE_PATH, {
  27. requires: {
  28. './MailChimpClient': this.Mailchimp,
  29. '@overleaf/settings': this.Settings,
  30. },
  31. globals: { AbortController },
  32. }).promises
  33. this.NewsletterManager.get = sinon.stub()
  34. this.NewsletterManager.delete = sinon.stub()
  35. this.user = {
  36. _id: 'user_id',
  37. email: 'overleaf.duck@example.com',
  38. first_name: 'Overleaf',
  39. last_name: 'Duck',
  40. }
  41. // MD5 sum of the user email
  42. this.emailHash = 'c02f60ed0ef51818186274e406c9a48f'
  43. })
  44. describe('subscribed', function () {
  45. it('calls Mailchimp to get the user status', async function () {
  46. await this.NewsletterManager.subscribed(this.user)
  47. expect(this.mailchimp.get).to.have.been.calledWith(
  48. `/lists/list_id/members/${this.emailHash}`
  49. )
  50. })
  51. it('returns true when subscribed', async function () {
  52. this.mailchimp.get.resolves({ status: 'subscribed' })
  53. const subscribed = await this.NewsletterManager.subscribed(this.user)
  54. expect(subscribed).to.be.true
  55. })
  56. it('returns false on 404', async function () {
  57. this.mailchimp.get.rejects(
  58. new RequestFailedError(
  59. 'http://some-url',
  60. {},
  61. { status: 404 },
  62. 'Not found'
  63. )
  64. )
  65. const subscribed = await this.NewsletterManager.subscribed(this.user)
  66. expect(subscribed).to.be.false
  67. })
  68. })
  69. describe('subscribe', function () {
  70. it('calls Mailchimp to subscribe the user', async function () {
  71. await this.NewsletterManager.subscribe(this.user)
  72. expect(this.mailchimp.put).to.have.been.calledWith(
  73. `/lists/list_id/members/${this.emailHash}`,
  74. {
  75. email_address: this.user.email,
  76. status: 'subscribed',
  77. status_if_new: 'subscribed',
  78. merge_fields: this.mergeFields,
  79. }
  80. )
  81. })
  82. })
  83. describe('unsubscribe', function () {
  84. describe('when unsubscribing normally', function () {
  85. it('calls Mailchimp to unsubscribe the user', async function () {
  86. await this.NewsletterManager.unsubscribe(this.user)
  87. expect(this.mailchimp.patch).to.have.been.calledWith(
  88. `/lists/list_id/members/${this.emailHash}`,
  89. {
  90. status: 'unsubscribed',
  91. merge_fields: this.mergeFields,
  92. }
  93. )
  94. })
  95. it('ignores a Mailchimp error about fake emails', async function () {
  96. this.mailchimp.patch.rejects(
  97. new Error(
  98. 'overleaf.duck@example.com looks fake or invalid, please enter a real email address'
  99. )
  100. )
  101. await expect(this.NewsletterManager.unsubscribe(this.user)).to.be
  102. .fulfilled
  103. })
  104. it('rejects on other errors', async function () {
  105. this.mailchimp.patch.rejects(
  106. new Error('something really wrong is happening')
  107. )
  108. await expect(this.NewsletterManager.unsubscribe(this.user)).to.be
  109. .rejected
  110. })
  111. })
  112. describe('when deleting', function () {
  113. it('calls Mailchimp to delete the user', async function () {
  114. await this.NewsletterManager.unsubscribe(this.user, { delete: true })
  115. expect(this.mailchimp.delete).to.have.been.calledWith(
  116. `/lists/list_id/members/${this.emailHash}`
  117. )
  118. })
  119. it('ignores a Mailchimp error about fake emails', async function () {
  120. this.mailchimp.delete.rejects(
  121. new Error(
  122. 'overleaf.duck@example.com looks fake or invalid, please enter a real email address'
  123. )
  124. )
  125. await expect(
  126. this.NewsletterManager.unsubscribe(this.user, { delete: true })
  127. ).to.be.fulfilled
  128. })
  129. it('rejects on other errors', async function () {
  130. this.mailchimp.delete.rejects(
  131. new Error('something really wrong is happening')
  132. )
  133. await expect(
  134. this.NewsletterManager.unsubscribe(this.user, { delete: true })
  135. ).to.be.rejected
  136. })
  137. })
  138. })
  139. describe('changeEmail', function () {
  140. it('calls Mailchimp to change the subscriber email', async function () {
  141. await this.NewsletterManager.changeEmail(
  142. this.user,
  143. 'overleaf.squirrel@example.com'
  144. )
  145. expect(this.mailchimp.patch).to.have.been.calledWith(
  146. `/lists/list_id/members/${this.emailHash}`,
  147. {
  148. email_address: 'overleaf.squirrel@example.com',
  149. merge_fields: this.mergeFields,
  150. }
  151. )
  152. })
  153. it('deletes the old email if changing the address fails', async function () {
  154. this.mailchimp.patch
  155. .withArgs(`/lists/list_id/members/${this.emailHash}`, {
  156. email_address: 'overleaf.squirrel@example.com',
  157. merge_fields: this.mergeFields,
  158. })
  159. .rejects(new Error('that did not work'))
  160. await expect(
  161. this.NewsletterManager.changeEmail(
  162. this.user,
  163. 'overleaf.squirrel@example.com'
  164. )
  165. ).to.be.rejected
  166. expect(this.mailchimp.delete).to.have.been.calledWith(
  167. `/lists/list_id/members/${this.emailHash}`
  168. )
  169. })
  170. it('does not reject on non-fatal error ', async function () {
  171. const nonFatalError = new Error('merge fields were invalid')
  172. this.mailchimp.patch.rejects(nonFatalError)
  173. await expect(
  174. this.NewsletterManager.changeEmail(
  175. this.user,
  176. 'overleaf.squirrel@example.com'
  177. )
  178. ).to.be.fulfilled
  179. })
  180. it('rejects on any other error', async function () {
  181. const fatalError = new Error('fatal error')
  182. this.mailchimp.patch.rejects(fatalError)
  183. await expect(
  184. this.NewsletterManager.changeEmail(
  185. this.user,
  186. 'overleaf.squirrel@example.com'
  187. )
  188. ).to.be.rejected
  189. })
  190. })
  191. })