SubscriptionControllerTests.coffee 16 KB

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