SubscriptionTests.js 18 KB

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