SubscriptionControllerTests.coffee 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430
  1. SandboxedModule = require('sandboxed-module')
  2. sinon = require 'sinon'
  3. should = require("chai").should()
  4. expect = require("chai").expect
  5. MockRequest = require "../helpers/MockRequest"
  6. MockResponse = require "../helpers/MockResponse"
  7. modulePath = '../../../../app/js/Features/Subscription/SubscriptionController'
  8. mockSubscriptions =
  9. "subscription-123-active":
  10. uuid: "subscription-123-active"
  11. plan:
  12. name: "Gold"
  13. plan_code: "gold"
  14. current_period_ends_at: new Date()
  15. state: "active"
  16. unit_amount_in_cents: 999
  17. account:
  18. account_code: "user-123"
  19. describe "SubscriptionController", ->
  20. beforeEach ->
  21. @user = {email:"tom@yahoo.com", _id: 'one', signUpDate: new Date('2000-10-01')}
  22. @activeRecurlySubscription = mockSubscriptions["subscription-123-active"]
  23. @AuthenticationController =
  24. getLoggedInUser: sinon.stub().callsArgWith(1, null, @user)
  25. getLoggedInUserId: sinon.stub().returns(@user._id)
  26. getSessionUser: sinon.stub().returns(@user)
  27. isUserLoggedIn: sinon.stub().returns(true)
  28. @SubscriptionHandler =
  29. createSubscription: sinon.stub().callsArgWith(3)
  30. updateSubscription: sinon.stub().callsArgWith(3)
  31. reactivateSubscription: sinon.stub().callsArgWith(1)
  32. cancelSubscription: sinon.stub().callsArgWith(1)
  33. recurlyCallback: sinon.stub().callsArgWith(1)
  34. startFreeTrial: sinon.stub()
  35. @PlansLocator =
  36. findLocalPlanInSettings: sinon.stub()
  37. @LimitationsManager =
  38. hasPaidSubscription: sinon.stub()
  39. userHasV1OrV2Subscription : sinon.stub()
  40. userHasV2Subscription: sinon.stub()
  41. @SubscriptionViewModelBuilder =
  42. buildUsersSubscriptionViewModel:sinon.stub().callsArgWith(1, null, {})
  43. buildViewModel: sinon.stub()
  44. @settings =
  45. coupon_codes:
  46. upgradeToAnnualPromo:
  47. student:"STUDENTCODEHERE"
  48. collaborator:"COLLABORATORCODEHERE"
  49. apis:
  50. recurly:
  51. subdomain:"sl"
  52. siteUrl: "http://de.sharelatex.dev:3000"
  53. gaExperiments:{}
  54. @GeoIpLookup =
  55. getCurrencyCode:sinon.stub()
  56. @SubscriptionDomainHandler =
  57. getDomainLicencePage:sinon.stub()
  58. @UserGetter =
  59. getUser: sinon.stub().callsArgWith(2, null, @user)
  60. @SubscriptionController = SandboxedModule.require modulePath, requires:
  61. '../Authentication/AuthenticationController': @AuthenticationController
  62. './SubscriptionHandler': @SubscriptionHandler
  63. "./PlansLocator": @PlansLocator
  64. './SubscriptionViewModelBuilder': @SubscriptionViewModelBuilder
  65. "./LimitationsManager": @LimitationsManager
  66. "../../infrastructure/GeoIpLookup":@GeoIpLookup
  67. "logger-sharelatex":
  68. log:->
  69. warn:->
  70. "settings-sharelatex": @settings
  71. "./SubscriptionDomainHandler":@SubscriptionDomainHandler
  72. "../User/UserGetter": @UserGetter
  73. "./RecurlyWrapper": @RecurlyWrapper = {}
  74. "./FeaturesUpdater": @FeaturesUpdater = {}
  75. "./GroupPlansData": @GroupPlansData = {}
  76. "./V1SubscriptionManager": @V1SubscriptionManager = {}
  77. @res = new MockResponse()
  78. @req = new MockRequest()
  79. @req.body = {}
  80. @req.query =
  81. planCode:"123123"
  82. @stubbedCurrencyCode = "GBP"
  83. describe "plansPage", ->
  84. beforeEach ->
  85. @req.ip = "1234.3123.3131.333 313.133.445.666 653.5345.5345.534"
  86. @GeoIpLookup.getCurrencyCode.callsArgWith(1, null, @stubbedCurrencyCode)
  87. describe 'when user is logged in', (done) ->
  88. beforeEach (done) ->
  89. @res.callback = done
  90. @SubscriptionController.plansPage(@req, @res)
  91. it 'should fetch the current user', (done) ->
  92. @UserGetter.getUser.callCount.should.equal 1
  93. done()
  94. describe 'not dependant on logged in state', (done) ->
  95. # these could have been put in 'when user is not logged in' too
  96. it "should set the recommended currency from the geoiplookup", (done)->
  97. @res.renderedVariables.recomendedCurrency.should.equal(@stubbedCurrencyCode)
  98. @GeoIpLookup.getCurrencyCode.calledWith(@req.ip).should.equal true
  99. done()
  100. it 'should include data for features table', (done) ->
  101. @res.renderedVariables.planFeatures.length.should.not.equal 0
  102. done()
  103. describe 'when user is not logged in', (done) ->
  104. beforeEach (done) ->
  105. @UserGetter =
  106. getUser: sinon.stub().callsArgWith(2, null, null)
  107. @res.callback = done
  108. @AuthenticationController =
  109. getLoggedInUser: sinon.stub().callsArgWith(1, null, null)
  110. getLoggedInUserId: sinon.stub().returns(null)
  111. getSessionUser: sinon.stub().returns(null)
  112. isUserLoggedIn: sinon.stub().returns(false)
  113. @SubscriptionController = SandboxedModule.require modulePath, requires:
  114. '../Authentication/AuthenticationController': @AuthenticationController
  115. './SubscriptionHandler': @SubscriptionHandler
  116. "./PlansLocator": @PlansLocator
  117. './SubscriptionViewModelBuilder': @SubscriptionViewModelBuilder
  118. "./LimitationsManager": @LimitationsManager
  119. "../../infrastructure/GeoIpLookup":@GeoIpLookup
  120. "logger-sharelatex":
  121. log:->
  122. warn:->
  123. "settings-sharelatex": @settings
  124. "./SubscriptionDomainHandler":@SubscriptionDomainHandler
  125. "../User/UserGetter": @UserGetter
  126. "./RecurlyWrapper": @RecurlyWrapper = {}
  127. "./FeaturesUpdater": @FeaturesUpdater = {}
  128. "./GroupPlansData": @GroupPlansData
  129. @SubscriptionController.plansPage(@req, @res)
  130. it 'should not fetch the current user', (done) ->
  131. @UserGetter.getUser.callCount.should.equal 0
  132. done()
  133. describe "paymentPage", ->
  134. beforeEach ->
  135. @req.headers = {}
  136. @RecurlyWrapper.sign = sinon.stub().yields(null, @signature = "signature")
  137. @SubscriptionHandler.validateNoSubscriptionInRecurly = sinon.stub().yields(null, true)
  138. @GeoIpLookup.getCurrencyCode.callsArgWith(1, null, @stubbedCurrencyCode)
  139. describe "with a user without a subscription", ->
  140. beforeEach ->
  141. @LimitationsManager.userHasV1OrV2Subscription.callsArgWith(1, null, false)
  142. @PlansLocator.findLocalPlanInSettings.returns({})
  143. describe "with a valid plan code", ->
  144. it "should render the new subscription page", (done)->
  145. @res.render = (page, opts)=>
  146. page.should.equal "subscriptions/new"
  147. done()
  148. @SubscriptionController.paymentPage @req, @res
  149. describe "with a user with subscription", ->
  150. it "should redirect to the subscription dashboard", (done)->
  151. @LimitationsManager.userHasV1OrV2Subscription.callsArgWith(1, null, true)
  152. @res.redirect = (url)=>
  153. url.should.equal "/user/subscription"
  154. done()
  155. @SubscriptionController.paymentPage(@req, @res)
  156. describe "with an invalid plan code", ->
  157. it "should redirect to the subscription dashboard", (done)->
  158. @LimitationsManager.userHasV1OrV2Subscription.callsArgWith(1, null, false)
  159. @PlansLocator.findLocalPlanInSettings.returns(null)
  160. @res.redirect = (url)=>
  161. url.should.equal "/user/subscription"
  162. done()
  163. @SubscriptionController.paymentPage(@req, @res)
  164. describe "which currency to use", ->
  165. beforeEach ->
  166. @LimitationsManager.userHasV1OrV2Subscription.callsArgWith(1, null, false)
  167. @PlansLocator.findLocalPlanInSettings.returns({})
  168. it "should use the set currency from the query string", (done)->
  169. @req.query.currency = "EUR"
  170. @res.render = (page, opts)=>
  171. opts.currency.should.equal "EUR"
  172. opts.currency.should.not.equal @stubbedCurrencyCode
  173. done()
  174. @SubscriptionController.paymentPage @req, @res
  175. it "should upercase the currency code", (done)->
  176. @req.query.currency = "eur"
  177. @res.render = (page, opts)=>
  178. opts.currency.should.equal "EUR"
  179. done()
  180. @SubscriptionController.paymentPage @req, @res
  181. it "should use the geo ip currency if non is provided", (done)->
  182. @req.query.currency = null
  183. @res.render = (page, opts)=>
  184. opts.currency.should.equal @stubbedCurrencyCode
  185. done()
  186. @SubscriptionController.paymentPage @req, @res
  187. describe "with a recurly subscription already", ->
  188. it "should redirect to the subscription dashboard", (done)->
  189. @LimitationsManager.userHasV1OrV2Subscription.callsArgWith(1, null, false)
  190. @SubscriptionHandler.validateNoSubscriptionInRecurly = sinon.stub().yields(null, false)
  191. @res.redirect = (url)=>
  192. url.should.equal "/user/subscription"
  193. done()
  194. @SubscriptionController.paymentPage(@req, @res)
  195. describe "successful_subscription", ->
  196. beforeEach (done) ->
  197. @SubscriptionViewModelBuilder.buildUsersSubscriptionViewModel.callsArgWith(1, null, {})
  198. @res.callback = done
  199. @SubscriptionController.successful_subscription @req, @res
  200. describe "userSubscriptionPage", ->
  201. beforeEach (done) ->
  202. @SubscriptionViewModelBuilder.buildUsersSubscriptionViewModel.callsArgWith(1, null, {
  203. personalSubscription: @personalSubscription = { 'personal-subscription': 'mock' }
  204. memberGroupSubscriptions: @memberGroupSubscriptions = { 'group-subscriptions': 'mock' }
  205. v1Subscriptions: @v1Subscriptions = { 'v1-subscriptions': 'mock' }
  206. })
  207. @SubscriptionViewModelBuilder.buildViewModel.returns(@plans = {'plans': 'mock'})
  208. @res.render = (view, @data) =>
  209. expect(view).to.equal 'subscriptions/dashboard'
  210. done()
  211. @SubscriptionController.userSubscriptionPage @req, @res
  212. it "should load the personal, groups and v1 subscriptions", ->
  213. expect(@data.personalSubscription).to.deep.equal @personalSubscription
  214. expect(@data.memberGroupSubscriptions).to.deep.equal @memberGroupSubscriptions
  215. expect(@data.v1Subscriptions).to.deep.equal @v1Subscriptions
  216. it "should load the user", ->
  217. expect(@data.user).to.deep.equal @user
  218. it "should load the plans", ->
  219. expect(@data.plans).to.deep.equal @plans
  220. describe "createSubscription", ->
  221. beforeEach (done)->
  222. @res =
  223. sendStatus:->
  224. done()
  225. sinon.spy @res, "sendStatus"
  226. @subscriptionDetails =
  227. card:"1234"
  228. cvv:"123"
  229. @req.body.recurly_token_id = "1234"
  230. @req.body.subscriptionDetails = @subscriptionDetails
  231. @LimitationsManager.userHasV1OrV2Subscription.yields(null, false)
  232. @SubscriptionController.createSubscription @req, @res
  233. it "should send the user and subscriptionId to the handler", (done)->
  234. @SubscriptionHandler.createSubscription.calledWith(@user, @subscriptionDetails, @req.body.recurly_token_id).should.equal true
  235. done()
  236. it "should redurect to the subscription page", (done)->
  237. @res.sendStatus.calledWith(201).should.equal true
  238. done()
  239. describe "updateSubscription via post", ->
  240. beforeEach (done)->
  241. @res =
  242. redirect:->
  243. done()
  244. sinon.spy @res, "redirect"
  245. @plan_code = "1234"
  246. @req.body.plan_code = @plan_code
  247. @SubscriptionController.updateSubscription @req, @res
  248. it "should send the user and subscriptionId to the handler", (done)->
  249. @SubscriptionHandler.updateSubscription.calledWith(@user, @plan_code).should.equal true
  250. done()
  251. it "should redurect to the subscription page", (done)->
  252. @res.redirect.calledWith("/user/subscription").should.equal true
  253. done()
  254. describe "reactivateSubscription", ->
  255. beforeEach (done)->
  256. @res =
  257. redirect:->
  258. done()
  259. sinon.spy @res, "redirect"
  260. @SubscriptionController.reactivateSubscription @req, @res
  261. it "should tell the handler to reactivate this user", (done)->
  262. @SubscriptionHandler.reactivateSubscription.calledWith(@user).should.equal true
  263. done()
  264. it "should redurect to the subscription page", (done)->
  265. @res.redirect.calledWith("/user/subscription").should.equal true
  266. done()
  267. describe "cancelSubscription", ->
  268. beforeEach (done)->
  269. @res =
  270. redirect:->
  271. done()
  272. sinon.spy @res, "redirect"
  273. @SubscriptionController.cancelSubscription @req, @res
  274. it "should tell the handler to cancel this user", (done)->
  275. @SubscriptionHandler.cancelSubscription.calledWith(@user).should.equal true
  276. done()
  277. it "should redurect to the subscription page", (done)->
  278. @res.redirect.calledWith("/user/subscription").should.equal true
  279. done()
  280. describe "recurly callback", ->
  281. describe "with a actionable request", ->
  282. beforeEach (done)->
  283. @req =
  284. body:
  285. expired_subscription_notification:
  286. subscription:
  287. uuid: @activeRecurlySubscription.uuid
  288. @res = sendStatus:->
  289. done()
  290. sinon.spy @res, "sendStatus"
  291. @SubscriptionController.recurlyCallback @req, @res
  292. it "should tell the SubscriptionHandler to process the recurly callback", (done)->
  293. @SubscriptionHandler.recurlyCallback.called.should.equal true
  294. done()
  295. it "should send a 200", (done)->
  296. @res.sendStatus.calledWith(200)
  297. done()
  298. describe "with a non-actionable request", ->
  299. beforeEach (done) ->
  300. @user.id = @activeRecurlySubscription.account.account_code
  301. @req =
  302. body:
  303. new_subscription_notification:
  304. subscription:
  305. uuid: @activeRecurlySubscription.uuid
  306. @res = sendStatus:->
  307. done()
  308. sinon.spy @res, "sendStatus"
  309. @SubscriptionController.recurlyCallback @req, @res
  310. it "should not call the subscriptionshandler", ->
  311. @SubscriptionHandler.recurlyCallback.called.should.equal false
  312. it "should respond with a 200 status", ->
  313. @res.sendStatus.calledWith(200)
  314. describe "renderUpgradeToAnnualPlanPage", ->
  315. it "should redirect to the plans page if the user does not have a subscription", (done)->
  316. @LimitationsManager.userHasV2Subscription.callsArgWith(1, null, false)
  317. @res.redirect = (url)->
  318. url.should.equal "/user/subscription/plans"
  319. done()
  320. @SubscriptionController.renderUpgradeToAnnualPlanPage @req, @res
  321. it "should pass the plan code to the view - student", (done)->
  322. @LimitationsManager.userHasV2Subscription.callsArgWith(1, null, true, {planCode:"Student free trial 14 days"})
  323. @res.render = (view, opts)->
  324. view.should.equal "subscriptions/upgradeToAnnual"
  325. opts.planName.should.equal "student"
  326. done()
  327. @SubscriptionController.renderUpgradeToAnnualPlanPage @req, @res
  328. it "should pass the plan code to the view - collaborator", (done)->
  329. @LimitationsManager.userHasV2Subscription.callsArgWith(1, null, true, {planCode:"free trial for Collaborator free trial 14 days"})
  330. @res.render = (view, opts)->
  331. opts.planName.should.equal "collaborator"
  332. done()
  333. @SubscriptionController.renderUpgradeToAnnualPlanPage @req, @res
  334. it "should pass annual as the plan name if the user is already on an annual plan", (done)->
  335. @LimitationsManager.userHasV2Subscription.callsArgWith(1, null, true, {planCode:"student annual with free trial"})
  336. @res.render = (view, opts)->
  337. opts.planName.should.equal "annual"
  338. done()
  339. @SubscriptionController.renderUpgradeToAnnualPlanPage @req, @res
  340. describe "processUpgradeToAnnualPlan", ->
  341. beforeEach ->
  342. it "should tell the subscription handler to update the subscription with the annual plan and apply a coupon code", (done)->
  343. @req.body =
  344. planName:"student"
  345. @res.sendStatus = ()=>
  346. @SubscriptionHandler.updateSubscription.calledWith(@user, "student-annual", "STUDENTCODEHERE").should.equal true
  347. done()
  348. @SubscriptionController.processUpgradeToAnnualPlan @req, @res
  349. it "should get the collaborator coupon code", (done)->
  350. @req.body =
  351. planName:"collaborator"
  352. @res.sendStatus = (url)=>
  353. @SubscriptionHandler.updateSubscription.calledWith(@user, "collaborator-annual", "COLLABORATORCODEHERE").should.equal true
  354. done()
  355. @SubscriptionController.processUpgradeToAnnualPlan @req, @res