SubscriptionHandlerTests.coffee 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. SandboxedModule = require('sandboxed-module')
  2. should = require('chai').should()
  3. sinon = require 'sinon'
  4. querystring = require 'querystring'
  5. modulePath = "../../../../app/js/Features/Subscription/SubscriptionHandler"
  6. mockRecurlySubscriptions =
  7. "subscription-123-active":
  8. uuid: "subscription-123-active"
  9. plan:
  10. name: "Gold"
  11. plan_code: "gold"
  12. current_period_ends_at: new Date()
  13. state: "active"
  14. unit_amount_in_cents: 999
  15. account:
  16. account_code: "user-123"
  17. describe "SubscriptionHandler", ->
  18. beforeEach ->
  19. @Settings =
  20. plans : [{
  21. planCode: "collaborator"
  22. name: "Collaborator"
  23. features:
  24. collaborators: -1
  25. versioning: true
  26. }]
  27. defaultPlanCode :
  28. collaborators: 0
  29. versioning: false
  30. @activeRecurlySubscription = mockRecurlySubscriptions["subscription-123-active"]
  31. @User = {}
  32. @user =
  33. _id: @user_id = "user_id_here_"
  34. @subscription =
  35. recurlySubscription_id: @activeRecurlySubscription.uuid
  36. @RecurlyWrapper =
  37. getSubscription: sinon.stub().callsArgWith(2, null, @activeRecurlySubscription)
  38. updateSubscription: sinon.stub().callsArgWith(2, null, @activeRecurlySubscription)
  39. cancelSubscription: sinon.stub().callsArgWith(1)
  40. reactivateSubscription: sinon.stub().callsArgWith(1)
  41. redeemCoupon:sinon.stub().callsArgWith(2)
  42. createSubscription: sinon.stub().callsArgWith(3, null, @activeRecurlySubscription)
  43. @DropboxHandler =
  44. unlinkAccount:sinon.stub().callsArgWith(1)
  45. @SubscriptionUpdater =
  46. syncSubscription: sinon.stub().callsArgWith(2)
  47. startFreeTrial: sinon.stub().callsArgWith(1)
  48. @LimitationsManager =
  49. userHasSubscription: sinon.stub()
  50. userHasSubscriptionOrFreeTrial: sinon.stub()
  51. @EmailHandler =
  52. sendEmail:sinon.stub()
  53. @AnalyticsManager =
  54. recordEvent:sinon.stub()
  55. @SubscriptionHandler = SandboxedModule.require modulePath, requires:
  56. "./RecurlyWrapper": @RecurlyWrapper
  57. "settings-sharelatex": @Settings
  58. '../../models/User': User:@User
  59. './SubscriptionUpdater': @SubscriptionUpdater
  60. "logger-sharelatex":{log:->}
  61. './LimitationsManager':@LimitationsManager
  62. "../Email/EmailHandler":@EmailHandler
  63. "../Dropbox/DropboxHandler":@DropboxHandler
  64. "../../infrastructure/Events": @Events = {emit: sinon.stub()}
  65. "../Analytics/AnalyticsManager": @AnalyticsManager
  66. @SubscriptionHandler.syncSubscriptionToUser = sinon.stub().callsArgWith(2)
  67. describe "createSubscription", ->
  68. beforeEach ->
  69. @callback = sinon.stub()
  70. @subscriptionDetails =
  71. cvv:"123"
  72. number:"12345"
  73. @recurly_token_id = "45555666"
  74. @SubscriptionHandler.validateNoSubscriptionInRecurly = sinon.stub().yields(null, true)
  75. describe "successfully", ->
  76. beforeEach ->
  77. @SubscriptionHandler.createSubscription(@user, @subscriptionDetails, @recurly_token_id, @callback)
  78. it "should create the subscription with the wrapper", ->
  79. @RecurlyWrapper.createSubscription.calledWith(@user, @subscriptionDetails, @recurly_token_id).should.equal true
  80. it "should sync the subscription to the user", ->
  81. @SubscriptionUpdater.syncSubscription.calledOnce.should.equal true
  82. @SubscriptionUpdater.syncSubscription.args[0][0].should.deep.equal @activeRecurlySubscription
  83. @SubscriptionUpdater.syncSubscription.args[0][1].should.deep.equal @user._id
  84. describe "when there is already a subscription in Recurly", ->
  85. beforeEach ->
  86. @SubscriptionHandler.validateNoSubscriptionInRecurly = sinon.stub().yields(null, false)
  87. @SubscriptionHandler.createSubscription(@user, @subscriptionDetails, @recurly_token_id, @callback)
  88. it "should return an error", ->
  89. @callback.calledWith(new Error("user already has subscription in recurly"))
  90. describe "updateSubscription", ->
  91. describe "with a user with a subscription", ->
  92. describe "with a valid plan code", ->
  93. beforeEach (done) ->
  94. @plan_code = "collaborator"
  95. @LimitationsManager.userHasSubscription.callsArgWith(1, null, true, @subscription)
  96. @SubscriptionHandler.updateSubscription(@user, @plan_code, null, done)
  97. it "should update the subscription", ->
  98. @RecurlyWrapper.updateSubscription.calledWith(@subscription.recurlySubscription_id).should.equal true
  99. updateOptions = @RecurlyWrapper.updateSubscription.args[0][1]
  100. updateOptions.plan_code.should.equal @plan_code
  101. it "should update immediately", ->
  102. updateOptions = @RecurlyWrapper.updateSubscription.args[0][1]
  103. updateOptions.timeframe.should.equal "now"
  104. it "should sync the new subscription to the user", ->
  105. @SubscriptionUpdater.syncSubscription.calledOnce.should.equal true
  106. @SubscriptionUpdater.syncSubscription.args[0][0].should.deep.equal @activeRecurlySubscription
  107. @SubscriptionUpdater.syncSubscription.args[0][1].should.deep.equal @user._id
  108. describe "with a user without a subscription", ->
  109. beforeEach (done) ->
  110. @LimitationsManager.userHasSubscription.callsArgWith(1, null, false)
  111. @SubscriptionHandler.updateSubscription(@user, @plan_code, null, done)
  112. it "should redirect to the subscription dashboard", ->
  113. @RecurlyWrapper.updateSubscription.called.should.equal false
  114. @SubscriptionHandler.syncSubscriptionToUser.called.should.equal false
  115. describe "with a coupon code", ->
  116. beforeEach (done) ->
  117. @plan_code = "collaborator"
  118. @coupon_code = "1231312"
  119. @LimitationsManager.userHasSubscription.callsArgWith(1, null, true, @subscription)
  120. @SubscriptionHandler.updateSubscription(@user, @plan_code, @coupon_code, done)
  121. it "should get the users account", ->
  122. @RecurlyWrapper.getSubscription.calledWith(@activeRecurlySubscription.uuid).should.equal true
  123. it "should redeme the coupon", (done)->
  124. @RecurlyWrapper.redeemCoupon.calledWith(@activeRecurlySubscription.account.account_code, @coupon_code).should.equal true
  125. done()
  126. it "should update the subscription", ->
  127. @RecurlyWrapper.updateSubscription.calledWith(@subscription.recurlySubscription_id).should.equal true
  128. updateOptions = @RecurlyWrapper.updateSubscription.args[0][1]
  129. updateOptions.plan_code.should.equal @plan_code
  130. describe "cancelSubscription", ->
  131. describe "with a user without a subscription", ->
  132. beforeEach (done) ->
  133. @LimitationsManager.userHasSubscription.callsArgWith(1, null, false, @subscription)
  134. @SubscriptionHandler.cancelSubscription @user, done
  135. it "should redirect to the subscription dashboard", ->
  136. @RecurlyWrapper.cancelSubscription.called.should.equal false
  137. describe "with a user with a subscription", ->
  138. beforeEach (done) ->
  139. @LimitationsManager.userHasSubscription.callsArgWith(1, null, true, @subscription)
  140. @SubscriptionHandler.cancelSubscription @user, done
  141. it "should cancel the subscription", ->
  142. @RecurlyWrapper.cancelSubscription.called.should.equal true
  143. @RecurlyWrapper.cancelSubscription.calledWith(@subscription.recurlySubscription_id).should.equal true
  144. it "should trigger the cancel subscription event", ->
  145. @Events.emit.calledWith("cancelSubscription", @user._id).should.equal true
  146. describe "reactiveRecurlySubscription", ->
  147. describe "with a user without a subscription", ->
  148. beforeEach (done) ->
  149. @LimitationsManager.userHasSubscription.callsArgWith(1, null, false, @subscription)
  150. @SubscriptionHandler.reactivateSubscription @user, done
  151. it "should redirect to the subscription dashboard", ->
  152. @RecurlyWrapper.reactivateSubscription.called.should.equal false
  153. describe "with a user with a subscription", ->
  154. beforeEach (done) ->
  155. @LimitationsManager.userHasSubscription.callsArgWith(1, null, true, @subscription)
  156. @SubscriptionHandler.reactivateSubscription @user, done
  157. it "should reactivate the subscription", ->
  158. @RecurlyWrapper.reactivateSubscription.called.should.equal true
  159. @RecurlyWrapper.reactivateSubscription.calledWith(@subscription.recurlySubscription_id).should.equal true
  160. describe "recurlyCallback", ->
  161. describe "with an actionable request", ->
  162. beforeEach (done) ->
  163. @user.id = @activeRecurlySubscription.account.account_code
  164. @User.findById = (userId, callback) =>
  165. userId.should.equal @user.id
  166. callback null, @user
  167. @SubscriptionHandler.recurlyCallback(@activeRecurlySubscription, done)
  168. it "should request the affected subscription from the API", ->
  169. @RecurlyWrapper.getSubscription.calledWith(@activeRecurlySubscription.uuid).should.equal true
  170. it "should request the account details of the subscription", ->
  171. options = @RecurlyWrapper.getSubscription.args[0][1]
  172. options.includeAccount.should.equal true
  173. it "should sync the subscription to the user", ->
  174. @SubscriptionUpdater.syncSubscription.calledOnce.should.equal true
  175. @SubscriptionUpdater.syncSubscription.args[0][0].should.deep.equal @activeRecurlySubscription
  176. @SubscriptionUpdater.syncSubscription.args[0][1].should.deep.equal @user._id
  177. describe "validateNoSubscriptionInRecurly", ->
  178. beforeEach ->
  179. @subscriptions = []
  180. @RecurlyWrapper.listAccountActiveSubscriptions = sinon.stub().yields(null, @subscriptions)
  181. @SubscriptionUpdater.syncSubscription = sinon.stub().yields()
  182. @callback = sinon.stub()
  183. describe "with no subscription in recurly", ->
  184. beforeEach ->
  185. @subscriptions.push @subscription = { "mock": "subscription" }
  186. @SubscriptionHandler.validateNoSubscriptionInRecurly @user_id, @callback
  187. it "should call RecurlyWrapper.listAccountActiveSubscriptions with the user id", ->
  188. @RecurlyWrapper.listAccountActiveSubscriptions
  189. .calledWith(@user_id)
  190. .should.equal true
  191. it "should sync the subscription", ->
  192. @SubscriptionUpdater.syncSubscription
  193. .calledWith(@subscription, @user_id)
  194. .should.equal true
  195. it "should call the callback with valid == false", ->
  196. @callback.calledWith(null, false).should.equal true
  197. describe "with a subscription in recurly", ->
  198. beforeEach ->
  199. @SubscriptionHandler.validateNoSubscriptionInRecurly @user_id, @callback
  200. it "should not sync the subscription", ->
  201. @SubscriptionUpdater.syncSubscription
  202. .called
  203. .should.equal false
  204. it "should call the callback with valid == true", ->
  205. @callback.calledWith(null, true).should.equal true