SubscriptionHandlerTests.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758
  1. const SandboxedModule = require('sandboxed-module')
  2. const sinon = require('sinon')
  3. const chai = require('chai')
  4. const { expect } = chai
  5. const {
  6. PaymentProviderSubscription,
  7. } = require('../../../../app/src/Features/Subscription/PaymentProviderEntities')
  8. const SubscriptionHelper = require('../../../../app/src/Features/Subscription/SubscriptionHelper')
  9. const MODULE_PATH =
  10. '../../../../app/src/Features/Subscription/SubscriptionHandler'
  11. const mockRecurlySubscriptions = {
  12. 'subscription-123-active': {
  13. uuid: 'subscription-123-active',
  14. plan: {
  15. name: 'Gold',
  16. plan_code: 'gold',
  17. },
  18. current_period_ends_at: new Date(),
  19. state: 'active',
  20. unit_amount_in_cents: 999,
  21. account: {
  22. account_code: 'user-123',
  23. },
  24. },
  25. }
  26. const mockRecurlyClientSubscriptions = {
  27. 'subscription-123-active': new PaymentProviderSubscription({
  28. id: 'subscription-123-active',
  29. userId: 'user-id',
  30. planCode: 'collaborator',
  31. planName: 'Collaborator',
  32. planPrice: 10,
  33. subtotal: 10,
  34. currency: 'USD',
  35. total: 10,
  36. }),
  37. }
  38. const mockSubscriptionChanges = {
  39. 'subscription-123-active': {
  40. id: 'subscription-change-id',
  41. subscriptionId: 'subscription-123-recurly-id', // not the UUID
  42. },
  43. }
  44. describe('SubscriptionHandler', function () {
  45. beforeEach(function () {
  46. this.Settings = {
  47. plans: [
  48. {
  49. planCode: 'collaborator',
  50. name: 'Collaborator',
  51. price_in_cents: 1000,
  52. features: {
  53. collaborators: -1,
  54. versioning: true,
  55. },
  56. },
  57. {
  58. planCode: 'professional',
  59. price_in_cents: 1500,
  60. },
  61. ],
  62. defaultPlanCode: {
  63. collaborators: 0,
  64. versioning: false,
  65. },
  66. }
  67. this.activeRecurlySubscription =
  68. mockRecurlySubscriptions['subscription-123-active']
  69. this.activeRecurlyClientSubscription =
  70. mockRecurlyClientSubscriptions['subscription-123-active']
  71. this.activeRecurlySubscriptionChange =
  72. mockSubscriptionChanges['subscription-123-active']
  73. this.User = {}
  74. this.user = { _id: (this.user_id = 'user_id_here_') }
  75. this.subscription = {
  76. recurlySubscription_id: this.activeRecurlySubscription.uuid,
  77. }
  78. this.RecurlyWrapper = {
  79. promises: {
  80. getSubscription: sinon.stub().resolves(this.activeRecurlySubscription),
  81. redeemCoupon: sinon.stub().resolves(),
  82. createSubscription: sinon
  83. .stub()
  84. .resolves(this.activeRecurlySubscription),
  85. getBillingInfo: sinon.stub().resolves(),
  86. getAccountPastDueInvoices: sinon.stub().resolves(),
  87. attemptInvoiceCollection: sinon.stub().resolves(),
  88. listAccountActiveSubscriptions: sinon.stub().resolves([]),
  89. },
  90. }
  91. this.RecurlyClient = {
  92. promises: {
  93. reactivateSubscriptionByUuid: sinon
  94. .stub()
  95. .resolves(this.activeRecurlyClientSubscription),
  96. cancelSubscriptionByUuid: sinon.stub().resolves(),
  97. applySubscriptionChangeRequest: sinon
  98. .stub()
  99. .resolves(this.activeRecurlySubscriptionChange),
  100. getSubscription: sinon
  101. .stub()
  102. .resolves(this.activeRecurlyClientSubscription),
  103. pauseSubscriptionByUuid: sinon.stub().resolves(),
  104. resumeSubscriptionByUuid: sinon.stub().resolves(),
  105. },
  106. }
  107. this.SubscriptionUpdater = {
  108. promises: {
  109. updateSubscriptionFromRecurly: sinon.stub().resolves(),
  110. syncSubscription: sinon.stub().resolves(),
  111. syncStripeSubscription: sinon.stub().resolves(),
  112. startFreeTrial: sinon.stub().resolves(),
  113. },
  114. }
  115. this.LimitationsManager = {
  116. promises: {
  117. userHasSubscription: sinon.stub().resolves(),
  118. },
  119. }
  120. this.SubscriptionLocator = {
  121. promises: {
  122. getUsersSubscription: sinon.stub().resolves(this.subscription),
  123. },
  124. }
  125. this.EmailHandler = {
  126. sendEmail: sinon.stub(),
  127. sendDeferredEmail: sinon.stub(),
  128. }
  129. this.UserUpdater = {
  130. promises: {
  131. updateUser: sinon.stub().resolves(),
  132. },
  133. }
  134. this.SubscriptionHandler = SandboxedModule.require(MODULE_PATH, {
  135. requires: {
  136. './RecurlyWrapper': this.RecurlyWrapper,
  137. './RecurlyClient': this.RecurlyClient,
  138. '@overleaf/settings': this.Settings,
  139. '../../models/User': {
  140. User: this.User,
  141. },
  142. './SubscriptionHelper': SubscriptionHelper,
  143. './SubscriptionUpdater': this.SubscriptionUpdater,
  144. './SubscriptionLocator': this.SubscriptionLocator,
  145. './LimitationsManager': this.LimitationsManager,
  146. '../Email/EmailHandler': this.EmailHandler,
  147. '../Analytics/AnalyticsManager': this.AnalyticsManager,
  148. '../User/UserUpdater': this.UserUpdater,
  149. '../../infrastructure/Modules': (this.Modules = {
  150. promises: {
  151. hooks: {
  152. fire: sinon.stub(),
  153. },
  154. },
  155. }),
  156. },
  157. })
  158. })
  159. describe('createSubscription', function () {
  160. beforeEach(function () {
  161. this.subscriptionDetails = {
  162. cvv: '123',
  163. number: '12345',
  164. }
  165. this.recurlyTokenIds = { billing: '45555666' }
  166. })
  167. describe('successfully', function () {
  168. beforeEach(async function () {
  169. await this.SubscriptionHandler.promises.createSubscription(
  170. this.user,
  171. this.subscriptionDetails,
  172. this.recurlyTokenIds
  173. )
  174. })
  175. it('should create the subscription with the wrapper', function () {
  176. this.RecurlyWrapper.promises.createSubscription
  177. .calledWith(this.user, this.subscriptionDetails, this.recurlyTokenIds)
  178. .should.equal(true)
  179. })
  180. it('should sync the subscription to the user', function () {
  181. this.SubscriptionUpdater.promises.syncSubscription.calledOnce.should.equal(
  182. true
  183. )
  184. this.SubscriptionUpdater.promises.syncSubscription.args[0][0].should.deep.equal(
  185. this.activeRecurlySubscription
  186. )
  187. this.SubscriptionUpdater.promises.syncSubscription.args[0][1].should.deep.equal(
  188. this.user._id
  189. )
  190. })
  191. it('should not set last trial date if not a trial/the trial_started_at is not set', function () {
  192. this.UserUpdater.promises.updateUser.should.not.have.been.called
  193. })
  194. })
  195. describe('when the subscription is a trial and has a trial_started_at date', function () {
  196. beforeEach(async function () {
  197. this.activeRecurlySubscription.trial_started_at =
  198. '2024-01-01T09:58:35.531+00:00'
  199. await this.SubscriptionHandler.promises.createSubscription(
  200. this.user,
  201. this.subscriptionDetails,
  202. this.recurlyTokenIds
  203. )
  204. })
  205. it('should set the users lastTrial date', function () {
  206. this.UserUpdater.promises.updateUser.should.have.been.calledOnce
  207. expect(this.UserUpdater.promises.updateUser.args[0][0]).to.deep.equal({
  208. _id: this.user_id,
  209. lastTrial: {
  210. $not: {
  211. $gt: new Date(this.activeRecurlySubscription.trial_started_at),
  212. },
  213. },
  214. })
  215. expect(this.UserUpdater.promises.updateUser.args[0][1]).to.deep.equal({
  216. $set: {
  217. lastTrial: new Date(
  218. this.activeRecurlySubscription.trial_started_at
  219. ),
  220. },
  221. })
  222. })
  223. })
  224. describe('when there is already a subscription in Recurly', function () {
  225. beforeEach(function () {
  226. this.RecurlyWrapper.promises.listAccountActiveSubscriptions.resolves([
  227. this.subscription,
  228. ])
  229. })
  230. it('should an error', function () {
  231. expect(
  232. this.SubscriptionHandler.promises.createSubscription(
  233. this.user,
  234. this.subscriptionDetails,
  235. this.recurlyTokenIds
  236. )
  237. ).to.be.rejectedWith('user already has subscription in recurly')
  238. })
  239. })
  240. })
  241. describe('updateSubscription', function () {
  242. beforeEach(function () {
  243. this.user.id = this.activeRecurlySubscription.account.account_code
  244. this.User.findById = (userId, projection) => ({
  245. exec: () => {
  246. userId.should.equal(this.user.id)
  247. return Promise.resolve(this.user)
  248. },
  249. })
  250. })
  251. it('should not fire updatePaidSubscription hook if user has no subscription', async function () {
  252. this.LimitationsManager.promises.userHasSubscription.resolves({
  253. hasSubscription: false,
  254. subscription: null,
  255. })
  256. await this.SubscriptionHandler.promises.updateSubscription(
  257. this.user,
  258. this.plan_code
  259. )
  260. expect(this.Modules.promises.hooks.fire).to.not.have.been.calledWith(
  261. 'updatePaidSubscription',
  262. sinon.match.any,
  263. sinon.match.any,
  264. sinon.match.any
  265. )
  266. })
  267. it('should not fire updatePaidSubscription hook if user has custom subscription', async function () {
  268. this.LimitationsManager.promises.userHasSubscription.resolves({
  269. hasSubscription: true,
  270. subscription: { customAccount: true },
  271. })
  272. await this.SubscriptionHandler.promises.updateSubscription(
  273. this.user,
  274. this.plan_code
  275. )
  276. expect(this.Modules.promises.hooks.fire).to.not.have.been.calledWith(
  277. 'updatePaidSubscription',
  278. sinon.match.any,
  279. sinon.match.any,
  280. sinon.match.any
  281. )
  282. })
  283. it('should fire updatePaidSubscription to update a valid subscription', async function () {
  284. this.LimitationsManager.promises.userHasSubscription.resolves({
  285. hasSubscription: true,
  286. subscription: this.subscription,
  287. })
  288. await this.SubscriptionHandler.promises.updateSubscription(
  289. this.user,
  290. this.plan_code
  291. )
  292. expect(this.Modules.promises.hooks.fire).to.have.been.calledWith(
  293. 'updatePaidSubscription',
  294. this.subscription,
  295. this.plan_code,
  296. this.user._id
  297. )
  298. })
  299. })
  300. describe('cancelPendingSubscriptionChange', function () {
  301. beforeEach(function () {
  302. this.user.id = this.activeRecurlySubscription.account.account_code
  303. this.User.findById = (userId, projection) => ({
  304. exec: () => {
  305. userId.should.equal(this.user.id)
  306. return Promise.resolve(this.user)
  307. },
  308. })
  309. })
  310. it('should not fire cancelPendingPaidSubscriptionChange hook if user has no subscription', async function () {
  311. this.LimitationsManager.promises.userHasSubscription.resolves({
  312. hasSubscription: false,
  313. subscription: null,
  314. })
  315. await this.SubscriptionHandler.promises.cancelPendingSubscriptionChange(
  316. this.user,
  317. this.plan_code
  318. )
  319. expect(this.Modules.promises.hooks.fire).to.not.have.been.calledWith(
  320. 'cancelPendingPaidSubscriptionChange',
  321. sinon.match.any
  322. )
  323. })
  324. it('should fire cancelPendingPaidSubscriptionChange to update a valid subscription', async function () {
  325. this.LimitationsManager.promises.userHasSubscription.resolves({
  326. hasSubscription: true,
  327. subscription: this.subscription,
  328. })
  329. await this.SubscriptionHandler.promises.cancelPendingSubscriptionChange(
  330. this.user,
  331. this.plan_code
  332. )
  333. expect(this.Modules.promises.hooks.fire).to.have.been.calledWith(
  334. 'cancelPendingPaidSubscriptionChange',
  335. this.subscription
  336. )
  337. })
  338. })
  339. describe('cancelSubscription', function () {
  340. describe('with a user without a subscription', function () {
  341. beforeEach(async function () {
  342. this.LimitationsManager.promises.userHasSubscription.resolves({
  343. hasSubscription: false,
  344. subscription: this.subscription,
  345. })
  346. await this.SubscriptionHandler.promises.cancelSubscription(this.user)
  347. })
  348. it('should redirect to the subscription dashboard', function () {
  349. this.RecurlyClient.promises.cancelSubscriptionByUuid.called.should.equal(
  350. false
  351. )
  352. })
  353. })
  354. describe('with a user with a subscription', function () {
  355. beforeEach(async function () {
  356. this.LimitationsManager.promises.userHasSubscription.resolves({
  357. hasSubscription: true,
  358. subscription: this.subscription,
  359. })
  360. await this.SubscriptionHandler.promises.cancelSubscription(this.user)
  361. })
  362. it('should cancel the subscription', function () {
  363. expect(this.Modules.promises.hooks.fire).to.have.been.calledWith(
  364. 'cancelPaidSubscription',
  365. this.subscription
  366. )
  367. })
  368. it('should send the email after 1 hour', function () {
  369. const ONE_HOUR_IN_MS = 1000 * 60 * 60
  370. expect(this.EmailHandler.sendDeferredEmail).to.have.been.calledWith(
  371. 'canceledSubscription',
  372. { to: this.user.email, first_name: this.user.first_name },
  373. ONE_HOUR_IN_MS
  374. )
  375. })
  376. })
  377. })
  378. describe('resumeSubscription', function () {
  379. describe('for a user without a subscription', function () {
  380. beforeEach(async function () {
  381. this.LimitationsManager.promises.userHasSubscription.resolves({
  382. hasSubscription: false,
  383. subscription: this.subscription,
  384. })
  385. })
  386. it('should not make a resume call to recurly', async function () {
  387. expect(
  388. this.SubscriptionHandler.promises.resumeSubscription(this.user)
  389. ).to.be.rejectedWith('No active subscription to resume')
  390. this.RecurlyClient.promises.resumeSubscriptionByUuid.called.should.equal(
  391. false
  392. )
  393. })
  394. })
  395. describe('for a user with a subscription', function () {
  396. beforeEach(async function () {
  397. this.LimitationsManager.promises.userHasSubscription.resolves({
  398. hasSubscription: true,
  399. subscription: {
  400. recurlySubscription_id: this.activeRecurlySubscription.uuid,
  401. recurlyStatus: { state: 'non-trial' },
  402. planCode: 'collaborator',
  403. },
  404. })
  405. })
  406. it('should make a resume call to recurly', async function () {
  407. await this.SubscriptionHandler.promises.resumeSubscription(this.user)
  408. this.RecurlyClient.promises.resumeSubscriptionByUuid.called.should.equal(
  409. true
  410. )
  411. })
  412. })
  413. })
  414. describe('pauseSubscription', function () {
  415. describe('for a user without a subscription', function () {
  416. beforeEach(async function () {
  417. this.LimitationsManager.promises.userHasSubscription.resolves({
  418. hasSubscription: false,
  419. subscription: this.subscription,
  420. })
  421. })
  422. it('should not make a pause call to recurly', async function () {
  423. expect(
  424. this.SubscriptionHandler.promises.pauseSubscription(this.user, 3)
  425. ).to.be.rejectedWith('No active subscription to pause')
  426. this.RecurlyClient.promises.pauseSubscriptionByUuid.called.should.equal(
  427. false
  428. )
  429. })
  430. })
  431. describe('for a user with an annual subscription', function () {
  432. beforeEach(async function () {
  433. this.LimitationsManager.promises.userHasSubscription.resolves({
  434. hasSubscription: false,
  435. subscription: {
  436. recurlySubscription_id: this.activeRecurlySubscription.uuid,
  437. recurlyStatus: { state: 'non-trial' },
  438. planCode: 'collaborator-annual',
  439. },
  440. })
  441. })
  442. it('should not make a pause call to recurly', async function () {
  443. expect(
  444. this.SubscriptionHandler.promises.pauseSubscription(this.user, 3)
  445. ).to.be.rejectedWith('Can only pause monthly individual plans')
  446. this.RecurlyClient.promises.pauseSubscriptionByUuid.called.should.equal(
  447. false
  448. )
  449. })
  450. })
  451. describe('for a user with a subscription', function () {
  452. beforeEach(async function () {
  453. this.LimitationsManager.promises.userHasSubscription.resolves({
  454. hasSubscription: true,
  455. subscription: {
  456. recurlySubscription_id: this.activeRecurlySubscription.uuid,
  457. recurlyStatus: { state: 'non-trial' },
  458. planCode: 'collaborator',
  459. addOns: [],
  460. },
  461. })
  462. })
  463. it('should call pause hook', async function () {
  464. await this.SubscriptionHandler.promises.pauseSubscription(this.user, 3)
  465. expect(this.Modules.promises.hooks.fire).to.have.been.calledWith(
  466. 'pausePaidSubscription',
  467. {
  468. recurlySubscription_id: this.activeRecurlySubscription.uuid,
  469. recurlyStatus: { state: 'non-trial' },
  470. planCode: 'collaborator',
  471. addOns: [],
  472. },
  473. 3
  474. )
  475. })
  476. })
  477. describe('for a user in a trial', function () {
  478. beforeEach(async function () {
  479. this.LimitationsManager.promises.userHasSubscription.resolves({
  480. hasSubscription: true,
  481. subscription: {
  482. recurlySubscription_id: this.activeRecurlySubscription.uuid,
  483. recurlyStatus: {
  484. state: 'trial',
  485. trialEndsAt: Date.now() + 1000000,
  486. },
  487. planCode: 'collaborator',
  488. },
  489. })
  490. })
  491. it('should not make a pause call to recurly', async function () {
  492. expect(
  493. this.SubscriptionHandler.promises.pauseSubscription(this.user, 3)
  494. ).to.be.rejectedWith('Cannot pause a subscription in a trial')
  495. this.RecurlyClient.promises.pauseSubscriptionByUuid.called.should.equal(
  496. false
  497. )
  498. })
  499. })
  500. describe('for a user with addons', function () {
  501. beforeEach(async function () {
  502. this.LimitationsManager.promises.userHasSubscription.resolves({
  503. hasSubscription: true,
  504. subscription: {
  505. recurlySubscription_id: this.activeRecurlySubscription.uuid,
  506. recurlyStatus: { state: 'non-trial' },
  507. planCode: 'collaborator',
  508. addOns: ['mock-addon'],
  509. },
  510. })
  511. })
  512. it('should not make a pause call to recurly', async function () {
  513. expect(
  514. this.SubscriptionHandler.promises.pauseSubscription(this.user, 3)
  515. ).to.be.rejectedWith('Cannot pause a subscription with addons')
  516. this.RecurlyClient.promises.pauseSubscriptionByUuid.called.should.equal(
  517. false
  518. )
  519. })
  520. })
  521. })
  522. describe('reactivateSubscription', function () {
  523. describe('with a user without a subscription', function () {
  524. beforeEach(async function () {
  525. this.LimitationsManager.promises.userHasSubscription.resolves({
  526. hasSubscription: false,
  527. subscription: this.subscription,
  528. })
  529. await this.SubscriptionHandler.promises.reactivateSubscription(
  530. this.user
  531. )
  532. })
  533. it('should redirect to the subscription dashboard', function () {
  534. this.RecurlyClient.promises.reactivateSubscriptionByUuid.called.should.equal(
  535. false
  536. )
  537. })
  538. it('should not send a notification email', function () {
  539. sinon.assert.notCalled(this.EmailHandler.sendEmail)
  540. })
  541. })
  542. describe('with a user with a subscription', function () {
  543. beforeEach(async function () {
  544. this.LimitationsManager.promises.userHasSubscription.resolves({
  545. hasSubscription: true,
  546. subscription: this.subscription,
  547. })
  548. await this.SubscriptionHandler.promises.reactivateSubscription(
  549. this.user
  550. )
  551. })
  552. it('should reactivate the subscription', function () {
  553. expect(this.Modules.promises.hooks.fire).to.have.been.calledWith(
  554. 'reactivatePaidSubscription',
  555. this.subscription
  556. )
  557. })
  558. it('should send a notification email', function () {
  559. sinon.assert.calledWith(
  560. this.EmailHandler.sendEmail,
  561. 'reactivatedSubscription'
  562. )
  563. })
  564. })
  565. })
  566. describe('syncSubscription', function () {
  567. describe('with an actionable request', function () {
  568. beforeEach(async function () {
  569. this.user.id = this.activeRecurlySubscription.account.account_code
  570. this.User.findById = (userId, projection) => ({
  571. exec: () => {
  572. userId.should.equal(this.user.id)
  573. return Promise.resolve(this.user)
  574. },
  575. })
  576. await this.SubscriptionHandler.promises.syncSubscription(
  577. this.activeRecurlySubscription,
  578. {}
  579. )
  580. })
  581. it('should request the affected subscription from the API', function () {
  582. this.RecurlyWrapper.promises.getSubscription
  583. .calledWith(this.activeRecurlySubscription.uuid)
  584. .should.equal(true)
  585. })
  586. it('should request the account details of the subscription', function () {
  587. const options = this.RecurlyWrapper.promises.getSubscription.args[0][1]
  588. options.includeAccount.should.equal(true)
  589. })
  590. it('should sync the subscription to the user', function () {
  591. this.SubscriptionUpdater.promises.syncSubscription.calledOnce.should.equal(
  592. true
  593. )
  594. this.SubscriptionUpdater.promises.syncSubscription.args[0][0].should.deep.equal(
  595. this.activeRecurlySubscription
  596. )
  597. this.SubscriptionUpdater.promises.syncSubscription.args[0][1].should.deep.equal(
  598. this.user._id
  599. )
  600. })
  601. })
  602. })
  603. describe('attemptPaypalInvoiceCollection', function () {
  604. describe('for credit card users', function () {
  605. beforeEach(async function () {
  606. this.RecurlyWrapper.promises.getBillingInfo.resolves({
  607. paypal_billing_agreement_id: null,
  608. })
  609. await this.SubscriptionHandler.promises.attemptPaypalInvoiceCollection(
  610. this.activeRecurlySubscription.account.account_code
  611. )
  612. })
  613. it('gets billing infos', function () {
  614. sinon.assert.calledWith(
  615. this.RecurlyWrapper.promises.getBillingInfo,
  616. this.activeRecurlySubscription.account.account_code
  617. )
  618. })
  619. it('skips user', function () {
  620. sinon.assert.notCalled(
  621. this.RecurlyWrapper.promises.getAccountPastDueInvoices
  622. )
  623. })
  624. })
  625. describe('for paypal users', function () {
  626. beforeEach(async function () {
  627. this.RecurlyWrapper.promises.getBillingInfo.resolves({
  628. paypal_billing_agreement_id: 'mock-billing-agreement',
  629. })
  630. this.RecurlyWrapper.promises.getAccountPastDueInvoices.resolves([
  631. { invoice_number: 'mock-invoice-number' },
  632. ])
  633. await this.SubscriptionHandler.promises.attemptPaypalInvoiceCollection(
  634. this.activeRecurlySubscription.account.account_code
  635. )
  636. })
  637. it('gets past due invoices', function () {
  638. sinon.assert.calledWith(
  639. this.RecurlyWrapper.promises.getAccountPastDueInvoices,
  640. this.activeRecurlySubscription.account.account_code
  641. )
  642. })
  643. it('calls attemptInvoiceCollection', function () {
  644. sinon.assert.calledWith(
  645. this.RecurlyWrapper.promises.attemptInvoiceCollection,
  646. 'mock-invoice-number'
  647. )
  648. })
  649. })
  650. })
  651. describe('validateNoSubscriptionInRecurly', function () {
  652. describe('with a subscription in recurly', function () {
  653. beforeEach(async function () {
  654. this.RecurlyWrapper.promises.listAccountActiveSubscriptions.resolves([
  655. this.subscription,
  656. ])
  657. this.isValid =
  658. await this.SubscriptionHandler.promises.validateNoSubscriptionInRecurly(
  659. this.user_id
  660. )
  661. })
  662. it('should call RecurlyWrapper.promises.listAccountActiveSubscriptions with the user id', function () {
  663. this.RecurlyWrapper.promises.listAccountActiveSubscriptions
  664. .calledWith(this.user_id)
  665. .should.equal(true)
  666. })
  667. it('should sync the subscription', function () {
  668. this.SubscriptionUpdater.promises.syncSubscription
  669. .calledWith(this.subscription, this.user_id)
  670. .should.equal(true)
  671. })
  672. it('should return false', function () {
  673. expect(this.isValid).to.equal(false)
  674. })
  675. })
  676. describe('with no subscription in recurly', function () {
  677. beforeEach(async function () {
  678. this.isValid =
  679. await this.SubscriptionHandler.promises.validateNoSubscriptionInRecurly(
  680. this.user_id
  681. )
  682. })
  683. it('should be rejected and not sync the subscription', function () {
  684. this.SubscriptionUpdater.promises.syncSubscription.called.should.equal(
  685. false
  686. )
  687. })
  688. it('should return true', function () {
  689. expect(this.isValid).to.equal(true)
  690. })
  691. })
  692. })
  693. })