const { expect } = require('chai') const UserHelper = require('./helpers/UserHelper') const settings = require('settings-sharelatex') const { ObjectId } = require('mongodb') const { Subscription } = require('../../../app/src/models/Subscription') const { User } = require('../../../app/src/models/User') const FeaturesUpdater = require('../../../app/src/Features/Subscription/FeaturesUpdater') const MockV1ApiClass = require('./mocks/MockV1Api') const logger = require('logger-sharelatex') logger.logger.level('error') let MockV1Api before(function () { MockV1Api = MockV1ApiClass.instance() }) const syncUserAndGetFeatures = function (user, callback) { FeaturesUpdater.refreshFeatures(user._id, error => { if (error) { return callback(error) } User.findById(user._id, (error, user) => { if (error) { return callback(error) } const { features } = user.toObject() delete features.$init // mongoose internals callback(null, features) }) }) } describe('FeatureUpdater.refreshFeatures', function () { 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) { syncUserAndGetFeatures(user, (error, features) => { if (error) { throw error } expect(features).to.deep.equal(settings.defaultFeatures) done() }) }) }) describe('when the user has an individual subscription', function () { beforeEach(function () { 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) { syncUserAndGetFeatures(user, (error, features) => { if (error) { throw error } const plan = settings.plans.find( plan => plan.planCode === 'collaborator' ) expect(features).to.deep.equal(plan.features) done() }) }) }) describe('when the user is in a group subscription', function () { beforeEach(function () { const groupAdminId = ObjectId() Subscription.create({ admin_id: groupAdminId, manager_ids: [groupAdminId], member_ids: [user._id], groupAccount: true, planCode: 'collaborator', customAccount: true }) }) // returns a promise it('should set their features to the upgraded set', function (done) { syncUserAndGetFeatures(user, (error, features) => { if (error) { throw error } const plan = settings.plans.find( plan => plan.planCode === 'collaborator' ) expect(features).to.deep.equal(plan.features) done() }) }) }) describe('when the user has bonus features', function () { beforeEach(function () { return User.updateOne( { _id: user._id }, { refered_user_count: 10 } ) }) // returns a promise it('should set their features to the bonus set', function (done) { syncUserAndGetFeatures(user, (error, features) => { if (error) { throw error } expect(features).to.deep.equal( Object.assign( {}, settings.defaultFeatures, settings.bonus_features[9] ) ) done() }) }) }) describe('when the user has affiliations', 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 ) }) it('should not set their features if email is not confirmed', function (done) { syncUserAndGetFeatures(user, (error, features) => { expect(features).to.deep.equal(settings.defaultFeatures) done() }) }) 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) 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() }) }) }) }) }) describe('when the user is due bonus features and has extra features that no longer apply', function () { beforeEach(function () { return User.updateOne( { _id: user._id }, { refered_user_count: 10, 'features.github': true } ) }) // returns a promise it('should set their features to the bonus set and downgrade the extras', function (done) { syncUserAndGetFeatures(user, (error, features) => { if (error) { throw error } expect(features).to.deep.equal( Object.assign( {}, settings.defaultFeatures, settings.bonus_features[9] ) ) done() }) }) }) describe('when the user has a v1 plan', function () { beforeEach(function () { MockV1Api.setUser(42, { plan_name: 'free' }) return User.updateOne( { _id: user._id }, { overleaf: { id: 42 } } ) }) // returns a promise it('should set their features to the v1 plan', function (done) { 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) done() }) }) }) describe('when the user has a v1 plan and bonus features', function () { beforeEach(function () { MockV1Api.setUser(42, { plan_name: 'free' }) return User.updateOne( { _id: user._id }, { overleaf: { id: 42 }, refered_user_count: 10 } ) }) // returns a promise it('should set their features to the best of the v1 plan and bonus features', function (done) { syncUserAndGetFeatures(user, (error, features) => { if (error) { throw error } const v1plan = settings.plans.find(plan => plan.planCode === 'v1_free') const expectedFeatures = Object.assign( {}, v1plan.features, settings.bonus_features[9] ) expect(features).to.deep.equal(expectedFeatures) done() }) }) }) describe('when the user has a group and personal subscription', function () { beforeEach(function (done) { const groupAdminId = ObjectId() Subscription.create( { admin_id: user._id, manager_ids: [user._id], planCode: 'professional', customAccount: true }, error => { if (error) { throw error } Subscription.create( { admin_id: groupAdminId, manager_ids: [groupAdminId], member_ids: [user._id], groupAccount: true, planCode: 'collaborator', customAccount: true }, done ) } ) }) it('should set their features to the best set', function (done) { syncUserAndGetFeatures(user, (error, features) => { if (error) { throw error } const plan = settings.plans.find( plan => plan.planCode === 'professional' ) expect(features).to.deep.equal(plan.features) done() }) }) }) describe('when the notifyV1Flag is passed', function () { beforeEach(function () { User.updateOne( { _id: user._id }, { overleaf: { id: 42 } } ) }) // returns a promise }) describe('when the user has features overrides', function () { beforeEach(function () { const futureDate = new Date() futureDate.setDate(futureDate.getDate() + 1) return User.updateOne( { _id: user._id }, { featuresOverrides: [ { features: { github: true } }, { features: { dropbox: true }, expiresAt: new Date(1990, 12, 25) }, { features: { trackChanges: true }, expiresAt: futureDate } ] } ) }) // returns a promise it('should set their features to the overridden set', function (done) { syncUserAndGetFeatures(user, (error, features) => { if (error) { throw error } let expectedFeatures = Object.assign(settings.defaultFeatures, { github: true, trackChanges: true }) expect(features).to.deep.equal(expectedFeatures) done() }) }) }) })