NewsletterManager.test.mjs 6.4 KB

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