SubscriptionDashboardTests.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516
  1. const { expect } = require('chai')
  2. const async = require('async')
  3. const UserHelper = require('./helpers/UserHelper')
  4. const { Subscription } = require('../../../app/src/models/Subscription')
  5. const { Institution } = require('../../../app/src/models/Institution')
  6. const SubscriptionViewModelBuilder = require('../../../app/src/Features/Subscription/SubscriptionViewModelBuilder')
  7. const RecurlySubscription = require('./helpers/RecurlySubscription')
  8. const MockRecurlyApiClass = require('./mocks/MockRecurlyApi')
  9. const MockV1ApiClass = require('./mocks/MockV1Api')
  10. async function buildUsersSubscriptionViewModelPromise(userId) {
  11. return new Promise((resolve, reject) => {
  12. SubscriptionViewModelBuilder.buildUsersSubscriptionViewModel(
  13. userId,
  14. (error, data) => {
  15. if (error) reject(error)
  16. resolve(data)
  17. }
  18. )
  19. })
  20. }
  21. let MockV1Api, MockRecurlyApi
  22. before(function () {
  23. MockV1Api = MockV1ApiClass.instance()
  24. MockRecurlyApi = MockRecurlyApiClass.instance()
  25. })
  26. describe('Subscriptions', function () {
  27. describe('dashboard', function () {
  28. let userHelper
  29. beforeEach(async function () {
  30. userHelper = await UserHelper.createUser()
  31. this.user = userHelper.user
  32. })
  33. it('should not list personal plan', function () {
  34. const plans = SubscriptionViewModelBuilder.buildPlansList()
  35. expect(plans.individualMonthlyPlans).to.be.a('Array')
  36. const personalMonthlyPlan = plans.individualMonthlyPlans.find(
  37. plan => plan.planCode === 'personal'
  38. )
  39. expect(personalMonthlyPlan).to.be.undefined
  40. })
  41. describe('when the user has no subscription', function () {
  42. beforeEach(function (done) {
  43. SubscriptionViewModelBuilder.buildUsersSubscriptionViewModel(
  44. this.user,
  45. (error, data) => {
  46. this.data = data
  47. if (error) {
  48. return done(error)
  49. }
  50. done()
  51. }
  52. )
  53. })
  54. it('should return no personalSubscription', function () {
  55. expect(this.data.personalSubscription).to.equal(null)
  56. })
  57. it('should return no memberGroupSubscriptions', function () {
  58. expect(this.data.memberGroupSubscriptions).to.deep.equal([])
  59. })
  60. })
  61. describe('when the user has a subscription with recurly', function () {
  62. beforeEach(function (done) {
  63. this.recurlySubscription = new RecurlySubscription({
  64. adminId: this.user._id,
  65. planCode: 'collaborator',
  66. tax_in_cents: 100,
  67. tax_rate: 0.2,
  68. unit_amount_in_cents: 500,
  69. currency: 'GBP',
  70. current_period_ends_at: new Date(2018, 4, 5),
  71. state: 'active',
  72. trial_ends_at: new Date(2018, 6, 7),
  73. account: {
  74. hosted_login_token: 'mock-login-token',
  75. email: 'mock@email.com',
  76. },
  77. })
  78. MockRecurlyApi.coupons = this.coupons = {
  79. 'test-coupon-1': { description: 'Test Coupon 1' },
  80. 'test-coupon-2': { description: 'Test Coupon 2' },
  81. 'test-coupon-3': { name: 'TestCoupon3' },
  82. }
  83. this.recurlySubscription.ensureExists(error => {
  84. if (error) {
  85. return done(error)
  86. }
  87. SubscriptionViewModelBuilder.buildUsersSubscriptionViewModel(
  88. this.user,
  89. (error, data) => {
  90. this.data = data
  91. if (error) {
  92. return done(error)
  93. }
  94. done()
  95. }
  96. )
  97. })
  98. })
  99. after(function (done) {
  100. MockRecurlyApi.mockSubscriptions = []
  101. MockRecurlyApi.coupons = {}
  102. MockRecurlyApi.redemptions = {}
  103. Subscription.deleteOne(
  104. {
  105. admin_id: this.user._id,
  106. },
  107. done
  108. )
  109. })
  110. it('should return a personalSubscription with populated recurly data', function () {
  111. const subscription = this.data.personalSubscription
  112. expect(subscription).to.exist
  113. expect(subscription.planCode).to.equal('collaborator')
  114. expect(subscription.recurly).to.exist
  115. expect(subscription.recurly).to.deep.equal({
  116. activeCoupons: [],
  117. billingDetailsLink:
  118. 'https://test.recurly.com/account/billing_info/edit?ht=mock-login-token',
  119. accountManagementLink:
  120. 'https://test.recurly.com/account/mock-login-token',
  121. currency: 'GBP',
  122. nextPaymentDueAt: '5th May 2018',
  123. price: '£6.00',
  124. state: 'active',
  125. tax: 100,
  126. taxRate: 0.2,
  127. trial_ends_at: new Date(2018, 6, 7),
  128. trialEndsAtFormatted: '7th July 2018',
  129. account: {
  130. account_code: this.user._id.toString(),
  131. email: 'mock@email.com',
  132. hosted_login_token: 'mock-login-token',
  133. },
  134. additionalLicenses: 0,
  135. totalLicenses: 0,
  136. })
  137. })
  138. it('should return no memberGroupSubscriptions', function () {
  139. expect(this.data.memberGroupSubscriptions).to.deep.equal([])
  140. })
  141. it('should include redeemed coupons', function (done) {
  142. MockRecurlyApi.redemptions[this.user._id] = [
  143. { state: 'active', coupon_code: 'test-coupon-1' },
  144. { state: 'inactive', coupon_code: 'test-coupon-2' },
  145. { state: 'active', coupon_code: 'test-coupon-3' },
  146. ]
  147. // rebuild the view model with the redemptions
  148. SubscriptionViewModelBuilder.buildUsersSubscriptionViewModel(
  149. this.user,
  150. (error, data) => {
  151. expect(error).to.not.exist
  152. expect(
  153. data.personalSubscription.recurly.activeCoupons
  154. ).to.deep.equal([
  155. {
  156. coupon_code: 'test-coupon-1',
  157. name: '',
  158. description: 'Test Coupon 1',
  159. },
  160. {
  161. coupon_code: 'test-coupon-3',
  162. name: 'TestCoupon3',
  163. description: '',
  164. },
  165. ])
  166. done()
  167. }
  168. )
  169. })
  170. it('should return Recurly account email', function () {
  171. expect(this.data.personalSubscription.recurly.account.email).to.equal(
  172. 'mock@email.com'
  173. )
  174. })
  175. })
  176. describe('when the user has a subscription without recurly', function () {
  177. beforeEach(function (done) {
  178. Subscription.create(
  179. {
  180. admin_id: this.user._id,
  181. manager_ids: [this.user._id],
  182. planCode: 'collaborator',
  183. },
  184. error => {
  185. if (error) {
  186. return done(error)
  187. }
  188. SubscriptionViewModelBuilder.buildUsersSubscriptionViewModel(
  189. this.user,
  190. (error, data) => {
  191. this.data = data
  192. if (error) {
  193. return done(error)
  194. }
  195. done()
  196. }
  197. )
  198. }
  199. )
  200. })
  201. after(function (done) {
  202. Subscription.deleteOne(
  203. {
  204. admin_id: this.user._id,
  205. },
  206. done
  207. )
  208. })
  209. it('should return a personalSubscription with no recurly data', function () {
  210. const subscription = this.data.personalSubscription
  211. expect(subscription).to.exist
  212. expect(subscription.planCode).to.equal('collaborator')
  213. expect(subscription.recurly).to.not.exist
  214. })
  215. it('should return no memberGroupSubscriptions', function () {
  216. expect(this.data.memberGroupSubscriptions).to.deep.equal([])
  217. })
  218. })
  219. describe('when the user is a member of a group subscription', function () {
  220. beforeEach(async function () {
  221. const userHelperOwner1 = await UserHelper.createUser()
  222. const userHelperOwner2 = await UserHelper.createUser()
  223. this.owner1 = userHelperOwner1.user
  224. this.owner2 = userHelperOwner2.user
  225. await Subscription.create({
  226. admin_id: this.owner1._id,
  227. manager_ids: [this.owner1._id],
  228. planCode: 'collaborator',
  229. groupPlan: true,
  230. member_ids: [this.user._id],
  231. })
  232. await Subscription.create({
  233. admin_id: this.owner2._id,
  234. manager_ids: [this.owner2._id],
  235. planCode: 'collaborator',
  236. groupPlan: true,
  237. member_ids: [this.user._id],
  238. })
  239. this.data = await buildUsersSubscriptionViewModelPromise(this.user._id)
  240. })
  241. after(function (done) {
  242. Subscription.deleteOne(
  243. {
  244. admin_id: this.owner1._id,
  245. },
  246. error => {
  247. if (error) {
  248. return done(error)
  249. }
  250. Subscription.deleteOne(
  251. {
  252. admin_id: this.owner2._id,
  253. },
  254. done
  255. )
  256. }
  257. )
  258. })
  259. it('should return no personalSubscription', function () {
  260. expect(this.data.personalSubscription).to.equal(null)
  261. })
  262. it('should return the two memberGroupSubscriptions', function () {
  263. expect(this.data.memberGroupSubscriptions.length).to.equal(2)
  264. expect(
  265. // Mongoose populates the admin_id with the user
  266. this.data.memberGroupSubscriptions[0].admin_id._id.toString()
  267. ).to.equal(this.owner1._id.toString())
  268. expect(
  269. this.data.memberGroupSubscriptions[1].admin_id._id.toString()
  270. ).to.equal(this.owner2._id.toString())
  271. })
  272. })
  273. describe('when the user is a manager of a group subscription', function () {
  274. beforeEach(async function () {
  275. const userHelperOwner1 = await UserHelper.createUser()
  276. const userHelperOwner2 = await UserHelper.createUser()
  277. this.owner1 = userHelperOwner1.user
  278. this.owner2 = userHelperOwner2.user
  279. await Subscription.create({
  280. admin_id: this.owner1._id,
  281. manager_ids: [this.owner1._id, this.user._id],
  282. planCode: 'collaborator',
  283. groupPlan: true,
  284. })
  285. this.data = await buildUsersSubscriptionViewModelPromise(this.user._id)
  286. })
  287. after(function (done) {
  288. Subscription.deleteOne(
  289. {
  290. admin_id: this.owner1._id,
  291. },
  292. done
  293. )
  294. })
  295. it('should return no personalSubscription', function () {
  296. expect(this.data.personalSubscription).to.equal(null)
  297. })
  298. it('should return the managedGroupSubscriptions', function () {
  299. expect(this.data.managedGroupSubscriptions.length).to.equal(1)
  300. const subscription = this.data.managedGroupSubscriptions[0]
  301. expect(
  302. // Mongoose populates the admin_id with the user
  303. subscription.admin_id._id.toString()
  304. ).to.equal(this.owner1._id.toString())
  305. expect(subscription.groupPlan).to.equal(true)
  306. })
  307. })
  308. describe('when the user is a manager of an institution', function () {
  309. beforeEach(function (done) {
  310. this.v1Id = MockV1Api.nextV1Id()
  311. async.series(
  312. [
  313. cb => {
  314. Institution.create(
  315. {
  316. v1Id: this.v1Id,
  317. managerIds: [this.user._id],
  318. },
  319. cb
  320. )
  321. },
  322. ],
  323. error => {
  324. if (error) {
  325. return done(error)
  326. }
  327. SubscriptionViewModelBuilder.buildUsersSubscriptionViewModel(
  328. this.user,
  329. (error, data) => {
  330. this.data = data
  331. if (error) {
  332. return done(error)
  333. }
  334. done()
  335. }
  336. )
  337. }
  338. )
  339. })
  340. after(function (done) {
  341. Institution.deleteOne(
  342. {
  343. v1Id: this.v1Id,
  344. },
  345. done
  346. )
  347. })
  348. it('should return the managedInstitutions', function () {
  349. expect(this.data.managedInstitutions.length).to.equal(1)
  350. const institution = this.data.managedInstitutions[0]
  351. expect(institution.v1Id).to.equal(this.v1Id)
  352. expect(institution.name).to.equal(`Institution ${this.v1Id}`)
  353. })
  354. })
  355. describe('when the user is a member of an affiliation', function () {
  356. beforeEach(async function () {
  357. const v1Id = MockV1Api.nextV1Id()
  358. MockV1Api.setUser(v1Id, {
  359. subscription: {},
  360. subscription_status: {},
  361. })
  362. await UserHelper.updateUser(this.user._id, {
  363. $set: { overleaf: { id: v1Id } },
  364. })
  365. const harvardDomain = 'harvard.example.edu'
  366. const mitDomain = 'mit.example.edu'
  367. const stanfordDomain = 'stanford.example.edu'
  368. const harvardId = MockV1Api.createInstitution({
  369. name: 'Harvard',
  370. hostname: harvardDomain,
  371. })
  372. const mitId = MockV1Api.createInstitution({
  373. name: 'MIT',
  374. hostname: mitDomain,
  375. })
  376. const stanfordId = MockV1Api.createInstitution({
  377. name: 'Stanford',
  378. hostname: stanfordDomain,
  379. })
  380. MockV1Api.updateInstitutionDomain(harvardId, harvardDomain, {
  381. confirmed: true,
  382. })
  383. MockV1Api.updateInstitutionDomain(mitId, mitDomain, {
  384. confirmed: false,
  385. })
  386. MockV1Api.updateInstitutionDomain(stanfordId, stanfordDomain, {
  387. confirmed: true,
  388. })
  389. this.harvardEmail = `unconfirmed-affiliation-email@${harvardDomain}`
  390. this.stanfordEmail = `confirmed-affiliation-email@${stanfordDomain}`
  391. const mitEmail = `confirmed-affiliation-email@${mitDomain}`
  392. userHelper = await UserHelper.loginUser(
  393. userHelper.getDefaultEmailPassword()
  394. )
  395. await userHelper.addEmail(this.harvardEmail)
  396. await userHelper.addEmailAndConfirm(this.user._id, this.stanfordEmail)
  397. await userHelper.addEmailAndConfirm(this.user._id, mitEmail)
  398. this.data = await buildUsersSubscriptionViewModelPromise(this.user._id)
  399. })
  400. it('should return only the affilations with confirmed institutions, and confirmed emails', function () {
  401. expect(this.data.confirmedMemberAffiliations.length).to.equal(1)
  402. expect(
  403. this.data.confirmedMemberAffiliations[0].institution.name
  404. ).to.equal('Stanford')
  405. })
  406. })
  407. describe('when the user has a v1 subscription', function () {
  408. beforeEach(async function () {
  409. let v1Id
  410. MockV1Api.setUser((v1Id = MockV1Api.nextV1Id()), {
  411. subscription: (this.subscription = {
  412. trial: false,
  413. has_plan: true,
  414. teams: [
  415. {
  416. id: 56,
  417. name: 'Test team',
  418. },
  419. ],
  420. }),
  421. subscription_status: (this.subscription_status = {
  422. product: { mock: 'product' },
  423. team: null,
  424. }),
  425. })
  426. await UserHelper.updateUser(this.user._id, {
  427. $set: {
  428. overleaf: {
  429. id: v1Id,
  430. },
  431. },
  432. })
  433. this.data = await buildUsersSubscriptionViewModelPromise(this.user._id)
  434. })
  435. it('should return no personalSubscription', function () {
  436. expect(this.data.personalSubscription).to.equal(null)
  437. })
  438. it('should return no memberGroupSubscriptions', function () {
  439. expect(this.data.memberGroupSubscriptions).to.deep.equal([])
  440. })
  441. it('should return a v1SubscriptionStatus', function () {
  442. expect(this.data.v1SubscriptionStatus).to.deep.equal(
  443. this.subscription_status
  444. )
  445. })
  446. })
  447. })
  448. describe('canceling', function () {
  449. let userHelper, v1Id
  450. beforeEach(async function () {
  451. v1Id = MockV1Api.nextV1Id()
  452. userHelper = await UserHelper.createUser({ overleaf: { id: v1Id } })
  453. this.user = userHelper.user
  454. MockV1Api.setUser(v1Id, (this.v1_user = {}))
  455. userHelper = await UserHelper.loginUser(
  456. userHelper.getDefaultEmailPassword()
  457. )
  458. this.response = await userHelper.request.post(
  459. '/user/subscription/v1/cancel',
  460. {
  461. simple: false,
  462. }
  463. )
  464. })
  465. it('should tell v1 to cancel the subscription', function () {
  466. expect(this.v1_user.canceled).to.equal(true)
  467. })
  468. it('should redirect to the subscription dashboard', function () {
  469. expect(this.response.statusCode).to.equal(302)
  470. expect(this.response.headers.location).to.equal('/user/subscription')
  471. })
  472. })
  473. })