overleaf/services/web/test/acceptance/src/SubscriptionDashboardTests.js

627 lines
19 KiB
JavaScript
Raw Normal View History

/* eslint-disable
handle-callback-err,
max-len,
*/
// TODO: This file was created by bulk-decaffeinate.
// Fix any style issues and re-enable lint.
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* DS207: Consider shorter variations of null checks
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
const { expect } = require('chai')
const async = require('async')
const User = require('./helpers/User')
const { Subscription } = require('../../../app/src/models/Subscription')
const { Institution } = require('../../../app/src/models/Institution')
const SubscriptionViewModelBuilder = require('../../../app/src/Features/Subscription/SubscriptionViewModelBuilder')
const MockRecurlyApi = require('./helpers/MockRecurlyApi')
const MockV1Api = require('./helpers/MockV1Api')
describe('Subscriptions', function() {
describe('dashboard', function() {
beforeEach(function(done) {
this.user = new User()
return this.user.ensureUserExists(done)
})
it('should not list personal plan', function() {
const plans = SubscriptionViewModelBuilder.buildPlansList()
expect(plans.individualMonthlyPlans).to.be.a('Array')
const personalMonthlyPlan = plans.individualMonthlyPlans.find(
plan => plan.planCode === 'personal'
)
expect(personalMonthlyPlan).to.be.undefined
})
describe('when the user has no subscription', function() {
beforeEach(function(done) {
return SubscriptionViewModelBuilder.buildUsersSubscriptionViewModel(
this.user,
(error, data) => {
this.data = data
if (error != null) {
return done(error)
}
return done()
}
)
})
it('should return no personalSubscription', function() {
return expect(this.data.personalSubscription).to.equal(null)
})
it('should return no memberGroupSubscriptions', function() {
return expect(this.data.memberGroupSubscriptions).to.deep.equal([])
})
})
describe('when the user has a subscription with recurly', function() {
beforeEach(function(done) {
MockRecurlyApi.accounts['mock-account-id'] = this.accounts = {
hosted_login_token: 'mock-login-token',
email: 'mock@email.com'
}
MockRecurlyApi.subscriptions[
'mock-subscription-id'
] = this.subscription = {
plan_code: 'collaborator',
tax_in_cents: 100,
tax_rate: 0.2,
unit_amount_in_cents: 500,
currency: 'GBP',
current_period_ends_at: new Date(2018, 4, 5),
state: 'active',
account_id: 'mock-account-id',
trial_ends_at: new Date(2018, 6, 7)
}
MockRecurlyApi.coupons = this.coupons = {
'test-coupon-1': { description: 'Test Coupon 1' },
'test-coupon-2': { description: 'Test Coupon 2' },
'test-coupon-3': { name: 'TestCoupon3' }
}
Subscription.create(
{
admin_id: this.user._id,
manager_ids: [this.user._id],
recurlySubscription_id: 'mock-subscription-id',
planCode: 'collaborator'
},
error => {
if (error != null) {
return done(error)
}
return SubscriptionViewModelBuilder.buildUsersSubscriptionViewModel(
this.user,
(error, data) => {
this.data = data
if (error != null) {
return done(error)
}
return done()
}
)
}
)
})
after(function(done) {
MockRecurlyApi.accounts = {}
MockRecurlyApi.subscriptions = {}
MockRecurlyApi.coupons = {}
MockRecurlyApi.redemptions = {}
Subscription.remove(
{
admin_id: this.user._id
},
done
)
})
it('should return a personalSubscription with populated recurly data', function() {
const subscription = this.data.personalSubscription
expect(subscription).to.exist
expect(subscription.planCode).to.equal('collaborator')
expect(subscription.recurly).to.exist
return expect(subscription.recurly).to.deep.equal({
activeCoupons: [],
billingDetailsLink:
'https://test.recurly.com/account/billing_info/edit?ht=mock-login-token',
accountManagementLink:
'https://test.recurly.com/account/mock-login-token',
currency: 'GBP',
nextPaymentDueAt: '5th May 2018',
price: '£6.00',
state: 'active',
tax: 100,
taxRate: 0.2,
trial_ends_at: new Date(2018, 6, 7),
trialEndsAtFormatted: '7th July 2018',
account: {
account_code: 'mock-account-id',
email: 'mock@email.com',
hosted_login_token: 'mock-login-token'
}
})
})
it('should return no memberGroupSubscriptions', function() {
return expect(this.data.memberGroupSubscriptions).to.deep.equal([])
})
it('should include redeemed coupons', function(done) {
MockRecurlyApi.redemptions['mock-account-id'] = [
{ state: 'active', coupon_code: 'test-coupon-1' },
{ state: 'inactive', coupon_code: 'test-coupon-2' },
{ state: 'active', coupon_code: 'test-coupon-3' }
]
// rebuild the view model with the redemptions
return SubscriptionViewModelBuilder.buildUsersSubscriptionViewModel(
this.user,
(error, data) => {
expect(error).to.not.exist
expect(
data.personalSubscription.recurly.activeCoupons
).to.deep.equal([
{
coupon_code: 'test-coupon-1',
name: '',
description: 'Test Coupon 1'
},
{
coupon_code: 'test-coupon-3',
name: 'TestCoupon3',
description: ''
}
])
return done()
}
)
})
it('should return Recurly account email', function() {
expect(this.data.personalSubscription.recurly.account.email).to.equal(
'mock@email.com'
)
})
})
describe('when the user has a subscription without recurly', function() {
beforeEach(function(done) {
Subscription.create(
{
admin_id: this.user._id,
manager_ids: [this.user._id],
planCode: 'collaborator'
},
error => {
if (error != null) {
return done(error)
}
return SubscriptionViewModelBuilder.buildUsersSubscriptionViewModel(
this.user,
(error, data) => {
this.data = data
if (error != null) {
return done(error)
}
return done()
}
)
}
)
})
after(function(done) {
Subscription.remove(
{
admin_id: this.user._id
},
done
)
})
it('should return a personalSubscription with no recurly data', function() {
const subscription = this.data.personalSubscription
expect(subscription).to.exist
expect(subscription.planCode).to.equal('collaborator')
return expect(subscription.recurly).to.not.exist
})
it('should return no memberGroupSubscriptions', function() {
return expect(this.data.memberGroupSubscriptions).to.deep.equal([])
})
})
describe('when the user is a member of a group subscription', function() {
beforeEach(function(done) {
this.owner1 = new User()
this.owner2 = new User()
async.series(
[
cb => this.owner1.ensureUserExists(cb),
cb => this.owner2.ensureUserExists(cb),
cb =>
Subscription.create(
{
admin_id: this.owner1._id,
manager_ids: [this.owner1._id],
planCode: 'collaborator',
groupPlan: true,
member_ids: [this.user._id]
},
cb
),
cb =>
Subscription.create(
{
admin_id: this.owner2._id,
manager_ids: [this.owner2._id],
planCode: 'collaborator',
groupPlan: true,
member_ids: [this.user._id]
},
cb
)
],
error => {
if (error != null) {
return done(error)
}
return SubscriptionViewModelBuilder.buildUsersSubscriptionViewModel(
this.user,
(error, data) => {
this.data = data
if (error != null) {
return done(error)
}
return done()
}
)
}
)
})
after(function(done) {
Subscription.remove(
{
admin_id: this.owner1._id
},
error => {
if (error != null) {
return done(error)
}
return Subscription.remove(
{
admin_id: this.owner2._id
},
done
)
}
)
})
it('should return no personalSubscription', function() {
return expect(this.data.personalSubscription).to.equal(null)
})
it('should return the two memberGroupSubscriptions', function() {
expect(this.data.memberGroupSubscriptions.length).to.equal(2)
expect(
// Mongoose populates the admin_id with the user
this.data.memberGroupSubscriptions[0].admin_id._id.toString()
).to.equal(this.owner1._id)
return expect(
this.data.memberGroupSubscriptions[1].admin_id._id.toString()
).to.equal(this.owner2._id)
})
})
describe('when the user is a manager of a group subscription', function() {
beforeEach(function(done) {
this.owner1 = new User()
this.owner2 = new User()
async.series(
[
cb => this.owner1.ensureUserExists(cb),
cb => this.owner2.ensureUserExists(cb),
cb =>
Subscription.create(
{
admin_id: this.owner1._id,
manager_ids: [this.owner1._id, this.user._id],
planCode: 'collaborator',
groupPlan: true
},
cb
)
],
error => {
if (error != null) {
return done(error)
}
return SubscriptionViewModelBuilder.buildUsersSubscriptionViewModel(
this.user,
(error, data) => {
this.data = data
if (error != null) {
return done(error)
}
return done()
}
)
}
)
})
after(function(done) {
Subscription.remove(
{
admin_id: this.owner1._id
},
done
)
})
it('should return no personalSubscription', function() {
return expect(this.data.personalSubscription).to.equal(null)
})
it('should return the managedGroupSubscriptions', function() {
expect(this.data.managedGroupSubscriptions.length).to.equal(1)
const subscription = this.data.managedGroupSubscriptions[0]
expect(
// Mongoose populates the admin_id with the user
subscription.admin_id._id.toString()
).to.equal(this.owner1._id)
return expect(subscription.groupPlan).to.equal(true)
})
})
describe('when the user is a manager of an institution', function() {
beforeEach(function(done) {
this.v1Id = MockV1Api.nextV1Id()
async.series(
[
cb => {
return Institution.create(
{
v1Id: this.v1Id,
managerIds: [this.user._id]
},
cb
)
}
],
error => {
if (error != null) {
return done(error)
}
return SubscriptionViewModelBuilder.buildUsersSubscriptionViewModel(
this.user,
(error, data) => {
this.data = data
if (error != null) {
return done(error)
}
return done()
}
)
}
)
})
after(function(done) {
Institution.remove(
{
v1Id: this.v1Id
},
done
)
})
it('should return the managedInstitutions', function() {
expect(this.data.managedInstitutions.length).to.equal(1)
const institution = this.data.managedInstitutions[0]
expect(institution.v1Id).to.equal(this.v1Id)
return expect(institution.name).to.equal(`Institution ${this.v1Id}`)
})
})
describe('when the user is a member of an affiliation', function() {
beforeEach(function(done) {
const v1Id = MockV1Api.nextV1Id()
MockV1Api.setUser(v1Id, {
subscription: {},
subscription_status: {}
})
MockV1Api.setAffiliations([
{
email: 'confirmed-affiliation-email@stanford.example.edu',
licence: 'pro_plus',
department: 'Math',
role: 'Prof',
inferred: false,
institution: {
name: 'Stanford',
confirmed: true
}
},
{
email: 'unconfirmed-affiliation-email@harvard.example.edu',
licence: 'pro_plus',
institution: {
name: 'Harvard',
confirmed: true
}
},
{
email: 'confirmed-affiliation-email@mit.example.edu',
licence: 'pro_plus',
institution: { name: 'MIT', confirmed: false }
}
])
return async.series(
[
cb => {
return this.user.setV1Id(v1Id, cb)
},
cb => {
return this.user.addEmail(
'unconfirmed-affiliation-email@harvard.example.edu',
cb
)
},
cb => {
return this.user.addEmail(
'confirmed-affiliation-email@stanford.example.edu',
cb
)
},
cb => {
return this.user.confirmEmail(
'confirmed-affiliation-email@stanford.example.edu',
cb
)
},
cb => {
return this.user.addEmail(
'confirmed-affiliation-email@mit.example.edu',
cb
)
},
cb => {
return this.user.confirmEmail(
'confirmed-affiliation-email@mit.example.edu',
cb
)
}
],
error => {
if (error != null) {
return done(error)
}
return SubscriptionViewModelBuilder.buildUsersSubscriptionViewModel(
this.user,
(error, data) => {
this.data = data
if (error != null) {
return done(error)
}
return done()
}
)
}
)
})
it('should return only the affilations with confirmed institutions, and confirmed emails', function() {
return expect(this.data.confirmedMemberAffiliations).to.deep.equal([
{
licence: 'pro_plus',
department: 'Math',
role: 'Prof',
inferred: false,
institution: {
name: 'Stanford',
confirmed: true
}
}
])
})
})
describe('when the user has a v1 subscription', function() {
beforeEach(function(done) {
let v1Id
MockV1Api.setUser((v1Id = MockV1Api.nextV1Id()), {
subscription: (this.subscription = {
trial: false,
has_plan: true,
teams: [
{
id: 56,
name: 'Test team'
}
]
}),
subscription_status: (this.subscription_status = {
product: { mock: 'product' },
team: null
})
})
return this.user.setV1Id(v1Id, error => {
if (error != null) {
return done(error)
}
return SubscriptionViewModelBuilder.buildUsersSubscriptionViewModel(
this.user,
(error, data) => {
this.data = data
if (error != null) {
return done(error)
}
return done()
}
)
})
})
it('should return no personalSubscription', function() {
return expect(this.data.personalSubscription).to.equal(null)
})
it('should return no memberGroupSubscriptions', function() {
return expect(this.data.memberGroupSubscriptions).to.deep.equal([])
})
it('should return a v1SubscriptionStatus', function() {
return expect(this.data.v1SubscriptionStatus).to.deep.equal(
this.subscription_status
)
})
})
})
describe('canceling', function() {
beforeEach(function(done) {
let v1Id
this.user = new User()
MockV1Api.setUser((v1Id = MockV1Api.nextV1Id()), (this.v1_user = {}))
return async.series(
[cb => this.user.login(cb), cb => this.user.setV1Id(v1Id, cb)],
error => {
return this.user.request(
{
method: 'POST',
url: '/user/subscription/v1/cancel'
},
(error, response) => {
this.response = response
if (error != null) {
return done(error)
}
return done()
}
)
}
)
})
it('should tell v1 to cancel the subscription', function() {
return expect(this.v1_user.canceled).to.equal(true)
})
it('should redirect to the subscription dashboard', function() {
expect(this.response.statusCode).to.equal(302)
return expect(this.response.headers.location).to.equal(
'/user/subscription'
)
})
})
})