SubscriptionControllerTests.js 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787
  1. /* eslint-disable
  2. max-len,
  3. mocha/handle-done-callback,
  4. no-return-assign,
  5. no-unused-vars,
  6. */
  7. // TODO: This file was created by bulk-decaffeinate.
  8. // Fix any style issues and re-enable lint.
  9. /*
  10. * decaffeinate suggestions:
  11. * DS102: Remove unnecessary code created because of implicit returns
  12. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  13. */
  14. const SandboxedModule = require('sandboxed-module')
  15. const sinon = require('sinon')
  16. const { expect } = require('chai')
  17. const MockRequest = require('../helpers/MockRequest')
  18. const MockResponse = require('../helpers/MockResponse')
  19. const modulePath =
  20. '../../../../app/src/Features/Subscription/SubscriptionController'
  21. const Errors = require('../../../../app/src/Features/Errors/Errors')
  22. const SubscriptionErrors = require('../../../../app/src/Features/Subscription/Errors')
  23. const mockSubscriptions = {
  24. 'subscription-123-active': {
  25. uuid: 'subscription-123-active',
  26. plan: {
  27. name: 'Gold',
  28. plan_code: 'gold',
  29. },
  30. current_period_ends_at: new Date(),
  31. state: 'active',
  32. unit_amount_in_cents: 999,
  33. account: {
  34. account_code: 'user-123',
  35. },
  36. },
  37. }
  38. describe('SubscriptionController', function () {
  39. beforeEach(function () {
  40. this.user = {
  41. email: 'tom@yahoo.com',
  42. _id: 'one',
  43. signUpDate: new Date('2000-10-01'),
  44. }
  45. this.activeRecurlySubscription =
  46. mockSubscriptions['subscription-123-active']
  47. this.AuthenticationController = {
  48. getLoggedInUser: sinon.stub().callsArgWith(1, null, this.user),
  49. getLoggedInUserId: sinon.stub().returns(this.user._id),
  50. getSessionUser: sinon.stub().returns(this.user),
  51. isUserLoggedIn: sinon.stub().returns(true),
  52. }
  53. this.SubscriptionHandler = {
  54. createSubscription: sinon.stub().callsArgWith(3),
  55. updateSubscription: sinon.stub().callsArgWith(3),
  56. reactivateSubscription: sinon.stub().callsArgWith(1),
  57. cancelSubscription: sinon.stub().callsArgWith(1),
  58. syncSubscription: sinon.stub().yields(),
  59. attemptPaypalInvoiceCollection: sinon.stub().yields(),
  60. startFreeTrial: sinon.stub(),
  61. promises: {
  62. createSubscription: sinon.stub().resolves(),
  63. updateSubscription: sinon.stub().resolves(),
  64. reactivateSubscription: sinon.stub().resolves(),
  65. cancelSubscription: sinon.stub().resolves(),
  66. syncSubscription: sinon.stub().resolves(),
  67. attemptPaypalInvoiceCollection: sinon.stub().resolves(),
  68. startFreeTrial: sinon.stub().resolves(),
  69. },
  70. }
  71. this.PlansLocator = { findLocalPlanInSettings: sinon.stub() }
  72. this.LimitationsManager = {
  73. hasPaidSubscription: sinon.stub(),
  74. userHasV1OrV2Subscription: sinon.stub(),
  75. userHasV2Subscription: sinon.stub(),
  76. promises: {
  77. hasPaidSubscription: sinon.stub().resolves(),
  78. userHasV1OrV2Subscription: sinon.stub().resolves(),
  79. userHasV2Subscription: sinon.stub().resolves(),
  80. },
  81. }
  82. this.SubscriptionViewModelBuilder = {
  83. buildUsersSubscriptionViewModel: sinon.stub().callsArgWith(1, null, {}),
  84. buildPlansList: sinon.stub(),
  85. promises: {
  86. buildUsersSubscriptionViewModel: sinon.stub().resolves({}),
  87. },
  88. }
  89. this.settings = {
  90. coupon_codes: {
  91. upgradeToAnnualPromo: {
  92. student: 'STUDENTCODEHERE',
  93. collaborator: 'COLLABORATORCODEHERE',
  94. },
  95. },
  96. apis: {
  97. recurly: {
  98. subdomain: 'sl',
  99. },
  100. },
  101. siteUrl: 'http://de.sharelatex.dev:3000',
  102. gaExperiments: {},
  103. }
  104. this.GeoIpLookup = {
  105. getCurrencyCode: sinon.stub(),
  106. promises: {
  107. getCurrencyCode: sinon.stub(),
  108. },
  109. }
  110. this.UserGetter = {
  111. getUser: sinon.stub().callsArgWith(2, null, this.user),
  112. promises: {
  113. getUser: sinon.stub().resolves(this.user),
  114. },
  115. }
  116. this.SubscriptionController = SandboxedModule.require(modulePath, {
  117. requires: {
  118. '../Authentication/AuthenticationController': this
  119. .AuthenticationController,
  120. './SubscriptionHandler': this.SubscriptionHandler,
  121. './PlansLocator': this.PlansLocator,
  122. './SubscriptionViewModelBuilder': this.SubscriptionViewModelBuilder,
  123. './LimitationsManager': this.LimitationsManager,
  124. '../../infrastructure/GeoIpLookup': this.GeoIpLookup,
  125. 'settings-sharelatex': this.settings,
  126. '../User/UserGetter': this.UserGetter,
  127. './RecurlyWrapper': (this.RecurlyWrapper = {
  128. updateAccountEmailAddress: sinon.stub().yields(),
  129. }),
  130. './FeaturesUpdater': (this.FeaturesUpdater = {}),
  131. './GroupPlansData': (this.GroupPlansData = {}),
  132. './V1SubscriptionManager': (this.V1SubscriptionManager = {}),
  133. '../Errors/HttpErrorHandler': (this.HttpErrorHandler = {
  134. unprocessableEntity: sinon.stub(),
  135. }),
  136. './Errors': SubscriptionErrors,
  137. '../Analytics/AnalyticsManager': (this.AnalyticsManager = {
  138. recordEvent: sinon.stub(),
  139. setUserProperty: sinon.stub(),
  140. }),
  141. '../SplitTests/SplitTestHandler': (this.SplitTestHandler = {
  142. getTestSegmentation: () => {},
  143. }),
  144. },
  145. })
  146. this.res = new MockResponse()
  147. this.req = new MockRequest()
  148. this.req.body = {}
  149. this.req.query = { planCode: '123123' }
  150. return (this.stubbedCurrencyCode = 'GBP')
  151. })
  152. describe('plansPage', function () {
  153. beforeEach(function () {
  154. this.req.ip = '1234.3123.3131.333 313.133.445.666 653.5345.5345.534'
  155. return this.GeoIpLookup.promises.getCurrencyCode.resolves(
  156. this.stubbedCurrencyCode
  157. )
  158. })
  159. })
  160. describe('paymentPage', function () {
  161. beforeEach(function () {
  162. this.req.headers = {}
  163. this.SubscriptionHandler.promises.validateNoSubscriptionInRecurly = sinon
  164. .stub()
  165. .resolves(true)
  166. return this.GeoIpLookup.promises.getCurrencyCode.resolves({
  167. recomendedCurrency: this.stubbedCurrencyCode,
  168. })
  169. })
  170. describe('with a user without a subscription', function () {
  171. beforeEach(function () {
  172. this.LimitationsManager.promises.userHasV1OrV2Subscription.resolves(
  173. false
  174. )
  175. return this.PlansLocator.findLocalPlanInSettings.returns({})
  176. })
  177. describe('with a valid plan code', function () {
  178. it('should render the new subscription page', function (done) {
  179. this.res.render = (page, opts) => {
  180. page.should.equal('subscriptions/new')
  181. done()
  182. }
  183. this.SubscriptionController.paymentPage(this.req, this.res)
  184. })
  185. })
  186. })
  187. describe('with a user with subscription', function () {
  188. it('should redirect to the subscription dashboard', function (done) {
  189. this.PlansLocator.findLocalPlanInSettings.returns({})
  190. this.LimitationsManager.promises.userHasV1OrV2Subscription.resolves(
  191. true
  192. )
  193. this.res.redirect = url => {
  194. url.should.equal('/user/subscription?hasSubscription=true')
  195. return done()
  196. }
  197. return this.SubscriptionController.paymentPage(this.req, this.res)
  198. })
  199. })
  200. describe('with an invalid plan code', function () {
  201. it('should return 422 error - Unprocessable Entity', function (done) {
  202. this.LimitationsManager.promises.userHasV1OrV2Subscription.resolves(
  203. false
  204. )
  205. this.PlansLocator.findLocalPlanInSettings.returns(null)
  206. this.HttpErrorHandler.unprocessableEntity = sinon.spy(
  207. (req, res, message) => {
  208. expect(req).to.exist
  209. expect(res).to.exist
  210. expect(message).to.deep.equal('Plan not found')
  211. done()
  212. }
  213. )
  214. return this.SubscriptionController.paymentPage(this.req, this.res)
  215. })
  216. })
  217. describe('which currency to use', function () {
  218. beforeEach(function () {
  219. this.LimitationsManager.promises.userHasV1OrV2Subscription.resolves(
  220. false
  221. )
  222. return this.PlansLocator.findLocalPlanInSettings.returns({})
  223. })
  224. it('should use the set currency from the query string', function (done) {
  225. this.req.query.currency = 'EUR'
  226. this.res.render = (page, opts) => {
  227. opts.currency.should.equal('EUR')
  228. opts.currency.should.not.equal(this.stubbedCurrencyCode)
  229. return done()
  230. }
  231. return this.SubscriptionController.paymentPage(this.req, this.res)
  232. })
  233. it('should upercase the currency code', function (done) {
  234. this.req.query.currency = 'eur'
  235. this.res.render = (page, opts) => {
  236. opts.currency.should.equal('EUR')
  237. return done()
  238. }
  239. return this.SubscriptionController.paymentPage(this.req, this.res)
  240. })
  241. it('should use the geo ip currency if non is provided', function (done) {
  242. this.req.query.currency = null
  243. this.res.render = (page, opts) => {
  244. opts.currency.should.equal(this.stubbedCurrencyCode)
  245. done()
  246. }
  247. this.SubscriptionController.paymentPage(this.req, this.res)
  248. })
  249. })
  250. describe('with a recurly subscription already', function () {
  251. it('should redirect to the subscription dashboard', function (done) {
  252. this.PlansLocator.findLocalPlanInSettings.returns({})
  253. this.LimitationsManager.promises.userHasV1OrV2Subscription.resolves(
  254. false
  255. )
  256. this.SubscriptionHandler.promises.validateNoSubscriptionInRecurly.resolves(
  257. false
  258. )
  259. this.res.redirect = url => {
  260. url.should.equal('/user/subscription?hasSubscription=true')
  261. return done()
  262. }
  263. return this.SubscriptionController.paymentPage(this.req, this.res)
  264. })
  265. })
  266. })
  267. describe('successfulSubscription', function () {
  268. beforeEach(function (done) {
  269. this.SubscriptionViewModelBuilder.buildUsersSubscriptionViewModel.callsArgWith(
  270. 1,
  271. null,
  272. {}
  273. )
  274. this.res.callback = done
  275. return this.SubscriptionController.successfulSubscription(
  276. this.req,
  277. this.res
  278. )
  279. })
  280. })
  281. describe('userSubscriptionPage', function () {
  282. beforeEach(function (done) {
  283. this.SubscriptionViewModelBuilder.promises.buildUsersSubscriptionViewModel.resolves(
  284. {
  285. personalSubscription: (this.personalSubscription = {
  286. 'personal-subscription': 'mock',
  287. }),
  288. memberGroupSubscriptions: (this.memberGroupSubscriptions = {
  289. 'group-subscriptions': 'mock',
  290. }),
  291. }
  292. )
  293. this.SubscriptionViewModelBuilder.buildPlansList.returns(
  294. (this.plans = { plans: 'mock' })
  295. )
  296. this.LimitationsManager.promises.userHasV1OrV2Subscription.resolves(false)
  297. this.res.render = (view, data) => {
  298. this.data = data
  299. expect(view).to.equal('subscriptions/dashboard')
  300. done()
  301. }
  302. this.SubscriptionController.userSubscriptionPage(this.req, this.res)
  303. })
  304. it('should load the personal, groups and v1 subscriptions', function () {
  305. expect(this.data.personalSubscription).to.deep.equal(
  306. this.personalSubscription
  307. )
  308. return expect(this.data.memberGroupSubscriptions).to.deep.equal(
  309. this.memberGroupSubscriptions
  310. )
  311. })
  312. it('should load the user', function () {
  313. return expect(this.data.user).to.deep.equal(this.user)
  314. })
  315. it('should load the plans', function () {
  316. return expect(this.data.plans).to.deep.equal(this.plans)
  317. })
  318. })
  319. describe('createSubscription', function () {
  320. beforeEach(function (done) {
  321. this.res = {
  322. sendStatus() {
  323. return done()
  324. },
  325. }
  326. sinon.spy(this.res, 'sendStatus')
  327. this.subscriptionDetails = {
  328. card: '1234',
  329. cvv: '123',
  330. }
  331. this.recurlyTokenIds = {
  332. billing: '1234',
  333. threeDSecureActionResult: '5678',
  334. }
  335. this.req.body.recurly_token_id = this.recurlyTokenIds.billing
  336. this.req.body.recurly_three_d_secure_action_result_token_id = this.recurlyTokenIds.threeDSecureActionResult
  337. this.req.body.subscriptionDetails = this.subscriptionDetails
  338. this.LimitationsManager.userHasV1OrV2Subscription.yields(null, false)
  339. return this.SubscriptionController.createSubscription(this.req, this.res)
  340. })
  341. it('should send the user and subscriptionId to the handler', function (done) {
  342. this.SubscriptionHandler.createSubscription
  343. .calledWithMatch(
  344. this.user,
  345. this.subscriptionDetails,
  346. this.recurlyTokenIds
  347. )
  348. .should.equal(true)
  349. return done()
  350. })
  351. it('should redurect to the subscription page', function (done) {
  352. this.res.sendStatus.calledWith(201).should.equal(true)
  353. return done()
  354. })
  355. })
  356. describe('createSubscription with errors', function () {
  357. it('should handle users with subscription', function (done) {
  358. this.LimitationsManager.userHasV1OrV2Subscription.yields(null, true)
  359. this.SubscriptionController.createSubscription(this.req, {
  360. sendStatus: status => {
  361. expect(status).to.equal(409)
  362. this.SubscriptionHandler.createSubscription.called.should.equal(false)
  363. done()
  364. },
  365. })
  366. })
  367. it('should handle 3DSecure errors', function (done) {
  368. this.next = sinon.stub()
  369. this.LimitationsManager.userHasV1OrV2Subscription.yields(null, false)
  370. this.SubscriptionHandler.createSubscription.yields(
  371. new SubscriptionErrors.RecurlyTransactionError({})
  372. )
  373. this.HttpErrorHandler.unprocessableEntity = sinon.spy(
  374. (req, res, message) => {
  375. expect(req).to.exist
  376. expect(res).to.exist
  377. expect(message).to.deep.equal('Unknown transaction error')
  378. done()
  379. }
  380. )
  381. this.SubscriptionController.createSubscription(this.req, this.res)
  382. })
  383. it('should handle validation errors', function (done) {
  384. this.next = sinon.stub()
  385. this.LimitationsManager.userHasV1OrV2Subscription.yields(null, false)
  386. this.SubscriptionHandler.createSubscription.yields(
  387. new Errors.InvalidError('invalid error test')
  388. )
  389. this.HttpErrorHandler.unprocessableEntity = sinon.spy(
  390. (req, res, message) => {
  391. expect(req).to.exist
  392. expect(res).to.exist
  393. expect(message).to.deep.equal('invalid error test')
  394. done()
  395. }
  396. )
  397. this.SubscriptionController.createSubscription(this.req, this.res)
  398. })
  399. it('should handle recurly errors', function (done) {
  400. this.LimitationsManager.userHasV1OrV2Subscription.yields(null, false)
  401. this.SubscriptionHandler.createSubscription.yields(
  402. new SubscriptionErrors.RecurlyTransactionError({})
  403. )
  404. this.HttpErrorHandler.unprocessableEntity = sinon.spy(
  405. (req, res, info) => {
  406. expect(req).to.exist
  407. expect(res).to.exist
  408. expect(info).to.deep.equal('Unknown transaction error')
  409. done()
  410. }
  411. )
  412. return this.SubscriptionController.createSubscription(this.req, this.res)
  413. })
  414. it('should handle invalid error', function (done) {
  415. this.LimitationsManager.userHasV1OrV2Subscription.yields(null, false)
  416. this.SubscriptionHandler.createSubscription.yields(
  417. new Errors.InvalidError({})
  418. )
  419. this.HttpErrorHandler.unprocessableEntity = sinon.spy((req, res) => {
  420. expect(req).to.exist
  421. expect(res).to.exist
  422. done()
  423. })
  424. return this.SubscriptionController.createSubscription(this.req, this.res)
  425. })
  426. })
  427. describe('updateSubscription via post', function () {
  428. beforeEach(function (done) {
  429. this.res = {
  430. redirect() {
  431. return done()
  432. },
  433. }
  434. sinon.spy(this.res, 'redirect')
  435. this.plan_code = '1234'
  436. this.req.body.plan_code = this.plan_code
  437. return this.SubscriptionController.updateSubscription(this.req, this.res)
  438. })
  439. it('should send the user and subscriptionId to the handler', function (done) {
  440. this.SubscriptionHandler.updateSubscription
  441. .calledWith(this.user, this.plan_code)
  442. .should.equal(true)
  443. return done()
  444. })
  445. it('should redurect to the subscription page', function (done) {
  446. this.res.redirect.calledWith('/user/subscription').should.equal(true)
  447. return done()
  448. })
  449. })
  450. describe('updateAccountEmailAddress via put', function () {
  451. it('should send the user and subscriptionId to RecurlyWrapper', function () {
  452. this.res.sendStatus = sinon.spy()
  453. this.SubscriptionController.updateAccountEmailAddress(this.req, this.res)
  454. this.RecurlyWrapper.updateAccountEmailAddress
  455. .calledWith(this.user._id, this.user.email)
  456. .should.equal(true)
  457. })
  458. it('should respond with 200', function () {
  459. this.res.sendStatus = sinon.spy()
  460. this.SubscriptionController.updateAccountEmailAddress(this.req, this.res)
  461. this.res.sendStatus.calledWith(200).should.equal(true)
  462. })
  463. it('should send the error to the next handler when updating recurly account email fails', function (done) {
  464. this.RecurlyWrapper.updateAccountEmailAddress.yields(new Error())
  465. this.next = sinon.spy(error => {
  466. expect(error).instanceOf(Error)
  467. done()
  468. })
  469. this.SubscriptionController.updateAccountEmailAddress(
  470. this.req,
  471. this.res,
  472. this.next
  473. )
  474. })
  475. })
  476. describe('reactivateSubscription', function () {
  477. beforeEach(function (done) {
  478. this.res = {
  479. redirect() {
  480. return done()
  481. },
  482. }
  483. sinon.spy(this.res, 'redirect')
  484. return this.SubscriptionController.reactivateSubscription(
  485. this.req,
  486. this.res
  487. )
  488. })
  489. it('should tell the handler to reactivate this user', function (done) {
  490. this.SubscriptionHandler.reactivateSubscription
  491. .calledWith(this.user)
  492. .should.equal(true)
  493. return done()
  494. })
  495. it('should redurect to the subscription page', function (done) {
  496. this.res.redirect.calledWith('/user/subscription').should.equal(true)
  497. return done()
  498. })
  499. })
  500. describe('cancelSubscription', function () {
  501. beforeEach(function (done) {
  502. this.res = {
  503. redirect() {
  504. return done()
  505. },
  506. }
  507. sinon.spy(this.res, 'redirect')
  508. return this.SubscriptionController.cancelSubscription(this.req, this.res)
  509. })
  510. it('should tell the handler to cancel this user', function (done) {
  511. this.SubscriptionHandler.cancelSubscription
  512. .calledWith(this.user)
  513. .should.equal(true)
  514. return done()
  515. })
  516. it('should redurect to the subscription page', function (done) {
  517. this.res.redirect
  518. .calledWith('/user/subscription/canceled')
  519. .should.equal(true)
  520. return done()
  521. })
  522. })
  523. describe('recurly callback', function () {
  524. describe('with a sync subscription request', function () {
  525. beforeEach(function (done) {
  526. this.req = {
  527. body: {
  528. expired_subscription_notification: {
  529. account: {
  530. account_code: this.user._id,
  531. },
  532. subscription: {
  533. uuid: this.activeRecurlySubscription.uuid,
  534. plan: {
  535. plan_code: 'collaborator',
  536. state: 'active',
  537. },
  538. },
  539. },
  540. },
  541. }
  542. this.res = {
  543. sendStatus() {
  544. return done()
  545. },
  546. }
  547. sinon.spy(this.res, 'sendStatus')
  548. return this.SubscriptionController.recurlyCallback(this.req, this.res)
  549. })
  550. it('should tell the SubscriptionHandler to process the recurly callback', function (done) {
  551. this.SubscriptionHandler.syncSubscription.called.should.equal(true)
  552. return done()
  553. })
  554. it('should send a 200', function (done) {
  555. this.res.sendStatus.calledWith(200)
  556. return done()
  557. })
  558. })
  559. describe('with a billing info updated request', function () {
  560. beforeEach(function (done) {
  561. this.req = {
  562. body: {
  563. billing_info_updated_notification: {
  564. account: {
  565. account_code: 'mock-account-code',
  566. },
  567. },
  568. },
  569. }
  570. this.res = {
  571. sendStatus() {
  572. done()
  573. },
  574. }
  575. sinon.spy(this.res, 'sendStatus')
  576. this.SubscriptionController.recurlyCallback(this.req, this.res)
  577. })
  578. it('should call attemptPaypalInvoiceCollection', function (done) {
  579. this.SubscriptionHandler.attemptPaypalInvoiceCollection
  580. .calledWith('mock-account-code')
  581. .should.equal(true)
  582. done()
  583. })
  584. it('should send a 200', function (done) {
  585. this.res.sendStatus.calledWith(200)
  586. done()
  587. })
  588. })
  589. describe('with a non-actionable request', function () {
  590. beforeEach(function (done) {
  591. this.user.id = this.activeRecurlySubscription.account.account_code
  592. this.req = {
  593. body: {
  594. renewed_subscription_notification: {
  595. account: {
  596. account_code: this.user._id,
  597. },
  598. subscription: {
  599. uuid: this.activeRecurlySubscription.uuid,
  600. plan: {
  601. plan_code: 'collaborator',
  602. state: 'active',
  603. },
  604. },
  605. },
  606. },
  607. }
  608. this.res = {
  609. sendStatus() {
  610. return done()
  611. },
  612. }
  613. sinon.spy(this.res, 'sendStatus')
  614. return this.SubscriptionController.recurlyCallback(this.req, this.res)
  615. })
  616. it('should not call the subscriptionshandler', function () {
  617. this.SubscriptionHandler.syncSubscription.called.should.equal(false)
  618. this.SubscriptionHandler.attemptPaypalInvoiceCollection.called.should.equal(
  619. false
  620. )
  621. })
  622. it('should respond with a 200 status', function () {
  623. return this.res.sendStatus.calledWith(200)
  624. })
  625. })
  626. })
  627. describe('renderUpgradeToAnnualPlanPage', function () {
  628. it('should redirect to the plans page if the user does not have a subscription', function (done) {
  629. this.LimitationsManager.userHasV2Subscription.callsArgWith(1, null, false)
  630. this.res.redirect = function (url) {
  631. url.should.equal('/user/subscription/plans')
  632. return done()
  633. }
  634. return this.SubscriptionController.renderUpgradeToAnnualPlanPage(
  635. this.req,
  636. this.res
  637. )
  638. })
  639. it('should pass the plan code to the view - student', function (done) {
  640. this.LimitationsManager.userHasV2Subscription.callsArgWith(
  641. 1,
  642. null,
  643. true,
  644. { planCode: 'Student free trial 14 days' }
  645. )
  646. this.res.render = function (view, opts) {
  647. view.should.equal('subscriptions/upgradeToAnnual')
  648. opts.planName.should.equal('student')
  649. return done()
  650. }
  651. return this.SubscriptionController.renderUpgradeToAnnualPlanPage(
  652. this.req,
  653. this.res
  654. )
  655. })
  656. it('should pass the plan code to the view - collaborator', function (done) {
  657. this.LimitationsManager.userHasV2Subscription.callsArgWith(
  658. 1,
  659. null,
  660. true,
  661. { planCode: 'free trial for Collaborator free trial 14 days' }
  662. )
  663. this.res.render = function (view, opts) {
  664. opts.planName.should.equal('collaborator')
  665. return done()
  666. }
  667. return this.SubscriptionController.renderUpgradeToAnnualPlanPage(
  668. this.req,
  669. this.res
  670. )
  671. })
  672. it('should pass annual as the plan name if the user is already on an annual plan', function (done) {
  673. this.LimitationsManager.userHasV2Subscription.callsArgWith(
  674. 1,
  675. null,
  676. true,
  677. { planCode: 'student annual with free trial' }
  678. )
  679. this.res.render = function (view, opts) {
  680. opts.planName.should.equal('annual')
  681. return done()
  682. }
  683. return this.SubscriptionController.renderUpgradeToAnnualPlanPage(
  684. this.req,
  685. this.res
  686. )
  687. })
  688. })
  689. describe('processUpgradeToAnnualPlan', function () {
  690. beforeEach(function () {})
  691. it('should tell the subscription handler to update the subscription with the annual plan and apply a coupon code', function (done) {
  692. this.req.body = { planName: 'student' }
  693. this.res.sendStatus = () => {
  694. this.SubscriptionHandler.updateSubscription
  695. .calledWith(this.user, 'student-annual', 'STUDENTCODEHERE')
  696. .should.equal(true)
  697. return done()
  698. }
  699. return this.SubscriptionController.processUpgradeToAnnualPlan(
  700. this.req,
  701. this.res
  702. )
  703. })
  704. it('should get the collaborator coupon code', function (done) {
  705. this.req.body = { planName: 'collaborator' }
  706. this.res.sendStatus = url => {
  707. this.SubscriptionHandler.updateSubscription
  708. .calledWith(this.user, 'collaborator-annual', 'COLLABORATORCODEHERE')
  709. .should.equal(true)
  710. return done()
  711. }
  712. return this.SubscriptionController.processUpgradeToAnnualPlan(
  713. this.req,
  714. this.res
  715. )
  716. })
  717. })
  718. })