NewsletterManagerTests.js 6.2 KB

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