SubscriptionControllerTests.coffee 16 KB

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