Merge pull request #3714 from overleaf/jel-tests

Update tests for domain handling

GitOrigin-RevId: d7902a7c55d36a35a436e5ac3adad174ea69e9f4
This commit is contained in:
Alasdair Smith 2021-03-04 14:22:43 +00:00 committed by Copybot
parent dc9841cb69
commit 1e07b5d14e
5 changed files with 340 additions and 435 deletions

View file

@ -1,21 +1,5 @@
/* eslint-disable
node/handle-callback-err,
max-len,
no-return-assign,
no-unused-vars,
*/
// 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 UserClient = require('./helpers/User')
const request = require('./helpers/request')
const UserHelper = require('./helpers/UserHelper')
const settings = require('settings-sharelatex')
const { ObjectId } = require('mongodb')
const { Subscription } = require('../../../app/src/models/Subscription')
@ -33,67 +17,60 @@ before(function() {
})
const syncUserAndGetFeatures = function(user, callback) {
if (callback == null) {
callback = function(error, features) {}
}
return FeaturesUpdater.refreshFeatures(user._id, error => {
if (error != null) {
FeaturesUpdater.refreshFeatures(user._id, error => {
if (error) {
return callback(error)
}
return User.findById(user._id, (error, user) => {
if (error != null) {
User.findById(user._id, (error, user) => {
if (error) {
return callback(error)
}
const { features } = user.toObject()
delete features.$init // mongoose internals
return callback(null, features)
callback(null, features)
})
})
}
describe('FeatureUpdater.refreshFeatures', function() {
beforeEach(function(done) {
this.user = new UserClient()
return this.user.ensureUserExists(error => {
if (error != null) {
throw error
}
return done()
})
let userHelper, user
beforeEach(async function() {
userHelper = await UserHelper.createUser()
user = userHelper.user
})
describe('when user has no subscriptions', function() {
it('should set their features to the basic set', function(done) {
return syncUserAndGetFeatures(this.user, (error, features) => {
if (error != null) {
syncUserAndGetFeatures(user, (error, features) => {
if (error) {
throw error
}
expect(features).to.deep.equal(settings.defaultFeatures)
return done()
done()
})
})
})
describe('when the user has an individual subscription', function() {
beforeEach(function() {
return Subscription.create({
admin_id: this.user._id,
manager_ids: [this.user._id],
Subscription.create({
admin_id: user._id,
manager_ids: [user._id],
planCode: 'collaborator',
customAccount: true
})
}) // returns a promise
it('should set their features to the upgraded set', function(done) {
return syncUserAndGetFeatures(this.user, (error, features) => {
if (error != null) {
syncUserAndGetFeatures(user, (error, features) => {
if (error) {
throw error
}
const plan = settings.plans.find(
plan => plan.planCode === 'collaborator'
)
expect(features).to.deep.equal(plan.features)
return done()
done()
})
})
})
@ -101,10 +78,10 @@ describe('FeatureUpdater.refreshFeatures', function() {
describe('when the user is in a group subscription', function() {
beforeEach(function() {
const groupAdminId = ObjectId()
return Subscription.create({
Subscription.create({
admin_id: groupAdminId,
manager_ids: [groupAdminId],
member_ids: [this.user._id],
member_ids: [user._id],
groupAccount: true,
planCode: 'collaborator',
customAccount: true
@ -112,15 +89,15 @@ describe('FeatureUpdater.refreshFeatures', function() {
}) // returns a promise
it('should set their features to the upgraded set', function(done) {
return syncUserAndGetFeatures(this.user, (error, features) => {
if (error != null) {
syncUserAndGetFeatures(user, (error, features) => {
if (error) {
throw error
}
const plan = settings.plans.find(
plan => plan.planCode === 'collaborator'
)
expect(features).to.deep.equal(plan.features)
return done()
done()
})
})
})
@ -129,7 +106,7 @@ describe('FeatureUpdater.refreshFeatures', function() {
beforeEach(function() {
return User.updateOne(
{
_id: this.user._id
_id: user._id
},
{
refered_user_count: 10
@ -138,8 +115,8 @@ describe('FeatureUpdater.refreshFeatures', function() {
}) // returns a promise
it('should set their features to the bonus set', function(done) {
return syncUserAndGetFeatures(this.user, (error, features) => {
if (error != null) {
syncUserAndGetFeatures(user, (error, features) => {
if (error) {
throw error
}
expect(features).to.deep.equal(
@ -149,49 +126,59 @@ describe('FeatureUpdater.refreshFeatures', function() {
settings.bonus_features[9]
)
)
return done()
done()
})
})
})
describe('when the user has affiliations', function() {
beforeEach(function() {
let email2, institutionId, hostname
beforeEach(async function() {
institutionId = MockV1Api.createInstitution({ commonsAccount: true })
hostname = 'institution.edu'
MockV1Api.addInstitutionDomain(institutionId, hostname, {
confirmed: true
})
email2 = `${user._id}@${hostname}`
userHelper = await UserHelper.loginUser(
userHelper.getDefaultEmailPassword()
)
await userHelper.addEmail(email2)
this.institutionPlan = settings.plans.find(
plan => plan.planCode === settings.institutionPlanCode
)
this.email = this.user.emails[0].email
return (this.affiliationData = {
email: this.email,
licence: 'pro_plus',
institution: { confirmed: true }
})
})
it('should not set their features if email is not confirmed', function(done) {
MockV1Api.setAffiliations(this.user._id, [this.affiliationData])
return syncUserAndGetFeatures(this.user, (error, features) => {
syncUserAndGetFeatures(user, (error, features) => {
expect(features).to.deep.equal(settings.defaultFeatures)
return done()
done()
})
})
it('should set their features if email is confirmed', function(done) {
MockV1Api.setAffiliations(this.user._id, [this.affiliationData])
return this.user.confirmEmail(this.email, error => {
return syncUserAndGetFeatures(this.user, (error, features) => {
describe('when email is confirmed', function() {
beforeEach(async function() {
await userHelper.confirmEmail(user._id, email2)
})
it('should set their features', function(done) {
syncUserAndGetFeatures(user, (error, features) => {
expect(features).to.deep.equal(this.institutionPlan.features)
return done()
done()
})
})
})
it('should not set their features if institution is not confirmed', function(done) {
this.affiliationData.institution.confirmed = false
MockV1Api.setAffiliations(this.user._id, [this.affiliationData])
return this.user.confirmEmail(this.email, error => {
return syncUserAndGetFeatures(this.user, (error, features) => {
expect(features).to.deep.equal(settings.defaultFeatures)
return done()
describe('when domain is not confirmed as part of institution', function() {
beforeEach(function() {
MockV1Api.updateInstitutionDomain(institutionId, hostname, {
confirmed: false
})
})
it('should not set their features', function(done) {
syncUserAndGetFeatures(user, (error, features) => {
expect(features).to.deep.equal(settings.defaultFeatures)
done()
})
})
})
})
@ -201,7 +188,7 @@ describe('FeatureUpdater.refreshFeatures', function() {
beforeEach(function() {
return User.updateOne(
{
_id: this.user._id
_id: user._id
},
{
refered_user_count: 10,
@ -211,8 +198,8 @@ describe('FeatureUpdater.refreshFeatures', function() {
}) // returns a promise
it('should set their features to the bonus set and downgrade the extras', function(done) {
return syncUserAndGetFeatures(this.user, (error, features) => {
if (error != null) {
syncUserAndGetFeatures(user, (error, features) => {
if (error) {
throw error
}
expect(features).to.deep.equal(
@ -222,7 +209,7 @@ describe('FeatureUpdater.refreshFeatures', function() {
settings.bonus_features[9]
)
)
return done()
done()
})
})
})
@ -232,7 +219,7 @@ describe('FeatureUpdater.refreshFeatures', function() {
MockV1Api.setUser(42, { plan_name: 'free' })
return User.updateOne(
{
_id: this.user._id
_id: user._id
},
{
overleaf: {
@ -243,13 +230,13 @@ describe('FeatureUpdater.refreshFeatures', function() {
}) // returns a promise
it('should set their features to the v1 plan', function(done) {
return syncUserAndGetFeatures(this.user, (error, features) => {
if (error != null) {
syncUserAndGetFeatures(user, (error, features) => {
if (error) {
throw error
}
const plan = settings.plans.find(plan => plan.planCode === 'v1_free')
expect(features).to.deep.equal(plan.features)
return done()
done()
})
})
})
@ -259,7 +246,7 @@ describe('FeatureUpdater.refreshFeatures', function() {
MockV1Api.setUser(42, { plan_name: 'free' })
return User.updateOne(
{
_id: this.user._id
_id: user._id
},
{
overleaf: {
@ -271,8 +258,8 @@ describe('FeatureUpdater.refreshFeatures', function() {
}) // returns a promise
it('should set their features to the best of the v1 plan and bonus features', function(done) {
return syncUserAndGetFeatures(this.user, (error, features) => {
if (error != null) {
syncUserAndGetFeatures(user, (error, features) => {
if (error) {
throw error
}
const v1plan = settings.plans.find(plan => plan.planCode === 'v1_free')
@ -282,7 +269,7 @@ describe('FeatureUpdater.refreshFeatures', function() {
settings.bonus_features[9]
)
expect(features).to.deep.equal(expectedFeatures)
return done()
done()
})
})
})
@ -293,20 +280,20 @@ describe('FeatureUpdater.refreshFeatures', function() {
Subscription.create(
{
admin_id: this.user._id,
manager_ids: [this.user._id],
admin_id: user._id,
manager_ids: [user._id],
planCode: 'professional',
customAccount: true
},
error => {
if (error != null) {
if (error) {
throw error
}
return Subscription.create(
Subscription.create(
{
admin_id: groupAdminId,
manager_ids: [groupAdminId],
member_ids: [this.user._id],
member_ids: [user._id],
groupAccount: true,
planCode: 'collaborator',
customAccount: true
@ -318,24 +305,24 @@ describe('FeatureUpdater.refreshFeatures', function() {
})
it('should set their features to the best set', function(done) {
return syncUserAndGetFeatures(this.user, (error, features) => {
if (error != null) {
syncUserAndGetFeatures(user, (error, features) => {
if (error) {
throw error
}
const plan = settings.plans.find(
plan => plan.planCode === 'professional'
)
expect(features).to.deep.equal(plan.features)
return done()
done()
})
})
})
describe('when the notifyV1Flag is passed', function() {
beforeEach(function() {
return User.updateOne(
User.updateOne(
{
_id: this.user._id
_id: user._id
},
{
overleaf: {
@ -352,7 +339,7 @@ describe('FeatureUpdater.refreshFeatures', function() {
futureDate.setDate(futureDate.getDate() + 1)
return User.updateOne(
{
_id: this.user._id
_id: user._id
},
{
featuresOverrides: [
@ -379,8 +366,8 @@ describe('FeatureUpdater.refreshFeatures', function() {
}) // returns a promise
it('should set their features to the overridden set', function(done) {
return syncUserAndGetFeatures(this.user, (error, features) => {
if (error != null) {
syncUserAndGetFeatures(user, (error, features) => {
if (error) {
throw error
}
let expectedFeatures = Object.assign(settings.defaultFeatures, {
@ -388,7 +375,7 @@ describe('FeatureUpdater.refreshFeatures', function() {
trackChanges: true
})
expect(features).to.deep.equal(expectedFeatures)
return done()
done()
})
})
})

View file

@ -1,18 +1,6 @@
/* eslint-disable
node/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 UserHelper = require('./helpers/UserHelper')
const { Subscription } = require('../../../app/src/models/Subscription')
const { Institution } = require('../../../app/src/models/Institution')
const SubscriptionViewModelBuilder = require('../../../app/src/Features/Subscription/SubscriptionViewModelBuilder')
@ -20,6 +8,18 @@ const RecurlySubscription = require('./helpers/RecurlySubscription')
const MockRecurlyApiClass = require('./mocks/MockRecurlyApi')
const MockV1ApiClass = require('./mocks/MockV1Api')
async function buildUsersSubscriptionViewModelPromise(userId) {
return new Promise((resolve, reject) => {
SubscriptionViewModelBuilder.buildUsersSubscriptionViewModel(
userId,
(error, data) => {
if (error) reject(error)
resolve(data)
}
)
})
}
let MockV1Api, MockRecurlyApi
before(function() {
@ -29,9 +29,10 @@ before(function() {
describe('Subscriptions', function() {
describe('dashboard', function() {
beforeEach(function(done) {
this.user = new User()
return this.user.ensureUserExists(done)
let userHelper
beforeEach(async function() {
userHelper = await UserHelper.createUser()
this.user = userHelper.user
})
it('should not list personal plan', function() {
@ -45,24 +46,24 @@ describe('Subscriptions', function() {
describe('when the user has no subscription', function() {
beforeEach(function(done) {
return SubscriptionViewModelBuilder.buildUsersSubscriptionViewModel(
SubscriptionViewModelBuilder.buildUsersSubscriptionViewModel(
this.user,
(error, data) => {
this.data = data
if (error != null) {
if (error) {
return done(error)
}
return done()
done()
}
)
})
it('should return no personalSubscription', function() {
return expect(this.data.personalSubscription).to.equal(null)
expect(this.data.personalSubscription).to.equal(null)
})
it('should return no memberGroupSubscriptions', function() {
return expect(this.data.memberGroupSubscriptions).to.deep.equal([])
expect(this.data.memberGroupSubscriptions).to.deep.equal([])
})
})
@ -89,17 +90,17 @@ describe('Subscriptions', function() {
'test-coupon-3': { name: 'TestCoupon3' }
}
this.recurlySubscription.ensureExists(error => {
if (error != null) {
if (error) {
return done(error)
}
return SubscriptionViewModelBuilder.buildUsersSubscriptionViewModel(
SubscriptionViewModelBuilder.buildUsersSubscriptionViewModel(
this.user,
(error, data) => {
this.data = data
if (error != null) {
if (error) {
return done(error)
}
return done()
done()
}
)
})
@ -122,7 +123,7 @@ describe('Subscriptions', function() {
expect(subscription).to.exist
expect(subscription.planCode).to.equal('collaborator')
expect(subscription.recurly).to.exist
return expect(subscription.recurly).to.deep.equal({
expect(subscription.recurly).to.deep.equal({
activeCoupons: [],
billingDetailsLink:
'https://test.recurly.com/account/billing_info/edit?ht=mock-login-token',
@ -137,7 +138,7 @@ describe('Subscriptions', function() {
trial_ends_at: new Date(2018, 6, 7),
trialEndsAtFormatted: '7th July 2018',
account: {
account_code: this.user._id,
account_code: this.user._id.toString(),
email: 'mock@email.com',
hosted_login_token: 'mock-login-token'
},
@ -147,7 +148,7 @@ describe('Subscriptions', function() {
})
it('should return no memberGroupSubscriptions', function() {
return expect(this.data.memberGroupSubscriptions).to.deep.equal([])
expect(this.data.memberGroupSubscriptions).to.deep.equal([])
})
it('should include redeemed coupons', function(done) {
@ -158,7 +159,7 @@ describe('Subscriptions', function() {
]
// rebuild the view model with the redemptions
return SubscriptionViewModelBuilder.buildUsersSubscriptionViewModel(
SubscriptionViewModelBuilder.buildUsersSubscriptionViewModel(
this.user,
(error, data) => {
expect(error).to.not.exist
@ -176,7 +177,7 @@ describe('Subscriptions', function() {
description: ''
}
])
return done()
done()
}
)
})
@ -197,17 +198,17 @@ describe('Subscriptions', function() {
planCode: 'collaborator'
},
error => {
if (error != null) {
if (error) {
return done(error)
}
return SubscriptionViewModelBuilder.buildUsersSubscriptionViewModel(
SubscriptionViewModelBuilder.buildUsersSubscriptionViewModel(
this.user,
(error, data) => {
this.data = data
if (error != null) {
if (error) {
return done(error)
}
return done()
done()
}
)
}
@ -227,61 +228,36 @@ describe('Subscriptions', function() {
const subscription = this.data.personalSubscription
expect(subscription).to.exist
expect(subscription.planCode).to.equal('collaborator')
return expect(subscription.recurly).to.not.exist
expect(subscription.recurly).to.not.exist
})
it('should return no memberGroupSubscriptions', function() {
return expect(this.data.memberGroupSubscriptions).to.deep.equal([])
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()
}
)
}
)
beforeEach(async function() {
const userHelperOwner1 = await UserHelper.createUser()
const userHelperOwner2 = await UserHelper.createUser()
this.owner1 = userHelperOwner1.user
this.owner2 = userHelperOwner2.user
await Subscription.create({
admin_id: this.owner1._id,
manager_ids: [this.owner1._id],
planCode: 'collaborator',
groupPlan: true,
member_ids: [this.user._id]
})
await Subscription.create({
admin_id: this.owner2._id,
manager_ids: [this.owner2._id],
planCode: 'collaborator',
groupPlan: true,
member_ids: [this.user._id]
})
this.data = await buildUsersSubscriptionViewModelPromise(this.user._id)
})
after(function(done) {
@ -290,10 +266,10 @@ describe('Subscriptions', function() {
admin_id: this.owner1._id
},
error => {
if (error != null) {
if (error) {
return done(error)
}
return Subscription.deleteOne(
Subscription.deleteOne(
{
admin_id: this.owner2._id
},
@ -304,7 +280,7 @@ describe('Subscriptions', function() {
})
it('should return no personalSubscription', function() {
return expect(this.data.personalSubscription).to.equal(null)
expect(this.data.personalSubscription).to.equal(null)
})
it('should return the two memberGroupSubscriptions', function() {
@ -312,48 +288,27 @@ describe('Subscriptions', function() {
expect(
// Mongoose populates the admin_id with the user
this.data.memberGroupSubscriptions[0].admin_id._id.toString()
).to.equal(this.owner1._id)
return expect(
).to.equal(this.owner1._id.toString())
expect(
this.data.memberGroupSubscriptions[1].admin_id._id.toString()
).to.equal(this.owner2._id)
).to.equal(this.owner2._id.toString())
})
})
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()
}
)
}
)
beforeEach(async function() {
const userHelperOwner1 = await UserHelper.createUser()
const userHelperOwner2 = await UserHelper.createUser()
this.owner1 = userHelperOwner1.user
this.owner2 = userHelperOwner2.user
await Subscription.create({
admin_id: this.owner1._id,
manager_ids: [this.owner1._id, this.user._id],
planCode: 'collaborator',
groupPlan: true
})
this.data = await buildUsersSubscriptionViewModelPromise(this.user._id)
})
after(function(done) {
@ -366,7 +321,7 @@ describe('Subscriptions', function() {
})
it('should return no personalSubscription', function() {
return expect(this.data.personalSubscription).to.equal(null)
expect(this.data.personalSubscription).to.equal(null)
})
it('should return the managedGroupSubscriptions', function() {
@ -375,8 +330,8 @@ describe('Subscriptions', function() {
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)
).to.equal(this.owner1._id.toString())
expect(subscription.groupPlan).to.equal(true)
})
})
@ -386,7 +341,7 @@ describe('Subscriptions', function() {
async.series(
[
cb => {
return Institution.create(
Institution.create(
{
v1Id: this.v1Id,
managerIds: [this.user._id]
@ -396,17 +351,17 @@ describe('Subscriptions', function() {
}
],
error => {
if (error != null) {
if (error) {
return done(error)
}
return SubscriptionViewModelBuilder.buildUsersSubscriptionViewModel(
SubscriptionViewModelBuilder.buildUsersSubscriptionViewModel(
this.user,
(error, data) => {
this.data = data
if (error != null) {
if (error) {
return done(error)
}
return done()
done()
}
)
}
@ -426,119 +381,67 @@ describe('Subscriptions', 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}`)
expect(institution.name).to.equal(`Institution ${this.v1Id}`)
})
})
describe('when the user is a member of an affiliation', function() {
beforeEach(function(done) {
beforeEach(async function() {
const v1Id = MockV1Api.nextV1Id()
MockV1Api.setUser(v1Id, {
subscription: {},
subscription_status: {}
})
MockV1Api.setAffiliations(this.user._id, [
{
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()
}
)
}
await UserHelper.updateUser(this.user._id, {
$set: { overleaf: { id: v1Id } }
})
const harvardDomain = 'harvard.example.edu'
const mitDomain = 'mit.example.edu'
const stanfordDomain = 'stanford.example.edu'
const harvardId = MockV1Api.createInstitution({
name: 'Harvard',
hostname: harvardDomain
})
const mitId = MockV1Api.createInstitution({
name: 'MIT',
hostname: mitDomain
})
const stanfordId = MockV1Api.createInstitution({
name: 'Stanford',
hostname: stanfordDomain
})
MockV1Api.updateInstitutionDomain(harvardId, harvardDomain, {
confirmed: true
})
MockV1Api.updateInstitutionDomain(mitId, mitDomain, {
confirmed: false
})
MockV1Api.updateInstitutionDomain(stanfordId, stanfordDomain, {
confirmed: true
})
this.harvardEmail = `unconfirmed-affiliation-email@${harvardDomain}`
this.stanfordEmail = `confirmed-affiliation-email@${stanfordDomain}`
const mitEmail = `confirmed-affiliation-email@${mitDomain}`
userHelper = await UserHelper.loginUser(
userHelper.getDefaultEmailPassword()
)
await userHelper.addEmail(this.harvardEmail)
await userHelper.addEmailAndConfirm(this.user._id, this.stanfordEmail)
await userHelper.addEmailAndConfirm(this.user._id, mitEmail)
this.data = await buildUsersSubscriptionViewModelPromise(this.user._id)
})
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,
inReconfirmNotificationPeriod: false,
institution: {
name: 'Stanford',
confirmed: true
},
lastDayToReconfirm: undefined,
pastReconfirmDate: false,
portal: undefined
}
])
expect(this.data.confirmedMemberAffiliations.length).to.equal(1)
expect(
this.data.confirmedMemberAffiliations[0].institution.name
).to.equal('Stanford')
})
})
describe('when the user has a v1 subscription', function() {
beforeEach(function(done) {
beforeEach(async function() {
let v1Id
MockV1Api.setUser((v1Id = MockV1Api.nextV1Id()), {
subscription: (this.subscription = {
@ -556,33 +459,26 @@ describe('Subscriptions', function() {
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()
await UserHelper.updateUser(this.user._id, {
$set: {
overleaf: {
id: v1Id
}
)
}
})
this.data = await buildUsersSubscriptionViewModelPromise(this.user._id)
})
it('should return no personalSubscription', function() {
return expect(this.data.personalSubscription).to.equal(null)
expect(this.data.personalSubscription).to.equal(null)
})
it('should return no memberGroupSubscriptions', function() {
return expect(this.data.memberGroupSubscriptions).to.deep.equal([])
expect(this.data.memberGroupSubscriptions).to.deep.equal([])
})
it('should return a v1SubscriptionStatus', function() {
return expect(this.data.v1SubscriptionStatus).to.deep.equal(
expect(this.data.v1SubscriptionStatus).to.deep.equal(
this.subscription_status
)
})
@ -590,39 +486,32 @@ describe('Subscriptions', function() {
})
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()
}
)
let userHelper, v1Id
beforeEach(async function() {
v1Id = MockV1Api.nextV1Id()
console.log('v1Id=', v1Id)
userHelper = await UserHelper.createUser({ overleaf: { id: v1Id } })
this.user = userHelper.user
MockV1Api.setUser(v1Id, (this.v1_user = {}))
userHelper = await UserHelper.loginUser(
userHelper.getDefaultEmailPassword()
)
this.response = await userHelper.request.post(
'/user/subscription/v1/cancel',
{
simple: false
}
)
})
it('should tell v1 to cancel the subscription', function() {
return expect(this.v1_user.canceled).to.equal(true)
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'
)
expect(this.response.headers.location).to.equal('/user/subscription')
})
})
})

View file

@ -340,6 +340,7 @@ class UserHelper {
.find({
use: 'email_confirmation',
'data.user_id': userId.toString(),
'data.email': email,
usedAt: { $exists: false }
})
.next()

View file

@ -66,10 +66,12 @@ class MockV1Api extends AbstractMockApi {
return id
}
addInstitutionDomain(id, domain) {
addInstitutionDomain(institutionId, domain, options = {}) {
if (this.allInstitutionDomains.has(domain)) return
if (!this.institutionDomains[id]) this.institutionDomains[id] = new Set()
this.institutionDomains[id].add(domain)
if (!this.institutionDomains[institutionId]) {
this.institutionDomains[institutionId] = {}
}
this.institutionDomains[institutionId][domain] = options
this.allInstitutionDomains.add(domain)
}
@ -77,9 +79,27 @@ class MockV1Api extends AbstractMockApi {
Object.assign(this.institutions[id], options)
}
updateInstitutionDomain(id, domain, options = {}) {
if (!this.institutionDomains[id] || !this.institutionDomains[id][domain])
return
this.institutionDomains[id][domain] = Object.assign(
{},
this.institutionDomains[id][domain],
options
)
}
addAffiliation(userId, email) {
let institution
let institution = {}
if (!email) return
if (!this.affiliations[userId]) this.affiliations[userId] = []
if (
this.affiliations[userId].find(affiliationData => {
return affiliationData.email === email
})
)
return
const domain = email.split('@').pop()
@ -88,23 +108,20 @@ class MockV1Api extends AbstractMockApi {
}
if (this.allInstitutionDomains.has(domain)) {
for (const [id, domainSet] of Object.entries(this.institutionDomains)) {
if (domainSet.has(domain)) {
institution = this.institutions[id]
for (const [institutionId, domainData] of Object.entries(
this.institutionDomains
)) {
if (domainData[domain]) {
institution.id = institutionId
}
}
}
if (institution) {
if (!this.affiliations[userId]) this.affiliations[userId] = []
if (institution.id) {
this.affiliations[userId].push({ email, institution })
}
}
setAffiliations(userId, affiliations) {
this.affiliations[userId] = affiliations
}
setDocExported(token, info) {
this.doc_exported[token] = info
}
@ -174,7 +191,34 @@ class MockV1Api extends AbstractMockApi {
})
this.app.get('/api/v2/users/:userId/affiliations', (req, res) => {
res.json(this.affiliations[req.params.userId] || [])
if (!this.affiliations[req.params.userId]) return res.json([])
const affiliations = this.affiliations[req.params.userId].map(
affiliation => {
const institutionId = affiliation.institution.id
const domain = affiliation.email.split('@').pop()
const domainData =
this.institutionDomains[institutionId][domain] || {}
const institutionData = this.institutions[institutionId] || {}
affiliation.institution = {
id: institutionId,
name: institutionData.name,
commonsAccount: institutionData.commonsAccount,
isUniversity: !institutionData.institution,
ssoBeta: institutionData.sso_beta || false,
ssoEnabled: institutionData.sso_enabled || false,
maxConfirmationMonths: institutionData.maxConfirmationMonths || null
}
affiliation.institution.confirmed = !!domainData.confirmed
if (institutionData.commonsAccount) {
affiliation.licence = 'pro_plus'
}
return affiliation
}
)
res.json(affiliations)
})
this.app.post('/api/v2/users/:userId/affiliations', (req, res) => {

View file

@ -1,28 +1,14 @@
/* eslint-disable
max-len,
no-return-assign,
no-unused-vars,
*/
// 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
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
const SandboxedModule = require('sandboxed-module')
const should = require('chai').should()
const { expect } = require('chai')
const sinon = require('sinon')
const modulePath = '../../../../app/src/Features/Subscription/FeaturesUpdater'
const { assert } = require('chai')
const { ObjectId } = require('mongodb')
describe('FeaturesUpdater', function() {
beforeEach(function() {
this.user_id = ObjectId().toString()
return (this.FeaturesUpdater = SandboxedModule.require(modulePath, {
this.FeaturesUpdater = SandboxedModule.require(modulePath, {
globals: {
console: console
},
@ -43,7 +29,7 @@ describe('FeaturesUpdater', function() {
hooks: { fire: sinon.stub() }
})
}
}))
})
})
describe('refreshFeatures', function() {
@ -72,51 +58,51 @@ describe('FeaturesUpdater', function() {
.stub()
.returns({ merged: 'features' })
this.UserGetter.getUser = sinon.stub().yields(null, this.user)
return (this.callback = sinon.stub())
this.callback = sinon.stub()
})
describe('normally', function() {
beforeEach(function() {
return this.FeaturesUpdater.refreshFeatures(this.user_id, this.callback)
this.FeaturesUpdater.refreshFeatures(this.user_id, this.callback)
})
it('should get the individual features', function() {
return this.FeaturesUpdater._getIndividualFeatures
this.FeaturesUpdater._getIndividualFeatures
.calledWith(this.user_id)
.should.equal(true)
})
it('should get the group features', function() {
return this.FeaturesUpdater._getGroupFeatureSets
this.FeaturesUpdater._getGroupFeatureSets
.calledWith(this.user_id)
.should.equal(true)
})
it('should get the institution features', function() {
return this.InstitutionsFeatures.getInstitutionsFeatures
this.InstitutionsFeatures.getInstitutionsFeatures
.calledWith(this.user_id)
.should.equal(true)
})
it('should get the v1 features', function() {
return this.FeaturesUpdater._getV1Features
this.FeaturesUpdater._getV1Features
.calledWith(this.user_id)
.should.equal(true)
})
it('should get the bonus features', function() {
return this.ReferalFeatures.getBonusFeatures
this.ReferalFeatures.getBonusFeatures
.calledWith(this.user_id)
.should.equal(true)
})
it('should merge from the default features', function() {
return this.FeaturesUpdater._mergeFeatures
this.FeaturesUpdater._mergeFeatures
.calledWith(this.Settings.defaultFeatures)
.should.equal(true)
})
it('should merge the individual features', function() {
return this.FeaturesUpdater._mergeFeatures
this.FeaturesUpdater._mergeFeatures
.calledWith(sinon.match.any, { individual: 'features' })
.should.equal(true)
})
@ -125,31 +111,31 @@ describe('FeaturesUpdater', function() {
this.FeaturesUpdater._mergeFeatures
.calledWith(sinon.match.any, { group: 'features' })
.should.equal(true)
return this.FeaturesUpdater._mergeFeatures
this.FeaturesUpdater._mergeFeatures
.calledWith(sinon.match.any, { group: 'features2' })
.should.equal(true)
})
it('should merge the institutions features', function() {
return this.FeaturesUpdater._mergeFeatures
this.FeaturesUpdater._mergeFeatures
.calledWith(sinon.match.any, { institutions: 'features' })
.should.equal(true)
})
it('should merge the v1 features', function() {
return this.FeaturesUpdater._mergeFeatures
this.FeaturesUpdater._mergeFeatures
.calledWith(sinon.match.any, { v1: 'features' })
.should.equal(true)
})
it('should merge the bonus features', function() {
return this.FeaturesUpdater._mergeFeatures
this.FeaturesUpdater._mergeFeatures
.calledWith(sinon.match.any, { bonus: 'features' })
.should.equal(true)
})
it('should update the user with the merged features', function() {
return this.UserFeaturesUpdater.updateFeatures
this.UserFeaturesUpdater.updateFeatures
.calledWith(this.user_id, { merged: 'features' })
.should.equal(true)
})
@ -164,7 +150,7 @@ describe('FeaturesUpdater', function() {
this.FeaturesUpdater._mergeFeatures = sinon
.stub()
.returns({ dropbox: false })
return this.FeaturesUpdater.refreshFeatures(this.user_id, this.callback)
this.FeaturesUpdater.refreshFeatures(this.user_id, this.callback)
})
it('should fire module hook to unlink dropbox', function() {
this.Modules.hooks.fire
@ -212,7 +198,7 @@ describe('FeaturesUpdater', function() {
).to.deep.equal({
compileGroup: 'priority'
})
return expect(
expect(
this.FeaturesUpdater._mergeFeatures(
{
compileGroup: 'standard'
@ -251,7 +237,7 @@ describe('FeaturesUpdater', function() {
).to.deep.equal({
collaborators: -1
})
return expect(
expect(
this.FeaturesUpdater._mergeFeatures(
{
collaborators: 4
@ -278,7 +264,7 @@ describe('FeaturesUpdater', function() {
).to.deep.equal({
compileTimeout: 20
})
return expect(
expect(
this.FeaturesUpdater._mergeFeatures(
{
compileTimeout: 10
@ -329,7 +315,7 @@ describe('FeaturesUpdater', function() {
).to.deep.equal({
github: true
})
return expect(
expect(
this.FeaturesUpdater._mergeFeatures(
{
github: false
@ -357,45 +343,45 @@ describe('FeaturesUpdater', function() {
this.UserGetter.getUser = sinon.stub().callsArgWith(2, null, this.user)
this.FeaturesUpdater.refreshFeatures = sinon.stub().yields(null)
return (this.call = cb => {
return this.FeaturesUpdater.doSyncFromV1(this.v1UserId, cb)
})
this.call = cb => {
this.FeaturesUpdater.doSyncFromV1(this.v1UserId, cb)
}
})
describe('when all goes well', function() {
it('should call getUser', function(done) {
return this.call(() => {
this.call(() => {
expect(this.UserGetter.getUser.callCount).to.equal(1)
expect(
this.UserGetter.getUser.calledWith({ 'overleaf.id': this.v1UserId })
).to.equal(true)
return done()
done()
})
})
it('should call refreshFeatures', function(done) {
return this.call(() => {
this.call(() => {
expect(this.FeaturesUpdater.refreshFeatures.callCount).to.equal(1)
expect(
this.FeaturesUpdater.refreshFeatures.calledWith(this.user_id)
).to.equal(true)
return done()
done()
})
})
it('should not produce an error', function(done) {
return this.call(err => {
this.call(err => {
expect(err).to.not.exist
return done()
done()
})
})
})
describe('when getUser produces an error', function() {
beforeEach(function() {
return (this.UserGetter.getUser = sinon
this.UserGetter.getUser = sinon
.stub()
.callsArgWith(2, new Error('woops')))
.callsArgWith(2, new Error('woops'))
})
it('should not call refreshFeatures', function() {
@ -403,31 +389,29 @@ describe('FeaturesUpdater', function() {
})
it('should produce an error', function(done) {
return this.call(err => {
this.call(err => {
expect(err).to.exist
return done()
done()
})
})
})
describe('when getUser does not find a user', function() {
beforeEach(function() {
return (this.UserGetter.getUser = sinon
.stub()
.callsArgWith(2, null, null))
this.UserGetter.getUser = sinon.stub().callsArgWith(2, null, null)
})
it('should not call refreshFeatures', function(done) {
return this.call(() => {
this.call(() => {
expect(this.FeaturesUpdater.refreshFeatures.callCount).to.equal(0)
return done()
done()
})
})
it('should not produce an error', function(done) {
return this.call(err => {
this.call(err => {
expect(err).to.not.exist
return done()
done()
})
})
})