RecurlyEventHandlerTests.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390
  1. const SandboxedModule = require('sandboxed-module')
  2. const { ObjectId } = require('mongodb-legacy')
  3. const sinon = require('sinon')
  4. const modulePath =
  5. '../../../../app/src/Features/Subscription/RecurlyEventHandler'
  6. describe('RecurlyEventHandler', function () {
  7. beforeEach(function () {
  8. this.userId = '123abc234bcd456cde567def'
  9. this.planCode = 'collaborator-annual'
  10. this.eventData = {
  11. account: {
  12. account_code: this.userId,
  13. },
  14. subscription: {
  15. uuid: '8435ad98c1ce45da99b07f6a6a2e780f',
  16. plan: {
  17. plan_code: 'collaborator-annual',
  18. },
  19. quantity: 1,
  20. state: 'active',
  21. trial_started_at: new Date('2021-01-01 12:34:56'),
  22. trial_ends_at: new Date('2021-01-08 12:34:56'),
  23. current_period_started_at: new Date('2021-01-01 12:34:56'),
  24. current_period_ends_at: new Date('2021-01-08 12:34:56'),
  25. },
  26. }
  27. this.RecurlyEventHandler = SandboxedModule.require(modulePath, {
  28. requires: {
  29. 'mongodb-legacy': { ObjectId },
  30. './SubscriptionEmailHandler': (this.SubscriptionEmailHandler = {
  31. sendTrialOnboardingEmail: sinon.stub(),
  32. }),
  33. '../Analytics/AnalyticsManager': (this.AnalyticsManager = {
  34. recordEventForUserInBackground: sinon.stub(),
  35. setUserPropertyForUserInBackground: sinon.stub(),
  36. }),
  37. '../SplitTests/SplitTestHandler': (this.SplitTestHandler = {
  38. promises: {
  39. getAssignmentForUser: sinon.stub().resolves({
  40. variant: 'default',
  41. }),
  42. },
  43. }),
  44. },
  45. })
  46. })
  47. it('with new_subscription_notification - free trial', function () {
  48. this.RecurlyEventHandler.sendRecurlyAnalyticsEvent(
  49. 'new_subscription_notification',
  50. this.eventData
  51. )
  52. sinon.assert.calledWith(
  53. this.AnalyticsManager.recordEventForUserInBackground,
  54. this.userId,
  55. 'subscription-started',
  56. {
  57. plan_code: this.planCode,
  58. quantity: 1,
  59. is_trial: true,
  60. has_ai_add_on: false,
  61. subscriptionId: this.eventData.subscription.uuid,
  62. }
  63. )
  64. sinon.assert.calledWith(
  65. this.AnalyticsManager.setUserPropertyForUserInBackground,
  66. this.userId,
  67. 'subscription-plan-code',
  68. this.planCode
  69. )
  70. sinon.assert.calledWith(
  71. this.AnalyticsManager.setUserPropertyForUserInBackground,
  72. this.userId,
  73. 'subscription-state',
  74. 'active'
  75. )
  76. sinon.assert.calledWith(
  77. this.AnalyticsManager.setUserPropertyForUserInBackground,
  78. this.userId,
  79. 'subscription-is-trial',
  80. true
  81. )
  82. })
  83. it('sends free trial onboarding email if user starting a trial', async function () {
  84. await this.RecurlyEventHandler.sendRecurlyAnalyticsEvent(
  85. 'new_subscription_notification',
  86. this.eventData
  87. )
  88. sinon.assert.called(this.SubscriptionEmailHandler.sendTrialOnboardingEmail)
  89. })
  90. it('with new_subscription_notification - no free trial', function () {
  91. this.eventData.subscription.current_period_started_at = new Date(
  92. '2021-02-10 12:34:56'
  93. )
  94. this.eventData.subscription.current_period_ends_at = new Date(
  95. '2021-02-17 12:34:56'
  96. )
  97. this.eventData.subscription.quantity = 3
  98. this.RecurlyEventHandler.sendRecurlyAnalyticsEvent(
  99. 'new_subscription_notification',
  100. this.eventData
  101. )
  102. sinon.assert.calledWith(
  103. this.AnalyticsManager.recordEventForUserInBackground,
  104. this.userId,
  105. 'subscription-started',
  106. {
  107. plan_code: this.planCode,
  108. quantity: 3,
  109. is_trial: false,
  110. has_ai_add_on: false,
  111. subscriptionId: this.eventData.subscription.uuid,
  112. }
  113. )
  114. sinon.assert.calledWith(
  115. this.AnalyticsManager.setUserPropertyForUserInBackground,
  116. this.userId,
  117. 'subscription-state',
  118. 'active'
  119. )
  120. sinon.assert.calledWith(
  121. this.AnalyticsManager.setUserPropertyForUserInBackground,
  122. this.userId,
  123. 'subscription-is-trial',
  124. false
  125. )
  126. })
  127. it('with updated_subscription_notification', function () {
  128. this.planCode = 'new-plan-code'
  129. this.eventData.subscription.plan.plan_code = this.planCode
  130. this.RecurlyEventHandler.sendRecurlyAnalyticsEvent(
  131. 'updated_subscription_notification',
  132. this.eventData
  133. )
  134. sinon.assert.calledWith(
  135. this.AnalyticsManager.recordEventForUserInBackground,
  136. this.userId,
  137. 'subscription-updated',
  138. {
  139. plan_code: this.planCode,
  140. quantity: 1,
  141. is_trial: true,
  142. has_ai_add_on: false,
  143. subscriptionId: this.eventData.subscription.uuid,
  144. }
  145. )
  146. sinon.assert.calledWith(
  147. this.AnalyticsManager.setUserPropertyForUserInBackground,
  148. this.userId,
  149. 'subscription-plan-code',
  150. this.planCode
  151. )
  152. sinon.assert.calledWith(
  153. this.AnalyticsManager.setUserPropertyForUserInBackground,
  154. this.userId,
  155. 'subscription-state',
  156. 'active'
  157. )
  158. sinon.assert.calledWith(
  159. this.AnalyticsManager.setUserPropertyForUserInBackground,
  160. this.userId,
  161. 'subscription-is-trial',
  162. true
  163. )
  164. })
  165. it('with canceled_subscription_notification', async function () {
  166. this.eventData.subscription.state = 'cancelled'
  167. await this.RecurlyEventHandler.sendRecurlyAnalyticsEvent(
  168. 'canceled_subscription_notification',
  169. this.eventData
  170. )
  171. sinon.assert.calledWith(
  172. this.AnalyticsManager.recordEventForUserInBackground,
  173. this.userId,
  174. 'subscription-cancelled',
  175. {
  176. plan_code: this.planCode,
  177. quantity: 1,
  178. is_trial: true,
  179. has_ai_add_on: false,
  180. subscriptionId: this.eventData.subscription.uuid,
  181. }
  182. )
  183. sinon.assert.calledWith(
  184. this.AnalyticsManager.setUserPropertyForUserInBackground,
  185. this.userId,
  186. 'subscription-state',
  187. 'cancelled'
  188. )
  189. sinon.assert.calledWith(
  190. this.AnalyticsManager.setUserPropertyForUserInBackground,
  191. this.userId,
  192. 'subscription-is-trial',
  193. true
  194. )
  195. })
  196. it('with expired_subscription_notification', function () {
  197. this.eventData.subscription.state = 'expired'
  198. this.RecurlyEventHandler.sendRecurlyAnalyticsEvent(
  199. 'expired_subscription_notification',
  200. this.eventData
  201. )
  202. sinon.assert.calledWith(
  203. this.AnalyticsManager.recordEventForUserInBackground,
  204. this.userId,
  205. 'subscription-expired',
  206. {
  207. plan_code: this.planCode,
  208. quantity: 1,
  209. is_trial: true,
  210. has_ai_add_on: false,
  211. subscriptionId: this.eventData.subscription.uuid,
  212. }
  213. )
  214. sinon.assert.calledWith(
  215. this.AnalyticsManager.setUserPropertyForUserInBackground,
  216. this.userId,
  217. 'subscription-plan-code',
  218. this.planCode
  219. )
  220. sinon.assert.calledWith(
  221. this.AnalyticsManager.setUserPropertyForUserInBackground,
  222. this.userId,
  223. 'subscription-state',
  224. 'expired'
  225. )
  226. sinon.assert.calledWith(
  227. this.AnalyticsManager.setUserPropertyForUserInBackground,
  228. this.userId,
  229. 'subscription-is-trial',
  230. true
  231. )
  232. })
  233. it('with renewed_subscription_notification', function () {
  234. this.RecurlyEventHandler.sendRecurlyAnalyticsEvent(
  235. 'renewed_subscription_notification',
  236. this.eventData
  237. )
  238. sinon.assert.calledWith(
  239. this.AnalyticsManager.recordEventForUserInBackground,
  240. this.userId,
  241. 'subscription-renewed',
  242. {
  243. plan_code: this.planCode,
  244. quantity: 1,
  245. is_trial: true,
  246. has_ai_add_on: false,
  247. subscriptionId: this.eventData.subscription.uuid,
  248. }
  249. )
  250. })
  251. it('with reactivated_account_notification', function () {
  252. this.RecurlyEventHandler.sendRecurlyAnalyticsEvent(
  253. 'reactivated_account_notification',
  254. this.eventData
  255. )
  256. sinon.assert.calledWith(
  257. this.AnalyticsManager.recordEventForUserInBackground,
  258. this.userId,
  259. 'subscription-reactivated',
  260. {
  261. plan_code: this.planCode,
  262. quantity: 1,
  263. has_ai_add_on: false,
  264. subscriptionId: this.eventData.subscription.uuid,
  265. }
  266. )
  267. })
  268. it('with paid_charge_invoice_notification', function () {
  269. const invoice = {
  270. invoice_number: 1234,
  271. currency: 'USD',
  272. state: 'paid',
  273. total_in_cents: 720,
  274. tax_in_cents: 12,
  275. address: {
  276. country: 'Liurnia',
  277. },
  278. collection_method: 'automatic',
  279. subscription_ids: ['abcd1234', 'defa3214'],
  280. }
  281. this.RecurlyEventHandler.sendRecurlyAnalyticsEvent(
  282. 'paid_charge_invoice_notification',
  283. {
  284. account: {
  285. account_code: this.userId,
  286. },
  287. invoice,
  288. }
  289. )
  290. sinon.assert.calledWith(
  291. this.AnalyticsManager.recordEventForUserInBackground,
  292. this.userId,
  293. 'subscription-invoice-collected',
  294. {
  295. invoiceNumber: invoice.invoice_number,
  296. currency: invoice.currency,
  297. totalInCents: invoice.total_in_cents,
  298. taxInCents: invoice.tax_in_cents,
  299. country: invoice.address.country,
  300. collectionMethod: invoice.collection_method,
  301. subscriptionId1: invoice.subscription_ids[0],
  302. subscriptionId2: invoice.subscription_ids[1],
  303. }
  304. )
  305. })
  306. it('with paid_charge_invoice_notification and total_in_cents 0', function () {
  307. this.RecurlyEventHandler.sendRecurlyAnalyticsEvent(
  308. 'paid_charge_invoice_notification',
  309. {
  310. account: {
  311. account_code: this.userId,
  312. },
  313. invoice: {
  314. state: 'paid',
  315. total_in_cents: 0,
  316. },
  317. }
  318. )
  319. sinon.assert.notCalled(this.AnalyticsManager.recordEventForUserInBackground)
  320. })
  321. it('with closed_invoice_notification', function () {
  322. this.RecurlyEventHandler.sendRecurlyAnalyticsEvent(
  323. 'closed_invoice_notification',
  324. {
  325. account: {
  326. account_code: this.userId,
  327. },
  328. invoice: {
  329. state: 'collected',
  330. total_in_cents: 720,
  331. },
  332. }
  333. )
  334. sinon.assert.calledWith(
  335. this.AnalyticsManager.recordEventForUserInBackground,
  336. this.userId,
  337. 'subscription-invoice-collected'
  338. )
  339. })
  340. it('with closed_invoice_notification and total_in_cents 0', function () {
  341. this.RecurlyEventHandler.sendRecurlyAnalyticsEvent(
  342. 'closed_invoice_notification',
  343. {
  344. account: {
  345. account_code: this.userId,
  346. },
  347. invoice: {
  348. state: 'collected',
  349. total_in_cents: 0,
  350. },
  351. }
  352. )
  353. sinon.assert.notCalled(this.AnalyticsManager.recordEventForUserInBackground)
  354. })
  355. it('nothing is called with invalid account code', function () {
  356. this.eventData.account.account_code = 'foo_bar'
  357. this.RecurlyEventHandler.sendRecurlyAnalyticsEvent(
  358. 'new_subscription_notification',
  359. this.eventData
  360. )
  361. sinon.assert.notCalled(this.AnalyticsManager.recordEventForUserInBackground)
  362. sinon.assert.notCalled(
  363. this.AnalyticsManager.setUserPropertyForUserInBackground
  364. )
  365. sinon.assert.notCalled(
  366. this.AnalyticsManager.setUserPropertyForUserInBackground
  367. )
  368. sinon.assert.notCalled(
  369. this.AnalyticsManager.setUserPropertyForUserInBackground
  370. )
  371. })
  372. })