SubscriptionHandlerTests.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478
  1. /* eslint-disable
  2. max-len,
  3. no-return-assign,
  4. no-unused-vars,
  5. */
  6. // TODO: This file was created by bulk-decaffeinate.
  7. // Fix any style issues and re-enable lint.
  8. /*
  9. * decaffeinate suggestions:
  10. * DS102: Remove unnecessary code created because of implicit returns
  11. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  12. */
  13. const SandboxedModule = require('sandboxed-module')
  14. const should = require('chai').should()
  15. const sinon = require('sinon')
  16. const querystring = require('querystring')
  17. const modulePath =
  18. '../../../../app/src/Features/Subscription/SubscriptionHandler'
  19. const mockRecurlySubscriptions = {
  20. 'subscription-123-active': {
  21. uuid: 'subscription-123-active',
  22. plan: {
  23. name: 'Gold',
  24. plan_code: 'gold'
  25. },
  26. current_period_ends_at: new Date(),
  27. state: 'active',
  28. unit_amount_in_cents: 999,
  29. account: {
  30. account_code: 'user-123'
  31. }
  32. }
  33. }
  34. describe('SubscriptionHandler', function() {
  35. beforeEach(function() {
  36. this.Settings = {
  37. plans: [
  38. {
  39. planCode: 'collaborator',
  40. name: 'Collaborator',
  41. features: {
  42. collaborators: -1,
  43. versioning: true
  44. }
  45. }
  46. ],
  47. defaultPlanCode: {
  48. collaborators: 0,
  49. versioning: false
  50. }
  51. }
  52. this.activeRecurlySubscription =
  53. mockRecurlySubscriptions['subscription-123-active']
  54. this.User = {}
  55. this.user = { _id: (this.user_id = 'user_id_here_') }
  56. this.subscription = {
  57. recurlySubscription_id: this.activeRecurlySubscription.uuid
  58. }
  59. this.RecurlyWrapper = {
  60. getSubscription: sinon
  61. .stub()
  62. .callsArgWith(2, null, this.activeRecurlySubscription),
  63. updateSubscription: sinon
  64. .stub()
  65. .callsArgWith(2, null, this.activeRecurlySubscription),
  66. cancelSubscription: sinon.stub().callsArgWith(1),
  67. reactivateSubscription: sinon.stub().callsArgWith(1),
  68. redeemCoupon: sinon.stub().callsArgWith(2),
  69. createSubscription: sinon
  70. .stub()
  71. .callsArgWith(3, null, this.activeRecurlySubscription)
  72. }
  73. this.DropboxHandler = { unlinkAccount: sinon.stub().callsArgWith(1) }
  74. this.SubscriptionUpdater = {
  75. syncSubscription: sinon.stub().callsArgWith(2),
  76. startFreeTrial: sinon.stub().callsArgWith(1)
  77. }
  78. this.LimitationsManager = { userHasV2Subscription: sinon.stub() }
  79. this.EmailHandler = { sendEmail: sinon.stub() }
  80. this.AnalyticsManager = { recordEvent: sinon.stub() }
  81. this.SubscriptionHandler = SandboxedModule.require(modulePath, {
  82. globals: {
  83. console: console
  84. },
  85. requires: {
  86. './RecurlyWrapper': this.RecurlyWrapper,
  87. 'settings-sharelatex': this.Settings,
  88. '../../models/User': {
  89. User: this.User
  90. },
  91. './SubscriptionUpdater': this.SubscriptionUpdater,
  92. 'logger-sharelatex': { log() {} },
  93. './LimitationsManager': this.LimitationsManager,
  94. '../Email/EmailHandler': this.EmailHandler,
  95. '../Dropbox/DropboxHandler': this.DropboxHandler,
  96. '../../infrastructure/Events': (this.Events = { emit: sinon.stub() }),
  97. '../Analytics/AnalyticsManager': this.AnalyticsManager
  98. }
  99. })
  100. return (this.SubscriptionHandler.syncSubscriptionToUser = sinon
  101. .stub()
  102. .callsArgWith(2))
  103. })
  104. describe('createSubscription', function() {
  105. beforeEach(function() {
  106. this.callback = sinon.stub()
  107. this.subscriptionDetails = {
  108. cvv: '123',
  109. number: '12345'
  110. }
  111. this.recurly_token_id = '45555666'
  112. return (this.SubscriptionHandler.validateNoSubscriptionInRecurly = sinon
  113. .stub()
  114. .yields(null, true))
  115. })
  116. describe('successfully', function() {
  117. beforeEach(function() {
  118. return this.SubscriptionHandler.createSubscription(
  119. this.user,
  120. this.subscriptionDetails,
  121. this.recurly_token_id,
  122. this.callback
  123. )
  124. })
  125. it('should create the subscription with the wrapper', function() {
  126. return this.RecurlyWrapper.createSubscription
  127. .calledWith(
  128. this.user,
  129. this.subscriptionDetails,
  130. this.recurly_token_id
  131. )
  132. .should.equal(true)
  133. })
  134. it('should sync the subscription to the user', function() {
  135. this.SubscriptionUpdater.syncSubscription.calledOnce.should.equal(true)
  136. this.SubscriptionUpdater.syncSubscription.args[0][0].should.deep.equal(
  137. this.activeRecurlySubscription
  138. )
  139. return this.SubscriptionUpdater.syncSubscription.args[0][1].should.deep.equal(
  140. this.user._id
  141. )
  142. })
  143. })
  144. describe('when there is already a subscription in Recurly', function() {
  145. beforeEach(function() {
  146. this.SubscriptionHandler.validateNoSubscriptionInRecurly = sinon
  147. .stub()
  148. .yields(null, false)
  149. return this.SubscriptionHandler.createSubscription(
  150. this.user,
  151. this.subscriptionDetails,
  152. this.recurly_token_id,
  153. this.callback
  154. )
  155. })
  156. it('should return an error', function() {
  157. return this.callback.calledWith(
  158. new Error('user already has subscription in recurly')
  159. )
  160. })
  161. })
  162. })
  163. describe('updateSubscription', function() {
  164. describe('with a user with a subscription', () =>
  165. describe('with a valid plan code', function() {
  166. beforeEach(function(done) {
  167. this.plan_code = 'collaborator'
  168. this.LimitationsManager.userHasV2Subscription.callsArgWith(
  169. 1,
  170. null,
  171. true,
  172. this.subscription
  173. )
  174. return this.SubscriptionHandler.updateSubscription(
  175. this.user,
  176. this.plan_code,
  177. null,
  178. done
  179. )
  180. })
  181. it('should update the subscription', function() {
  182. this.RecurlyWrapper.updateSubscription
  183. .calledWith(this.subscription.recurlySubscription_id)
  184. .should.equal(true)
  185. const updateOptions = this.RecurlyWrapper.updateSubscription
  186. .args[0][1]
  187. return updateOptions.plan_code.should.equal(this.plan_code)
  188. })
  189. it('should update immediately', function() {
  190. const updateOptions = this.RecurlyWrapper.updateSubscription
  191. .args[0][1]
  192. return updateOptions.timeframe.should.equal('now')
  193. })
  194. it('should sync the new subscription to the user', function() {
  195. this.SubscriptionUpdater.syncSubscription.calledOnce.should.equal(
  196. true
  197. )
  198. this.SubscriptionUpdater.syncSubscription.args[0][0].should.deep.equal(
  199. this.activeRecurlySubscription
  200. )
  201. return this.SubscriptionUpdater.syncSubscription.args[0][1].should.deep.equal(
  202. this.user._id
  203. )
  204. })
  205. }))
  206. describe('with a user without a subscription', function() {
  207. beforeEach(function(done) {
  208. this.LimitationsManager.userHasV2Subscription.callsArgWith(
  209. 1,
  210. null,
  211. false
  212. )
  213. return this.SubscriptionHandler.updateSubscription(
  214. this.user,
  215. this.plan_code,
  216. null,
  217. done
  218. )
  219. })
  220. it('should redirect to the subscription dashboard', function() {
  221. this.RecurlyWrapper.updateSubscription.called.should.equal(false)
  222. return this.SubscriptionHandler.syncSubscriptionToUser.called.should.equal(
  223. false
  224. )
  225. })
  226. })
  227. describe('with a coupon code', function() {
  228. beforeEach(function(done) {
  229. this.plan_code = 'collaborator'
  230. this.coupon_code = '1231312'
  231. this.LimitationsManager.userHasV2Subscription.callsArgWith(
  232. 1,
  233. null,
  234. true,
  235. this.subscription
  236. )
  237. return this.SubscriptionHandler.updateSubscription(
  238. this.user,
  239. this.plan_code,
  240. this.coupon_code,
  241. done
  242. )
  243. })
  244. it('should get the users account', function() {
  245. return this.RecurlyWrapper.getSubscription
  246. .calledWith(this.activeRecurlySubscription.uuid)
  247. .should.equal(true)
  248. })
  249. it('should redeme the coupon', function(done) {
  250. this.RecurlyWrapper.redeemCoupon
  251. .calledWith(
  252. this.activeRecurlySubscription.account.account_code,
  253. this.coupon_code
  254. )
  255. .should.equal(true)
  256. return done()
  257. })
  258. it('should update the subscription', function() {
  259. this.RecurlyWrapper.updateSubscription
  260. .calledWith(this.subscription.recurlySubscription_id)
  261. .should.equal(true)
  262. const updateOptions = this.RecurlyWrapper.updateSubscription.args[0][1]
  263. return updateOptions.plan_code.should.equal(this.plan_code)
  264. })
  265. })
  266. })
  267. describe('cancelSubscription', function() {
  268. describe('with a user without a subscription', function() {
  269. beforeEach(function(done) {
  270. this.LimitationsManager.userHasV2Subscription.callsArgWith(
  271. 1,
  272. null,
  273. false,
  274. this.subscription
  275. )
  276. return this.SubscriptionHandler.cancelSubscription(this.user, done)
  277. })
  278. it('should redirect to the subscription dashboard', function() {
  279. return this.RecurlyWrapper.cancelSubscription.called.should.equal(false)
  280. })
  281. })
  282. describe('with a user with a subscription', function() {
  283. beforeEach(function(done) {
  284. this.LimitationsManager.userHasV2Subscription.callsArgWith(
  285. 1,
  286. null,
  287. true,
  288. this.subscription
  289. )
  290. return this.SubscriptionHandler.cancelSubscription(this.user, done)
  291. })
  292. it('should cancel the subscription', function() {
  293. this.RecurlyWrapper.cancelSubscription.called.should.equal(true)
  294. return this.RecurlyWrapper.cancelSubscription
  295. .calledWith(this.subscription.recurlySubscription_id)
  296. .should.equal(true)
  297. })
  298. it('should trigger the cancel subscription event', function() {
  299. return this.Events.emit
  300. .calledWith('cancelSubscription', this.user._id)
  301. .should.equal(true)
  302. })
  303. })
  304. })
  305. describe('reactiveRecurlySubscription', function() {
  306. describe('with a user without a subscription', function() {
  307. beforeEach(function(done) {
  308. this.LimitationsManager.userHasV2Subscription.callsArgWith(
  309. 1,
  310. null,
  311. false,
  312. this.subscription
  313. )
  314. return this.SubscriptionHandler.reactivateSubscription(this.user, done)
  315. })
  316. it('should redirect to the subscription dashboard', function() {
  317. return this.RecurlyWrapper.reactivateSubscription.called.should.equal(
  318. false
  319. )
  320. })
  321. it('should not send a notification email', function() {
  322. return sinon.assert.notCalled(this.EmailHandler.sendEmail)
  323. })
  324. })
  325. describe('with a user with a subscription', function() {
  326. beforeEach(function(done) {
  327. this.LimitationsManager.userHasV2Subscription.callsArgWith(
  328. 1,
  329. null,
  330. true,
  331. this.subscription
  332. )
  333. return this.SubscriptionHandler.reactivateSubscription(this.user, done)
  334. })
  335. it('should reactivate the subscription', function() {
  336. this.RecurlyWrapper.reactivateSubscription.called.should.equal(true)
  337. return this.RecurlyWrapper.reactivateSubscription
  338. .calledWith(this.subscription.recurlySubscription_id)
  339. .should.equal(true)
  340. })
  341. it('should send a notification email', function() {
  342. return sinon.assert.calledWith(
  343. this.EmailHandler.sendEmail,
  344. 'reactivatedSubscription'
  345. )
  346. })
  347. })
  348. })
  349. describe('recurlyCallback', () =>
  350. describe('with an actionable request', function() {
  351. beforeEach(function(done) {
  352. this.user.id = this.activeRecurlySubscription.account.account_code
  353. this.User.findById = (userId, callback) => {
  354. userId.should.equal(this.user.id)
  355. return callback(null, this.user)
  356. }
  357. return this.SubscriptionHandler.recurlyCallback(
  358. this.activeRecurlySubscription,
  359. done
  360. )
  361. })
  362. it('should request the affected subscription from the API', function() {
  363. return this.RecurlyWrapper.getSubscription
  364. .calledWith(this.activeRecurlySubscription.uuid)
  365. .should.equal(true)
  366. })
  367. it('should request the account details of the subscription', function() {
  368. const options = this.RecurlyWrapper.getSubscription.args[0][1]
  369. return options.includeAccount.should.equal(true)
  370. })
  371. it('should sync the subscription to the user', function() {
  372. this.SubscriptionUpdater.syncSubscription.calledOnce.should.equal(true)
  373. this.SubscriptionUpdater.syncSubscription.args[0][0].should.deep.equal(
  374. this.activeRecurlySubscription
  375. )
  376. return this.SubscriptionUpdater.syncSubscription.args[0][1].should.deep.equal(
  377. this.user._id
  378. )
  379. })
  380. }))
  381. describe('validateNoSubscriptionInRecurly', function() {
  382. beforeEach(function() {
  383. this.subscriptions = []
  384. this.RecurlyWrapper.listAccountActiveSubscriptions = sinon
  385. .stub()
  386. .yields(null, this.subscriptions)
  387. this.SubscriptionUpdater.syncSubscription = sinon.stub().yields()
  388. return (this.callback = sinon.stub())
  389. })
  390. describe('with no subscription in recurly', function() {
  391. beforeEach(function() {
  392. this.subscriptions.push((this.subscription = { mock: 'subscription' }))
  393. return this.SubscriptionHandler.validateNoSubscriptionInRecurly(
  394. this.user_id,
  395. this.callback
  396. )
  397. })
  398. it('should call RecurlyWrapper.listAccountActiveSubscriptions with the user id', function() {
  399. return this.RecurlyWrapper.listAccountActiveSubscriptions
  400. .calledWith(this.user_id)
  401. .should.equal(true)
  402. })
  403. it('should sync the subscription', function() {
  404. return this.SubscriptionUpdater.syncSubscription
  405. .calledWith(this.subscription, this.user_id)
  406. .should.equal(true)
  407. })
  408. it('should call the callback with valid == false', function() {
  409. return this.callback.calledWith(null, false).should.equal(true)
  410. })
  411. })
  412. describe('with a subscription in recurly', function() {
  413. beforeEach(function() {
  414. return this.SubscriptionHandler.validateNoSubscriptionInRecurly(
  415. this.user_id,
  416. this.callback
  417. )
  418. })
  419. it('should not sync the subscription', function() {
  420. return this.SubscriptionUpdater.syncSubscription.called.should.equal(
  421. false
  422. )
  423. })
  424. it('should call the callback with valid == true', function() {
  425. return this.callback.calledWith(null, true).should.equal(true)
  426. })
  427. })
  428. })
  429. })