SubscriptionTests.js 18 KB

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