Merge pull request #737 from sharelatex/ta-affiliation-features

Check Institution Licence on Features Update
This commit is contained in:
Timothée Alby 2018-07-11 10:20:29 +02:00 committed by GitHub
commit 467a910d74
8 changed files with 167 additions and 4 deletions

View file

@ -0,0 +1,23 @@
UserGetter = require '../User/UserGetter'
PlansLocator = require '../Subscription/PlansLocator'
Settings = require 'settings-sharelatex'
logger = require 'logger-sharelatex'
module.exports = InstitutionsFeatures =
getInstitutionsFeatures: (userId, callback = (error, features) ->) ->
InstitutionsFeatures.hasLicence userId, (error, hasLicence) ->
return callback error if error?
return callback(null, {}) unless hasLicence
plan = PlansLocator.findLocalPlanInSettings Settings.institutionPlanCode
callback(null, plan?.features or {})
hasLicence: (userId, callback = (error, hasLicence) ->) ->
UserGetter.getUserFullEmails userId, (error, emailsData) ->
return callback error if error?
affiliation = emailsData.find (emailData) ->
licence = emailData.affiliation?.institution?.licence
emailData.confirmedAt? and licence? and licence != 'free'
callback(null, !!affiliation)

View file

@ -7,6 +7,7 @@ Settings = require("settings-sharelatex")
logger = require("logger-sharelatex")
ReferalFeatures = require("../Referal/ReferalFeatures")
V1SubscriptionManager = require("./V1SubscriptionManager")
InstitutionsFeatures = require '../Institutions/InstitutionsFeatures'
oneMonthInSeconds = 60 * 60 * 24 * 30
@ -21,9 +22,11 @@ module.exports = FeaturesUpdater =
if error?
logger.err {err: error, user_id}, "error notifying v1 about updated features"
jobs =
individualFeatures: (cb) -> FeaturesUpdater._getIndividualFeatures user_id, cb
groupFeatureSets: (cb) -> FeaturesUpdater._getGroupFeatureSets user_id, cb
institutionFeatures:(cb) -> InstitutionsFeatures.getInstitutionsFeatures user_id, cb
v1Features: (cb) -> FeaturesUpdater._getV1Features user_id, cb
bonusFeatures: (cb) -> ReferalFeatures.getBonusFeatures user_id, cb
async.series jobs, (err, results)->
@ -32,9 +35,9 @@ module.exports = FeaturesUpdater =
"error getting subscription or group for refreshFeatures"
return callback(err)
{individualFeatures, groupFeatureSets, v1Features, bonusFeatures} = results
logger.log {user_id, individualFeatures, groupFeatureSets, v1Features, bonusFeatures}, 'merging user features'
featureSets = groupFeatureSets.concat [individualFeatures, v1Features, bonusFeatures]
{individualFeatures, groupFeatureSets, institutionFeatures, v1Features, bonusFeatures} = results
logger.log {user_id, individualFeatures, groupFeatureSets, institutionFeatures, v1Features, bonusFeatures}, 'merging user features'
featureSets = groupFeatureSets.concat [individualFeatures, institutionFeatures, v1Features, bonusFeatures]
features = _.reduce(featureSets, FeaturesUpdater._mergeFeatures, Settings.defaultFeatures)
logger.log {user_id, features}, 'updating user features'

View file

@ -83,6 +83,27 @@ describe "FeatureUpdater.refreshFeatures", ->
))
done()
describe "when the user has affiliations", ->
beforeEach ->
@institutionPlan = settings.plans.find (plan) ->
plan.planCode == settings.institutionPlanCode
@email = @user.emails[0].email
affiliationData =
email: @email
institution: { licence: 'pro_plus' }
MockV1Api.setAffiliations [affiliationData]
it "should not set their features if email is not confirmed", (done) ->
syncUserAndGetFeatures @user, (error, features) =>
expect(features).to.deep.equal(settings.defaultFeatures)
done()
it "should set their features if email is confirmed", (done) ->
@user.confirmEmail @email, (error) =>
syncUserAndGetFeatures @user, (error, features) =>
expect(features).to.deep.equal(@institutionPlan.features)
done()
describe "when the user is due bonus features and has extra features that no longer apply", ->
beforeEach ->
User.update {

View file

@ -26,6 +26,10 @@ module.exports = MockV1Api =
syncUserFeatures: sinon.stub()
affiliations: []
setAffiliations: (affiliations) -> @affiliations = affiliations
run: () ->
app.get "/api/v1/sharelatex/users/:v1_user_id/plan_code", (req, res, next) =>
user = @users[req.params.v1_user_id]
@ -43,7 +47,7 @@ module.exports = MockV1Api =
res.json exportId: @exportId
app.get "/api/v2/users/:userId/affiliations", (req, res, next) =>
res.json []
res.json @affiliations
app.post "/api/v2/users/:userId/affiliations", (req, res, next) =>
res.sendStatus 201

View file

@ -100,6 +100,11 @@ class User
@emails.push(email: email, createdAt: new Date())
UserUpdater.addEmailAddress @id, email, callback
confirmEmail: (email, callback = (error) ->) ->
for emailData, idx in @emails
@emails[idx].confirmedAt = new Date() if emailData.email == email
UserUpdater.confirmEmail @id, email, callback
ensure_admin: (callback = (error) ->) ->
db.users.update {_id: ObjectId(@id)}, { $set: { isAdmin: true }}, callback

View file

@ -56,6 +56,7 @@ module.exports =
defaultFeatures: features.personal
defaultPlanCode: 'personal'
institutionPlanCode: 'professional'
plans: plans = [{
planCode: "v1_free"

View file

@ -0,0 +1,96 @@
SandboxedModule = require('sandboxed-module')
assert = require('assert')
require('chai').should()
expect = require('chai').expect
sinon = require('sinon')
modulePath = require('path').join __dirname, '../../../../app/js/Features/Institutions/InstitutionsFeatures.js'
describe 'InstitutionsFeatures', ->
beforeEach ->
@UserGetter = getUserFullEmails: sinon.stub()
@PlansLocator = findLocalPlanInSettings: sinon.stub()
@institutionPlanCode = 'institution_plan_code'
@InstitutionsFeatures = SandboxedModule.require modulePath, requires:
'../User/UserGetter': @UserGetter
'../Subscription/PlansLocator': @PlansLocator
'settings-sharelatex': institutionPlanCode: @institutionPlanCode
'logger-sharelatex':
log:->
err:->
@userId = '12345abcde'
describe "hasLicence", ->
it 'should handle error', (done)->
@UserGetter.getUserFullEmails.yields(new Error('Nope'))
@InstitutionsFeatures.hasLicence @userId, (error, hasLicence) ->
expect(error).to.exist
done()
it 'should return false if user has no affiliations', (done) ->
@UserGetter.getUserFullEmails.yields(null, [])
@InstitutionsFeatures.hasLicence @userId, (error, hasLicence) ->
expect(error).to.not.exist
expect(hasLicence).to.be.false
done()
it 'should return false if user has no confirmed affiliations', (done) ->
affiliations = [
{ confirmedAt: null, affiliation: institution: { licence: 'pro_plus' } }
]
@UserGetter.getUserFullEmails.yields(null, affiliations)
@InstitutionsFeatures.hasLicence @userId, (error, hasLicence) ->
expect(error).to.not.exist
expect(hasLicence).to.be.false
done()
it 'should return false if user has no paid affiliations', (done) ->
affiliations = [
{ confirmedAt: new Date(), affiliation: institution: { licence: 'free' } }
]
@UserGetter.getUserFullEmails.yields(null, affiliations)
@InstitutionsFeatures.hasLicence @userId, (error, hasLicence) ->
expect(error).to.not.exist
expect(hasLicence).to.be.false
done()
it 'should return true if user has confirmed paid affiliation', (done)->
affiliations = [
{ confirmedAt: new Date(), affiliation: institution: { licence: 'pro_plus' } }
{ confirmedAt: new Date(), affiliation: institution: { licence: 'free' } }
{ confirmedAt: null, affiliation: institution: { licence: 'pro' } }
{ confirmedAt: null, affiliation: institution: { licence: null } }
{ confirmedAt: new Date(), affiliation: institution: {} }
]
@UserGetter.getUserFullEmails.yields(null, affiliations)
@InstitutionsFeatures.hasLicence @userId, (error, hasLicence) ->
expect(error).to.not.exist
expect(hasLicence).to.be.true
done()
describe "getInstitutionsFeatures", ->
beforeEach ->
@InstitutionsFeatures.hasLicence = sinon.stub()
@testFeatures = features: { institution: 'all' }
@PlansLocator.findLocalPlanInSettings.withArgs(@institutionPlanCode).returns(@testFeatures)
it 'should handle error', (done)->
@InstitutionsFeatures.hasLicence.yields(new Error('Nope'))
@InstitutionsFeatures.getInstitutionsFeatures @userId, (error, features) ->
expect(error).to.exist
done()
it 'should return no feaures if user has no plan code', (done) ->
@InstitutionsFeatures.hasLicence.yields(null, false)
@InstitutionsFeatures.getInstitutionsFeatures @userId, (error, features) ->
expect(error).to.not.exist
expect(features).to.deep.equal {}
done()
it 'should return feaures if user has affiliations plan code', (done) ->
@InstitutionsFeatures.hasLicence.yields(null, true)
@InstitutionsFeatures.getInstitutionsFeatures @userId, (error, features) =>
expect(error).to.not.exist
expect(features).to.deep.equal @testFeatures.features
done()

View file

@ -19,6 +19,7 @@ describe "FeaturesUpdater", ->
'settings-sharelatex': @Settings = {}
"../Referal/ReferalFeatures" : @ReferalFeatures = {}
"./V1SubscriptionManager": @V1SubscriptionManager = {}
'../Institutions/InstitutionsFeatures': @InstitutionsFeatures = {}
describe "refreshFeatures", ->
beforeEach ->
@ -26,6 +27,7 @@ describe "FeaturesUpdater", ->
@UserFeaturesUpdater.updateFeatures = sinon.stub().yields()
@FeaturesUpdater._getIndividualFeatures = sinon.stub().yields(null, { 'individual': 'features' })
@FeaturesUpdater._getGroupFeatureSets = sinon.stub().yields(null, [{ 'group': 'features' }, { 'group': 'features2' }])
@InstitutionsFeatures.getInstitutionsFeatures = sinon.stub().yields(null, { 'institutions': 'features' })
@FeaturesUpdater._getV1Features = sinon.stub().yields(null, { 'v1': 'features' })
@ReferalFeatures.getBonusFeatures = sinon.stub().yields(null, { 'bonus': 'features' })
@FeaturesUpdater._mergeFeatures = sinon.stub().returns({'merged': 'features'})
@ -45,6 +47,11 @@ describe "FeaturesUpdater", ->
.calledWith(@user_id)
.should.equal true
it "should get the institution features", ->
@InstitutionsFeatures.getInstitutionsFeatures
.calledWith(@user_id)
.should.equal true
it "should get the v1 features", ->
@FeaturesUpdater._getV1Features
.calledWith(@user_id)
@ -65,6 +72,9 @@ describe "FeaturesUpdater", ->
@FeaturesUpdater._mergeFeatures.calledWith(sinon.match.any, { 'group': 'features' }).should.equal true
@FeaturesUpdater._mergeFeatures.calledWith(sinon.match.any, { 'group': 'features2' }).should.equal true
it "should merge the institutions features", ->
@FeaturesUpdater._mergeFeatures.calledWith(sinon.match.any, { 'institutions': 'features' }).should.equal true
it "should merge the v1 features", ->
@FeaturesUpdater._mergeFeatures.calledWith(sinon.match.any, { 'v1': 'features' }).should.equal true