SubscriptionDashboardTests.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616
  1. /* eslint-disable
  2. handle-callback-err,
  3. max-len,
  4. */
  5. // TODO: This file was created by bulk-decaffeinate.
  6. // Fix any style issues and re-enable lint.
  7. /*
  8. * decaffeinate suggestions:
  9. * DS102: Remove unnecessary code created because of implicit returns
  10. * DS207: Consider shorter variations of null checks
  11. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  12. */
  13. const { expect } = require('chai')
  14. const async = require('async')
  15. const User = require('./helpers/User')
  16. const { Subscription } = require('../../../app/src/models/Subscription')
  17. const { Institution } = require('../../../app/src/models/Institution')
  18. const SubscriptionViewModelBuilder = require('../../../app/src/Features/Subscription/SubscriptionViewModelBuilder')
  19. const RecurlySubscription = require('./helpers/RecurlySubscription')
  20. const MockRecurlyApi = require('./helpers/MockRecurlyApi')
  21. const MockV1Api = require('./helpers/MockV1Api')
  22. describe('Subscriptions', function() {
  23. describe('dashboard', function() {
  24. beforeEach(function(done) {
  25. this.user = new User()
  26. return this.user.ensureUserExists(done)
  27. })
  28. it('should not list personal plan', function() {
  29. const plans = SubscriptionViewModelBuilder.buildPlansList()
  30. expect(plans.individualMonthlyPlans).to.be.a('Array')
  31. const personalMonthlyPlan = plans.individualMonthlyPlans.find(
  32. plan => plan.planCode === 'personal'
  33. )
  34. expect(personalMonthlyPlan).to.be.undefined
  35. })
  36. describe('when the user has no subscription', function() {
  37. beforeEach(function(done) {
  38. return SubscriptionViewModelBuilder.buildUsersSubscriptionViewModel(
  39. this.user,
  40. (error, data) => {
  41. this.data = data
  42. if (error != null) {
  43. return done(error)
  44. }
  45. return done()
  46. }
  47. )
  48. })
  49. it('should return no personalSubscription', function() {
  50. return expect(this.data.personalSubscription).to.equal(null)
  51. })
  52. it('should return no memberGroupSubscriptions', function() {
  53. return expect(this.data.memberGroupSubscriptions).to.deep.equal([])
  54. })
  55. })
  56. describe('when the user has a subscription with recurly', function() {
  57. beforeEach(function(done) {
  58. this.recurlySubscription = new RecurlySubscription({
  59. adminId: this.user._id,
  60. planCode: 'collaborator',
  61. tax_in_cents: 100,
  62. tax_rate: 0.2,
  63. unit_amount_in_cents: 500,
  64. currency: 'GBP',
  65. current_period_ends_at: new Date(2018, 4, 5),
  66. state: 'active',
  67. trial_ends_at: new Date(2018, 6, 7),
  68. account: {
  69. hosted_login_token: 'mock-login-token',
  70. email: 'mock@email.com'
  71. }
  72. })
  73. MockRecurlyApi.coupons = this.coupons = {
  74. 'test-coupon-1': { description: 'Test Coupon 1' },
  75. 'test-coupon-2': { description: 'Test Coupon 2' },
  76. 'test-coupon-3': { name: 'TestCoupon3' }
  77. }
  78. this.recurlySubscription.ensureExists(error => {
  79. if (error != null) {
  80. return done(error)
  81. }
  82. return SubscriptionViewModelBuilder.buildUsersSubscriptionViewModel(
  83. this.user,
  84. (error, data) => {
  85. this.data = data
  86. if (error != null) {
  87. return done(error)
  88. }
  89. return done()
  90. }
  91. )
  92. })
  93. })
  94. after(function(done) {
  95. MockRecurlyApi.mockSubscriptions = []
  96. MockRecurlyApi.coupons = {}
  97. MockRecurlyApi.redemptions = {}
  98. Subscription.remove(
  99. {
  100. admin_id: this.user._id
  101. },
  102. done
  103. )
  104. })
  105. it('should return a personalSubscription with populated recurly data', function() {
  106. const subscription = this.data.personalSubscription
  107. expect(subscription).to.exist
  108. expect(subscription.planCode).to.equal('collaborator')
  109. expect(subscription.recurly).to.exist
  110. return expect(subscription.recurly).to.deep.equal({
  111. activeCoupons: [],
  112. billingDetailsLink:
  113. 'https://test.recurly.com/account/billing_info/edit?ht=mock-login-token',
  114. accountManagementLink:
  115. 'https://test.recurly.com/account/mock-login-token',
  116. currency: 'GBP',
  117. nextPaymentDueAt: '5th May 2018',
  118. price: '£6.00',
  119. state: 'active',
  120. tax: 100,
  121. taxRate: 0.2,
  122. trial_ends_at: new Date(2018, 6, 7),
  123. trialEndsAtFormatted: '7th July 2018',
  124. account: {
  125. account_code: this.user._id,
  126. email: 'mock@email.com',
  127. hosted_login_token: 'mock-login-token'
  128. }
  129. })
  130. })
  131. it('should return no memberGroupSubscriptions', function() {
  132. return expect(this.data.memberGroupSubscriptions).to.deep.equal([])
  133. })
  134. it('should include redeemed coupons', function(done) {
  135. MockRecurlyApi.redemptions[this.user._id] = [
  136. { state: 'active', coupon_code: 'test-coupon-1' },
  137. { state: 'inactive', coupon_code: 'test-coupon-2' },
  138. { state: 'active', coupon_code: 'test-coupon-3' }
  139. ]
  140. // rebuild the view model with the redemptions
  141. return SubscriptionViewModelBuilder.buildUsersSubscriptionViewModel(
  142. this.user,
  143. (error, data) => {
  144. expect(error).to.not.exist
  145. expect(
  146. data.personalSubscription.recurly.activeCoupons
  147. ).to.deep.equal([
  148. {
  149. coupon_code: 'test-coupon-1',
  150. name: '',
  151. description: 'Test Coupon 1'
  152. },
  153. {
  154. coupon_code: 'test-coupon-3',
  155. name: 'TestCoupon3',
  156. description: ''
  157. }
  158. ])
  159. return done()
  160. }
  161. )
  162. })
  163. it('should return Recurly account email', function() {
  164. expect(this.data.personalSubscription.recurly.account.email).to.equal(
  165. 'mock@email.com'
  166. )
  167. })
  168. })
  169. describe('when the user has a subscription without recurly', function() {
  170. beforeEach(function(done) {
  171. Subscription.create(
  172. {
  173. admin_id: this.user._id,
  174. manager_ids: [this.user._id],
  175. planCode: 'collaborator'
  176. },
  177. error => {
  178. if (error != null) {
  179. return done(error)
  180. }
  181. return SubscriptionViewModelBuilder.buildUsersSubscriptionViewModel(
  182. this.user,
  183. (error, data) => {
  184. this.data = data
  185. if (error != null) {
  186. return done(error)
  187. }
  188. return done()
  189. }
  190. )
  191. }
  192. )
  193. })
  194. after(function(done) {
  195. Subscription.remove(
  196. {
  197. admin_id: this.user._id
  198. },
  199. done
  200. )
  201. })
  202. it('should return a personalSubscription with no recurly data', function() {
  203. const subscription = this.data.personalSubscription
  204. expect(subscription).to.exist
  205. expect(subscription.planCode).to.equal('collaborator')
  206. return expect(subscription.recurly).to.not.exist
  207. })
  208. it('should return no memberGroupSubscriptions', function() {
  209. return expect(this.data.memberGroupSubscriptions).to.deep.equal([])
  210. })
  211. })
  212. describe('when the user is a member of a group subscription', function() {
  213. beforeEach(function(done) {
  214. this.owner1 = new User()
  215. this.owner2 = new User()
  216. async.series(
  217. [
  218. cb => this.owner1.ensureUserExists(cb),
  219. cb => this.owner2.ensureUserExists(cb),
  220. cb =>
  221. Subscription.create(
  222. {
  223. admin_id: this.owner1._id,
  224. manager_ids: [this.owner1._id],
  225. planCode: 'collaborator',
  226. groupPlan: true,
  227. member_ids: [this.user._id]
  228. },
  229. cb
  230. ),
  231. cb =>
  232. Subscription.create(
  233. {
  234. admin_id: this.owner2._id,
  235. manager_ids: [this.owner2._id],
  236. planCode: 'collaborator',
  237. groupPlan: true,
  238. member_ids: [this.user._id]
  239. },
  240. cb
  241. )
  242. ],
  243. error => {
  244. if (error != null) {
  245. return done(error)
  246. }
  247. return SubscriptionViewModelBuilder.buildUsersSubscriptionViewModel(
  248. this.user,
  249. (error, data) => {
  250. this.data = data
  251. if (error != null) {
  252. return done(error)
  253. }
  254. return done()
  255. }
  256. )
  257. }
  258. )
  259. })
  260. after(function(done) {
  261. Subscription.remove(
  262. {
  263. admin_id: this.owner1._id
  264. },
  265. error => {
  266. if (error != null) {
  267. return done(error)
  268. }
  269. return Subscription.remove(
  270. {
  271. admin_id: this.owner2._id
  272. },
  273. done
  274. )
  275. }
  276. )
  277. })
  278. it('should return no personalSubscription', function() {
  279. return expect(this.data.personalSubscription).to.equal(null)
  280. })
  281. it('should return the two memberGroupSubscriptions', function() {
  282. expect(this.data.memberGroupSubscriptions.length).to.equal(2)
  283. expect(
  284. // Mongoose populates the admin_id with the user
  285. this.data.memberGroupSubscriptions[0].admin_id._id.toString()
  286. ).to.equal(this.owner1._id)
  287. return expect(
  288. this.data.memberGroupSubscriptions[1].admin_id._id.toString()
  289. ).to.equal(this.owner2._id)
  290. })
  291. })
  292. describe('when the user is a manager of a group subscription', function() {
  293. beforeEach(function(done) {
  294. this.owner1 = new User()
  295. this.owner2 = new User()
  296. async.series(
  297. [
  298. cb => this.owner1.ensureUserExists(cb),
  299. cb => this.owner2.ensureUserExists(cb),
  300. cb =>
  301. Subscription.create(
  302. {
  303. admin_id: this.owner1._id,
  304. manager_ids: [this.owner1._id, this.user._id],
  305. planCode: 'collaborator',
  306. groupPlan: true
  307. },
  308. cb
  309. )
  310. ],
  311. error => {
  312. if (error != null) {
  313. return done(error)
  314. }
  315. return SubscriptionViewModelBuilder.buildUsersSubscriptionViewModel(
  316. this.user,
  317. (error, data) => {
  318. this.data = data
  319. if (error != null) {
  320. return done(error)
  321. }
  322. return done()
  323. }
  324. )
  325. }
  326. )
  327. })
  328. after(function(done) {
  329. Subscription.remove(
  330. {
  331. admin_id: this.owner1._id
  332. },
  333. done
  334. )
  335. })
  336. it('should return no personalSubscription', function() {
  337. return expect(this.data.personalSubscription).to.equal(null)
  338. })
  339. it('should return the managedGroupSubscriptions', function() {
  340. expect(this.data.managedGroupSubscriptions.length).to.equal(1)
  341. const subscription = this.data.managedGroupSubscriptions[0]
  342. expect(
  343. // Mongoose populates the admin_id with the user
  344. subscription.admin_id._id.toString()
  345. ).to.equal(this.owner1._id)
  346. return expect(subscription.groupPlan).to.equal(true)
  347. })
  348. })
  349. describe('when the user is a manager of an institution', function() {
  350. beforeEach(function(done) {
  351. this.v1Id = MockV1Api.nextV1Id()
  352. async.series(
  353. [
  354. cb => {
  355. return Institution.create(
  356. {
  357. v1Id: this.v1Id,
  358. managerIds: [this.user._id]
  359. },
  360. cb
  361. )
  362. }
  363. ],
  364. error => {
  365. if (error != null) {
  366. return done(error)
  367. }
  368. return SubscriptionViewModelBuilder.buildUsersSubscriptionViewModel(
  369. this.user,
  370. (error, data) => {
  371. this.data = data
  372. if (error != null) {
  373. return done(error)
  374. }
  375. return done()
  376. }
  377. )
  378. }
  379. )
  380. })
  381. after(function(done) {
  382. Institution.remove(
  383. {
  384. v1Id: this.v1Id
  385. },
  386. done
  387. )
  388. })
  389. it('should return the managedInstitutions', function() {
  390. expect(this.data.managedInstitutions.length).to.equal(1)
  391. const institution = this.data.managedInstitutions[0]
  392. expect(institution.v1Id).to.equal(this.v1Id)
  393. return expect(institution.name).to.equal(`Institution ${this.v1Id}`)
  394. })
  395. })
  396. describe('when the user is a member of an affiliation', function() {
  397. beforeEach(function(done) {
  398. const v1Id = MockV1Api.nextV1Id()
  399. MockV1Api.setUser(v1Id, {
  400. subscription: {},
  401. subscription_status: {}
  402. })
  403. MockV1Api.setAffiliations([
  404. {
  405. email: 'confirmed-affiliation-email@stanford.example.edu',
  406. licence: 'pro_plus',
  407. department: 'Math',
  408. role: 'Prof',
  409. inferred: false,
  410. institution: {
  411. name: 'Stanford',
  412. confirmed: true
  413. }
  414. },
  415. {
  416. email: 'unconfirmed-affiliation-email@harvard.example.edu',
  417. licence: 'pro_plus',
  418. institution: {
  419. name: 'Harvard',
  420. confirmed: true
  421. }
  422. },
  423. {
  424. email: 'confirmed-affiliation-email@mit.example.edu',
  425. licence: 'pro_plus',
  426. institution: { name: 'MIT', confirmed: false }
  427. }
  428. ])
  429. return async.series(
  430. [
  431. cb => {
  432. return this.user.setV1Id(v1Id, cb)
  433. },
  434. cb => {
  435. return this.user.addEmail(
  436. 'unconfirmed-affiliation-email@harvard.example.edu',
  437. cb
  438. )
  439. },
  440. cb => {
  441. return this.user.addEmail(
  442. 'confirmed-affiliation-email@stanford.example.edu',
  443. cb
  444. )
  445. },
  446. cb => {
  447. return this.user.confirmEmail(
  448. 'confirmed-affiliation-email@stanford.example.edu',
  449. cb
  450. )
  451. },
  452. cb => {
  453. return this.user.addEmail(
  454. 'confirmed-affiliation-email@mit.example.edu',
  455. cb
  456. )
  457. },
  458. cb => {
  459. return this.user.confirmEmail(
  460. 'confirmed-affiliation-email@mit.example.edu',
  461. cb
  462. )
  463. }
  464. ],
  465. error => {
  466. if (error != null) {
  467. return done(error)
  468. }
  469. return SubscriptionViewModelBuilder.buildUsersSubscriptionViewModel(
  470. this.user,
  471. (error, data) => {
  472. this.data = data
  473. if (error != null) {
  474. return done(error)
  475. }
  476. return done()
  477. }
  478. )
  479. }
  480. )
  481. })
  482. it('should return only the affilations with confirmed institutions, and confirmed emails', function() {
  483. return expect(this.data.confirmedMemberAffiliations).to.deep.equal([
  484. {
  485. licence: 'pro_plus',
  486. department: 'Math',
  487. role: 'Prof',
  488. inferred: false,
  489. institution: {
  490. name: 'Stanford',
  491. confirmed: true
  492. }
  493. }
  494. ])
  495. })
  496. })
  497. describe('when the user has a v1 subscription', function() {
  498. beforeEach(function(done) {
  499. let v1Id
  500. MockV1Api.setUser((v1Id = MockV1Api.nextV1Id()), {
  501. subscription: (this.subscription = {
  502. trial: false,
  503. has_plan: true,
  504. teams: [
  505. {
  506. id: 56,
  507. name: 'Test team'
  508. }
  509. ]
  510. }),
  511. subscription_status: (this.subscription_status = {
  512. product: { mock: 'product' },
  513. team: null
  514. })
  515. })
  516. return this.user.setV1Id(v1Id, error => {
  517. if (error != null) {
  518. return done(error)
  519. }
  520. return SubscriptionViewModelBuilder.buildUsersSubscriptionViewModel(
  521. this.user,
  522. (error, data) => {
  523. this.data = data
  524. if (error != null) {
  525. return done(error)
  526. }
  527. return done()
  528. }
  529. )
  530. })
  531. })
  532. it('should return no personalSubscription', function() {
  533. return expect(this.data.personalSubscription).to.equal(null)
  534. })
  535. it('should return no memberGroupSubscriptions', function() {
  536. return expect(this.data.memberGroupSubscriptions).to.deep.equal([])
  537. })
  538. it('should return a v1SubscriptionStatus', function() {
  539. return expect(this.data.v1SubscriptionStatus).to.deep.equal(
  540. this.subscription_status
  541. )
  542. })
  543. })
  544. })
  545. describe('canceling', function() {
  546. beforeEach(function(done) {
  547. let v1Id
  548. this.user = new User()
  549. MockV1Api.setUser((v1Id = MockV1Api.nextV1Id()), (this.v1_user = {}))
  550. return async.series(
  551. [cb => this.user.login(cb), cb => this.user.setV1Id(v1Id, cb)],
  552. error => {
  553. return this.user.request(
  554. {
  555. method: 'POST',
  556. url: '/user/subscription/v1/cancel'
  557. },
  558. (error, response) => {
  559. this.response = response
  560. if (error != null) {
  561. return done(error)
  562. }
  563. return done()
  564. }
  565. )
  566. }
  567. )
  568. })
  569. it('should tell v1 to cancel the subscription', function() {
  570. return expect(this.v1_user.canceled).to.equal(true)
  571. })
  572. it('should redirect to the subscription dashboard', function() {
  573. expect(this.response.statusCode).to.equal(302)
  574. return expect(this.response.headers.location).to.equal(
  575. '/user/subscription'
  576. )
  577. })
  578. })
  579. })