mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-05-28 11:31:55 +02:00
Merge pull request #568 from sharelatex/ja-merge-subscriptions
Correctly sync multiple sources of upgraded features
This commit is contained in:
@@ -9,7 +9,7 @@ COFFEE := node_modules/.bin/coffee $(COFFEE_OPTIONS)
|
||||
GRUNT := node_modules/.bin/grunt
|
||||
APP_COFFEE_FILES := $(shell find app/coffee -name '*.coffee')
|
||||
FRONT_END_COFFEE_FILES := $(shell find public/coffee -name '*.coffee')
|
||||
TEST_COFFEE_FILES := $(shell find test -name '*.coffee')
|
||||
TEST_COFFEE_FILES := $(shell find test/*/coffee -name '*.coffee')
|
||||
MODULE_MAIN_COFFEE_FILES := $(shell find modules -type f -wholename '*main/index.coffee')
|
||||
MODULE_IDE_COFFEE_FILES := $(shell find modules -type f -wholename '*ide/index.coffee')
|
||||
COFFEE_FILES := app.coffee $(APP_COFFEE_FILES) $(FRONT_END_COFFEE_FILES) $(TEST_COFFEE_FILES)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
_ = require("underscore")
|
||||
logger = require('logger-sharelatex')
|
||||
User = require('../../models/User').User
|
||||
SubscriptionLocator = require "../Subscription/SubscriptionLocator"
|
||||
Settings = require "settings-sharelatex"
|
||||
FeaturesUpdater = require "../Subscription/FeaturesUpdater"
|
||||
|
||||
module.exports = ReferalAllocator =
|
||||
allocate: (referal_id, new_user_id, referal_source, referal_medium, callback = ->)->
|
||||
@@ -25,50 +25,6 @@ module.exports = ReferalAllocator =
|
||||
if err?
|
||||
logger.err err:err, referal_id:referal_id, new_user_id:new_user_id, "something went wrong allocating referal"
|
||||
return callback(err)
|
||||
ReferalAllocator.assignBonus user._id, callback
|
||||
FeaturesUpdater.refreshFeatures user._id, callback
|
||||
else
|
||||
callback()
|
||||
|
||||
|
||||
|
||||
assignBonus: (user_id, callback = (error) ->) ->
|
||||
query = _id: user_id
|
||||
User.findOne query, (error, user) ->
|
||||
return callback(error) if error
|
||||
return callback(new Error("user not found #{user_id} for assignBonus")) if !user?
|
||||
logger.log user_id: user_id, refered_user_count: user.refered_user_count, "assigning bonus"
|
||||
if user.refered_user_count? and user.refered_user_count > 0
|
||||
newFeatures = ReferalAllocator._calculateFeatures(user)
|
||||
if _.isEqual newFeatures, user.features
|
||||
return callback()
|
||||
User.update query, { $set: features: newFeatures }, callback
|
||||
else
|
||||
callback()
|
||||
|
||||
_calculateFeatures : (user)->
|
||||
bonusLevel = ReferalAllocator._getBonusLevel(user)
|
||||
currentFeatures = _.clone(user.features) #need to clone because we exend with underscore later
|
||||
betterBonusFeatures = {}
|
||||
_.each Settings.bonus_features["#{bonusLevel}"], (bonusLevel, key)->
|
||||
currentLevel = user?.features?[key]
|
||||
if _.isBoolean(currentLevel) and currentLevel == false
|
||||
betterBonusFeatures[key] = bonusLevel
|
||||
|
||||
if _.isNumber(currentLevel)
|
||||
if currentLevel == -1
|
||||
return
|
||||
bonusIsGreaterThanCurrent = currentLevel < bonusLevel
|
||||
if bonusIsGreaterThanCurrent or bonusLevel == -1
|
||||
betterBonusFeatures[key] = bonusLevel
|
||||
newFeatures = _.extend(currentFeatures, betterBonusFeatures)
|
||||
return newFeatures
|
||||
|
||||
|
||||
_getBonusLevel: (user)->
|
||||
highestBonusLevel = 0
|
||||
_.each _.keys(Settings.bonus_features), (level)->
|
||||
levelIsLessThanUser = level <= user.refered_user_count
|
||||
levelIsMoreThanCurrentHighest = level >= highestBonusLevel
|
||||
if levelIsLessThanUser and levelIsMoreThanCurrentHighest
|
||||
highestBonusLevel = level
|
||||
return highestBonusLevel
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
_ = require("underscore")
|
||||
logger = require('logger-sharelatex')
|
||||
User = require('../../models/User').User
|
||||
Settings = require "settings-sharelatex"
|
||||
|
||||
module.exports = ReferalFeatures =
|
||||
getBonusFeatures: (user_id, callback = (error) ->) ->
|
||||
query = _id: user_id
|
||||
User.findOne query, (error, user) ->
|
||||
return callback(error) if error
|
||||
return callback(new Error("user not found #{user_id} for assignBonus")) if !user?
|
||||
logger.log user_id: user_id, refered_user_count: user.refered_user_count, "assigning bonus"
|
||||
if user.refered_user_count? and user.refered_user_count > 0
|
||||
newFeatures = ReferalFeatures._calculateFeatures(user)
|
||||
callback null, newFeatures
|
||||
else
|
||||
callback null, {}
|
||||
|
||||
_calculateFeatures : (user)->
|
||||
bonusLevel = ReferalFeatures._getBonusLevel(user)
|
||||
currentFeatures = _.clone(user.features) #need to clone because we exend with underscore later
|
||||
betterBonusFeatures = {}
|
||||
_.each Settings.bonus_features["#{bonusLevel}"], (bonusLevel, key)->
|
||||
currentLevel = user?.features?[key]
|
||||
if _.isBoolean(currentLevel) and currentLevel == false
|
||||
betterBonusFeatures[key] = bonusLevel
|
||||
|
||||
if _.isNumber(currentLevel)
|
||||
if currentLevel == -1
|
||||
return
|
||||
bonusIsGreaterThanCurrent = currentLevel < bonusLevel
|
||||
if bonusIsGreaterThanCurrent or bonusLevel == -1
|
||||
betterBonusFeatures[key] = bonusLevel
|
||||
newFeatures = _.extend(currentFeatures, betterBonusFeatures)
|
||||
return newFeatures
|
||||
|
||||
_getBonusLevel: (user)->
|
||||
highestBonusLevel = 0
|
||||
_.each _.keys(Settings.bonus_features), (level)->
|
||||
levelIsLessThanUser = level <= user.refered_user_count
|
||||
levelIsMoreThanCurrentHighest = level >= highestBonusLevel
|
||||
if levelIsLessThanUser and levelIsMoreThanCurrentHighest
|
||||
highestBonusLevel = level
|
||||
return highestBonusLevel
|
||||
@@ -0,0 +1,83 @@
|
||||
async = require("async")
|
||||
PlansLocator = require("./PlansLocator")
|
||||
_ = require("underscore")
|
||||
SubscriptionLocator = require("./SubscriptionLocator")
|
||||
UserFeaturesUpdater = require("./UserFeaturesUpdater")
|
||||
Settings = require("settings-sharelatex")
|
||||
logger = require("logger-sharelatex")
|
||||
ReferalFeatures = require("../Referal/ReferalFeatures")
|
||||
V1SubscriptionManager = require("./V1SubscriptionManager")
|
||||
|
||||
oneMonthInSeconds = 60 * 60 * 24 * 30
|
||||
|
||||
module.exports = FeaturesUpdater =
|
||||
refreshFeatures: (user_id, callback)->
|
||||
jobs =
|
||||
individualFeatures: (cb) -> FeaturesUpdater._getIndividualFeatures user_id, cb
|
||||
groupFeatureSets: (cb) -> FeaturesUpdater._getGroupFeatureSets user_id, cb
|
||||
v1Features: (cb) -> FeaturesUpdater._getV1Features user_id, cb
|
||||
bonusFeatures: (cb) -> ReferalFeatures.getBonusFeatures user_id, cb
|
||||
async.series jobs, (err, results)->
|
||||
if err?
|
||||
logger.err err:err, user_id:user_id,
|
||||
"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]
|
||||
features = _.reduce(featureSets, FeaturesUpdater._mergeFeatures, Settings.defaultFeatures)
|
||||
|
||||
logger.log {user_id, features}, 'updating user features'
|
||||
UserFeaturesUpdater.updateFeatures user_id, features, callback
|
||||
|
||||
_getIndividualFeatures: (user_id, callback = (error, features = {}) ->) ->
|
||||
SubscriptionLocator.getUsersSubscription user_id, (err, sub)->
|
||||
callback err, FeaturesUpdater._subscriptionToFeatures(sub)
|
||||
|
||||
_getGroupFeatureSets: (user_id, callback = (error, featureSets = []) ->) ->
|
||||
SubscriptionLocator.getGroupSubscriptionsMemberOf user_id, (err, subs) ->
|
||||
callback err, (subs or []).map FeaturesUpdater._subscriptionToFeatures
|
||||
|
||||
_getV1Features: (user_id, callback = (error, features = {}) ->) ->
|
||||
V1SubscriptionManager.getPlanCodeFromV1 user_id, (err, planCode) ->
|
||||
callback err, FeaturesUpdater._planCodeToFeatures(planCode)
|
||||
|
||||
_mergeFeatures: (featuresA, featuresB) ->
|
||||
features = Object.assign({}, featuresA)
|
||||
for key, value of featuresB
|
||||
# Special merging logic for non-boolean features
|
||||
if key == 'compileGroup'
|
||||
if features['compileGroup'] == 'priority' or featuresB['compileGroup'] == 'priority'
|
||||
features['compileGroup'] = 'priority'
|
||||
else
|
||||
features['compileGroup'] = 'standard'
|
||||
else if key == 'collaborators'
|
||||
if features['collaborators'] == -1 or featuresB['collaborators'] == -1
|
||||
features['collaborators'] = -1
|
||||
else
|
||||
features['collaborators'] = Math.max(
|
||||
features['collaborators'] or 0,
|
||||
featuresB['collaborators'] or 0
|
||||
)
|
||||
else if key == 'compileTimeout'
|
||||
features['compileTimeout'] = Math.max(
|
||||
features['compileTimeout'] or 0,
|
||||
featuresB['compileTimeout'] or 0
|
||||
)
|
||||
else
|
||||
# Boolean keys, true is better
|
||||
features[key] = features[key] or featuresB[key]
|
||||
return features
|
||||
|
||||
_subscriptionToFeatures: (subscription) ->
|
||||
FeaturesUpdater._planCodeToFeatures(subscription?.planCode)
|
||||
|
||||
_planCodeToFeatures: (planCode) ->
|
||||
if !planCode?
|
||||
return {}
|
||||
plan = PlansLocator.findLocalPlanInSettings planCode
|
||||
if !plan?
|
||||
return {}
|
||||
else
|
||||
return plan.features
|
||||
@@ -9,6 +9,7 @@ logger = require('logger-sharelatex')
|
||||
GeoIpLookup = require("../../infrastructure/GeoIpLookup")
|
||||
SubscriptionDomainHandler = require("./SubscriptionDomainHandler")
|
||||
UserGetter = require "../User/UserGetter"
|
||||
FeaturesUpdater = require './FeaturesUpdater'
|
||||
|
||||
module.exports = SubscriptionController =
|
||||
|
||||
@@ -237,3 +238,9 @@ module.exports = SubscriptionController =
|
||||
return next(error) if error?
|
||||
req.body = body
|
||||
next()
|
||||
|
||||
refreshUserFeatures: (req, res, next) ->
|
||||
{user_id} = req.params
|
||||
FeaturesUpdater.refreshFeatures user_id, (error) ->
|
||||
return next(error) if error?
|
||||
res.sendStatus 200
|
||||
@@ -28,8 +28,8 @@ module.exports =
|
||||
getSubscriptionByMemberIdAndId: (user_id, subscription_id, callback)->
|
||||
Subscription.findOne {member_ids: user_id, _id:subscription_id}, {_id:1}, callback
|
||||
|
||||
getGroupSubscriptionMemberOf: (user_id, callback)->
|
||||
Subscription.findOne {member_ids: user_id}, {_id:1, planCode:1}, callback
|
||||
getGroupSubscriptionsMemberOf: (user_id, callback)->
|
||||
Subscription.find {member_ids: user_id}, {_id:1, planCode:1}, callback
|
||||
|
||||
getGroupsWithEmailInvite: (email, callback) ->
|
||||
Subscription.find { invited_emails: email }, callback
|
||||
@@ -46,4 +46,6 @@ module.exports =
|
||||
webRouter.get "/user/subscription/upgrade-annual", AuthenticationController.requireLogin(), SubscriptionController.renderUpgradeToAnnualPlanPage
|
||||
webRouter.post "/user/subscription/upgrade-annual", AuthenticationController.requireLogin(), SubscriptionController.processUpgradeToAnnualPlan
|
||||
|
||||
# Currently used in acceptance tests only, as a way to trigger the syncing logic
|
||||
publicApiRouter.post "/user/:user_id/features/sync", AuthenticationController.httpAuth, SubscriptionController.refreshUserFeatures
|
||||
|
||||
|
||||
@@ -2,26 +2,26 @@ async = require("async")
|
||||
_ = require("underscore")
|
||||
Subscription = require('../../models/Subscription').Subscription
|
||||
SubscriptionLocator = require("./SubscriptionLocator")
|
||||
UserFeaturesUpdater = require("./UserFeaturesUpdater")
|
||||
PlansLocator = require("./PlansLocator")
|
||||
Settings = require("settings-sharelatex")
|
||||
logger = require("logger-sharelatex")
|
||||
ObjectId = require('mongoose').Types.ObjectId
|
||||
ReferalAllocator = require("../Referal/ReferalAllocator")
|
||||
ObjectId = require('mongoose').Types.ObjectId
|
||||
FeaturesUpdater = require('./FeaturesUpdater')
|
||||
|
||||
oneMonthInSeconds = 60 * 60 * 24 * 30
|
||||
|
||||
module.exports = SubscriptionUpdater =
|
||||
|
||||
syncSubscription: (recurlySubscription, adminUser_id, callback) ->
|
||||
logger.log adminUser_id:adminUser_id, recurlySubscription:recurlySubscription, "syncSubscription, creating new if subscription does not exist"
|
||||
SubscriptionLocator.getUsersSubscription adminUser_id, (err, subscription)->
|
||||
return callback(err) if err?
|
||||
if subscription?
|
||||
logger.log adminUser_id:adminUser_id, recurlySubscription:recurlySubscription, "subscription does exist"
|
||||
SubscriptionUpdater._updateSubscriptionFromRecurly recurlySubscription, subscription, callback
|
||||
else
|
||||
logger.log adminUser_id:adminUser_id, recurlySubscription:recurlySubscription, "subscription does not exist, creating a new one"
|
||||
SubscriptionUpdater._createNewSubscription adminUser_id, (err, subscription)->
|
||||
return callback(err) if err?
|
||||
SubscriptionUpdater._updateSubscriptionFromRecurly recurlySubscription, subscription, callback
|
||||
|
||||
addUserToGroup: (adminUser_id, user_id, callback)->
|
||||
@@ -34,7 +34,7 @@ module.exports = SubscriptionUpdater =
|
||||
if err?
|
||||
logger.err err:err, searchOps:searchOps, insertOperation:insertOperation, "error findy and modify add user to group"
|
||||
return callback(err)
|
||||
UserFeaturesUpdater.updateFeatures user_id, subscription.planCode, callback
|
||||
FeaturesUpdater.refreshFeatures user_id, callback
|
||||
|
||||
addEmailInviteToGroup: (adminUser_id, email, callback) ->
|
||||
logger.log {adminUser_id, email}, "adding email into mongo subscription"
|
||||
@@ -53,7 +53,7 @@ module.exports = SubscriptionUpdater =
|
||||
if err?
|
||||
logger.err err:err, searchOps:searchOps, removeOperation:removeOperation, "error removing user from group"
|
||||
return callback(err)
|
||||
SubscriptionUpdater._setUsersMinimumFeatures user_id, callback
|
||||
FeaturesUpdater.refreshFeatures user_id, callback
|
||||
|
||||
removeEmailInviteFromGroup: (adminUser_id, email, callback)->
|
||||
Subscription.update {
|
||||
@@ -62,9 +62,6 @@ module.exports = SubscriptionUpdater =
|
||||
invited_emails: email
|
||||
}, callback
|
||||
|
||||
refreshSubscription: (user_id, callback=(err)->) ->
|
||||
SubscriptionUpdater._setUsersMinimumFeatures user_id, callback
|
||||
|
||||
deleteSubscription: (subscription_id, callback = (error) ->) ->
|
||||
SubscriptionLocator.getSubscription subscription_id, (err, subscription) ->
|
||||
return callback(err) if err?
|
||||
@@ -72,7 +69,7 @@ module.exports = SubscriptionUpdater =
|
||||
logger.log {subscription_id, affected_user_ids}, "deleting subscription and downgrading users"
|
||||
Subscription.remove {_id: ObjectId(subscription_id)}, (err) ->
|
||||
return callback(err) if err?
|
||||
async.mapSeries affected_user_ids, SubscriptionUpdater._setUsersMinimumFeatures, callback
|
||||
async.mapSeries affected_user_ids, FeaturesUpdater.refreshFeatures, callback
|
||||
|
||||
_createNewSubscription: (adminUser_id, callback)->
|
||||
logger.log adminUser_id:adminUser_id, "creating new subscription"
|
||||
@@ -100,43 +97,5 @@ module.exports = SubscriptionUpdater =
|
||||
allIds = _.union subscription.member_ids, [subscription.admin_id]
|
||||
jobs = allIds.map (user_id)->
|
||||
return (cb)->
|
||||
SubscriptionUpdater._setUsersMinimumFeatures user_id, cb
|
||||
FeaturesUpdater.refreshFeatures user_id, cb
|
||||
async.series jobs, callback
|
||||
|
||||
_setUsersMinimumFeatures: (user_id, callback)->
|
||||
jobs =
|
||||
subscription: (cb)->
|
||||
SubscriptionLocator.getUsersSubscription user_id, cb
|
||||
groupSubscription: (cb)->
|
||||
SubscriptionLocator.getGroupSubscriptionMemberOf user_id, cb
|
||||
v1PlanCode: (cb) ->
|
||||
Modules = require '../../infrastructure/Modules'
|
||||
Modules.hooks.fire 'getV1PlanCode', user_id, (err, results) ->
|
||||
cb(err, results?[0] || null)
|
||||
async.series jobs, (err, results)->
|
||||
if err?
|
||||
logger.err err:err, user_id:user_id,
|
||||
"error getting subscription or group for _setUsersMinimumFeatures"
|
||||
return callback(err)
|
||||
{subscription, groupSubscription, v1PlanCode} = results
|
||||
# Group Subscription
|
||||
if groupSubscription? and groupSubscription.planCode?
|
||||
logger.log user_id:user_id, "using group which user is memor of for features"
|
||||
UserFeaturesUpdater.updateFeatures user_id, groupSubscription.planCode, callback
|
||||
# Personal Subscription
|
||||
else if subscription? and subscription.planCode? and subscription.planCode != Settings.defaultPlanCode
|
||||
logger.log user_id:user_id, "using users subscription plan code for features"
|
||||
UserFeaturesUpdater.updateFeatures user_id, subscription.planCode, callback
|
||||
# V1 Subscription
|
||||
else if v1PlanCode?
|
||||
logger.log user_id: user_id, "using the V1 plan for features"
|
||||
UserFeaturesUpdater.updateFeatures user_id, v1PlanCode, callback
|
||||
# Default
|
||||
else
|
||||
logger.log user_id:user_id, "using default features for user with no subscription or group"
|
||||
UserFeaturesUpdater.updateFeatures user_id, Settings.defaultPlanCode, (err)->
|
||||
if err?
|
||||
logger.err err:err, user_id:user_id, "Error setting minimum user feature"
|
||||
return callback(err)
|
||||
ReferalAllocator.assignBonus user_id, callback
|
||||
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
logger = require("logger-sharelatex")
|
||||
User = require('../../models/User').User
|
||||
PlansLocator = require("./PlansLocator")
|
||||
|
||||
module.exports =
|
||||
|
||||
updateFeatures: (user_id, plan_code, callback = (err, features)->)->
|
||||
updateFeatures: (user_id, features, callback = (err, features)->)->
|
||||
conditions = _id:user_id
|
||||
update = {}
|
||||
plan = PlansLocator.findLocalPlanInSettings(plan_code)
|
||||
logger.log user_id:user_id, features:plan.features, plan_code:plan_code, "updating users features"
|
||||
update["features.#{key}"] = value for key, value of plan.features
|
||||
logger.log user_id:user_id, features:features, "updating users features"
|
||||
update["features.#{key}"] = value for key, value of features
|
||||
User.update conditions, update, (err)->
|
||||
callback err, plan.features
|
||||
callback err, features
|
||||
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
UserGetter = require "../User/UserGetter"
|
||||
request = require "request"
|
||||
settings = require "settings-sharelatex"
|
||||
logger = require "logger-sharelatex"
|
||||
|
||||
module.exports = V1SubscriptionManager =
|
||||
# Returned planCode = 'v1_pro' | 'v1_pro_plus' | 'v1_student' | 'v1_free' | null
|
||||
# For this to work, we need plans in settings with plan-codes:
|
||||
# - 'v1_pro'
|
||||
# - 'v1_pro_plus'
|
||||
# - 'v1_student'
|
||||
# - 'v1_free'
|
||||
getPlanCodeFromV1: (userId, callback=(err, planCode)->) ->
|
||||
logger.log {userId}, "[V1SubscriptionManager] fetching v1 plan for user"
|
||||
UserGetter.getUser userId, {'overleaf.id': 1}, (err, user) ->
|
||||
return callback(err) if err?
|
||||
v1Id = user?.overleaf?.id
|
||||
if !v1Id?
|
||||
logger.log {userId}, "[V1SubscriptionManager] no v1 id found for user"
|
||||
return callback(null, null)
|
||||
V1SubscriptionManager._v1PlanRequest v1Id, (err, body) ->
|
||||
return callback(err) if err?
|
||||
planName = body?.plan_name
|
||||
logger.log {userId, planName, body}, "[V1SubscriptionManager] fetched v1 plan for user"
|
||||
if planName in ['pro', 'pro_plus', 'student', 'free']
|
||||
planName = "v1_#{planName}"
|
||||
else
|
||||
# Throw away 'anonymous', etc as being equivalent to null
|
||||
planName = null
|
||||
return callback(null, planName)
|
||||
|
||||
_v1PlanRequest: (v1Id, callback=(err, body)->) ->
|
||||
if !settings?.apis?.v1
|
||||
return callback null, null
|
||||
request {
|
||||
method: 'GET',
|
||||
url: settings.apis.v1.url +
|
||||
"/api/v1/sharelatex/users/#{v1Id}/plan_code"
|
||||
auth:
|
||||
user: settings.apis.v1.user
|
||||
pass: settings.apis.v1.pass
|
||||
sendImmediately: true
|
||||
json: true,
|
||||
timeout: 5 * 1000
|
||||
}, (error, response, body) ->
|
||||
return callback(error) if error?
|
||||
if 200 <= response.statusCode < 300
|
||||
return callback null, body
|
||||
else
|
||||
return callback new Error("non-success code from v1: #{response.statusCode}")
|
||||
@@ -17,6 +17,7 @@ services:
|
||||
PROJECT_HISTORY_ENABLED: 'true'
|
||||
ENABLED_LINKED_FILE_TYPES: 'url'
|
||||
LINKED_URL_PROXY: 'http://localhost:6543'
|
||||
SHARELATEX_CONFIG: /app/test/acceptance/config/settings.test.coffee
|
||||
depends_on:
|
||||
- redis
|
||||
- mongo
|
||||
|
||||
@@ -16,6 +16,7 @@ createInvite = (sendingUser, projectId, email, callback=(err, invite)->) ->
|
||||
privileges: 'readAndWrite'
|
||||
}, (err, response, body) ->
|
||||
return callback(err) if err
|
||||
expect(response.statusCode).to.equal 200
|
||||
callback(null, body.invite)
|
||||
|
||||
createProject = (owner, projectName, callback=(err, projectId, project)->) ->
|
||||
@@ -207,9 +208,9 @@ describe "ProjectInviteTests", ->
|
||||
@email = 'smoketestuser@example.com'
|
||||
@projectName = 'sharing test'
|
||||
Async.series [
|
||||
(cb) => @user.login cb
|
||||
(cb) => @user.logout cb
|
||||
(cb) => @user.ensureUserExists cb
|
||||
(cb) => @sendingUser.login cb
|
||||
(cb) => @sendingUser.setFeatures { collaborators: 10 }, cb
|
||||
], done
|
||||
|
||||
describe 'creating invites', ->
|
||||
@@ -266,7 +267,7 @@ describe "ProjectInviteTests", ->
|
||||
(cb) => expectInvitesInJoinProjectCount @sendingUser, @projectId, 0, cb
|
||||
], done
|
||||
|
||||
it 'should allow the project owner to many invites at once', (done) ->
|
||||
it 'should allow the project owner to create many invites at once', (done) ->
|
||||
@inviteOne = null
|
||||
@inviteTwo = null
|
||||
Async.series [
|
||||
|
||||
151
services/web/test/acceptance/coffee/SubscriptionTests.coffee
Normal file
151
services/web/test/acceptance/coffee/SubscriptionTests.coffee
Normal file
@@ -0,0 +1,151 @@
|
||||
expect = require("chai").expect
|
||||
async = require("async")
|
||||
UserClient = require "./helpers/User"
|
||||
request = require "./helpers/request"
|
||||
settings = require "settings-sharelatex"
|
||||
{ObjectId} = require("../../../app/js/infrastructure/mongojs")
|
||||
Subscription = require("../../../app/js/models/Subscription").Subscription
|
||||
User = require("../../../app/js/models/User").User
|
||||
|
||||
MockV1Api = require "./helpers/MockV1Api"
|
||||
|
||||
syncUserAndGetFeatures = (user, callback = (error, features) ->) ->
|
||||
request {
|
||||
method: 'POST',
|
||||
url: "/user/#{user._id}/features/sync",
|
||||
auth:
|
||||
user: 'sharelatex'
|
||||
pass: 'password'
|
||||
sendImmediately: true
|
||||
}, (error, response, body) ->
|
||||
throw error if error?
|
||||
expect(response.statusCode).to.equal 200
|
||||
User.findById user._id, (error, user) ->
|
||||
return callback(error) if error?
|
||||
features = user.toObject().features
|
||||
delete features.$init # mongoose internals
|
||||
return callback null, features
|
||||
|
||||
describe "Subscriptions", ->
|
||||
beforeEach (done) ->
|
||||
@user = new UserClient()
|
||||
@user.ensureUserExists (error) ->
|
||||
throw error if error?
|
||||
done()
|
||||
|
||||
describe "when user has no subscriptions", ->
|
||||
it "should set their features to the basic set", (done) ->
|
||||
syncUserAndGetFeatures @user, (error, features) =>
|
||||
throw error if error?
|
||||
expect(features).to.deep.equal(settings.defaultFeatures)
|
||||
done()
|
||||
|
||||
describe "when the user has an individual subscription", ->
|
||||
beforeEach ->
|
||||
Subscription.create {
|
||||
admin_id: @user._id
|
||||
planCode: 'collaborator'
|
||||
customAccount: true
|
||||
} # returns a promise
|
||||
|
||||
it "should set their features to the upgraded set", (done) ->
|
||||
syncUserAndGetFeatures @user, (error, features) =>
|
||||
throw error if error?
|
||||
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", ->
|
||||
beforeEach ->
|
||||
Subscription.create {
|
||||
admin_id: ObjectId()
|
||||
member_ids: [@user._id]
|
||||
groupAccount: true
|
||||
planCode: 'collaborator'
|
||||
customAccount: true
|
||||
} # returns a promise
|
||||
|
||||
it "should set their features to the upgraded set", (done) ->
|
||||
syncUserAndGetFeatures @user, (error, features) =>
|
||||
throw error if error?
|
||||
plan = settings.plans.find (plan) -> plan.planCode == 'collaborator'
|
||||
expect(features).to.deep.equal(plan.features)
|
||||
done()
|
||||
|
||||
describe "when the user has bonus features", ->
|
||||
beforeEach ->
|
||||
User.update {
|
||||
_id: @user._id
|
||||
}, {
|
||||
refered_user_count: 10
|
||||
} # returns a promise
|
||||
|
||||
it "should set their features to the bonus set", (done) ->
|
||||
syncUserAndGetFeatures @user, (error, features) =>
|
||||
throw error if error?
|
||||
expect(features).to.deep.equal(Object.assign(
|
||||
{}, settings.defaultFeatures, settings.bonus_features[9]
|
||||
))
|
||||
done()
|
||||
|
||||
describe "when the user has a v1 plan", ->
|
||||
beforeEach ->
|
||||
MockV1Api.setUser 42, plan_name: 'free'
|
||||
User.update {
|
||||
_id: @user._id
|
||||
}, {
|
||||
overleaf:
|
||||
id: 42
|
||||
} # returns a promise
|
||||
|
||||
it "should set their features to the v1 plan", (done) ->
|
||||
syncUserAndGetFeatures @user, (error, features) =>
|
||||
throw error if error?
|
||||
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", ->
|
||||
beforeEach ->
|
||||
MockV1Api.setUser 42, plan_name: 'free'
|
||||
User.update {
|
||||
_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", (done) ->
|
||||
syncUserAndGetFeatures @user, (error, features) =>
|
||||
throw error if error?
|
||||
v1plan = settings.plans.find (plan) -> plan.planCode == 'v1_free'
|
||||
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", ->
|
||||
beforeEach (done) ->
|
||||
Subscription.create {
|
||||
admin_id: @user._id
|
||||
planCode: 'professional'
|
||||
customAccount: true
|
||||
}, (error) =>
|
||||
throw error if error?
|
||||
Subscription.create {
|
||||
admin_id: ObjectId()
|
||||
member_ids: [@user._id]
|
||||
groupAccount: true
|
||||
planCode: 'collaborator'
|
||||
customAccount: true
|
||||
}, done
|
||||
return
|
||||
|
||||
it "should set their features to the best set", (done) ->
|
||||
syncUserAndGetFeatures @user, (error, features) =>
|
||||
throw error if error?
|
||||
plan = settings.plans.find (plan) -> plan.planCode == 'professional'
|
||||
expect(features).to.deep.equal(plan.features)
|
||||
done()
|
||||
@@ -5,6 +5,10 @@ bodyParser = require('body-parser')
|
||||
app.use(bodyParser.json())
|
||||
|
||||
module.exports = MockV1Api =
|
||||
users: { }
|
||||
|
||||
setUser: (id, user) ->
|
||||
@users[id] = user
|
||||
|
||||
exportId: null
|
||||
|
||||
@@ -20,6 +24,13 @@ module.exports = MockV1Api =
|
||||
@exportParams = null
|
||||
|
||||
run: () ->
|
||||
app.get "/api/v1/sharelatex/users/:ol_user_id/plan_code", (req, res, next) =>
|
||||
user = @users[req.params.ol_user_id]
|
||||
if user
|
||||
res.json user
|
||||
else
|
||||
res.sendStatus 404
|
||||
|
||||
app.post "/api/v1/sharelatex/exports", (req, res, next) =>
|
||||
#{project, version, pathname}
|
||||
@exportParams = Object.assign({}, req.body)
|
||||
@@ -28,7 +39,7 @@ module.exports = MockV1Api =
|
||||
app.listen 5000, (error) ->
|
||||
throw error if error?
|
||||
.on "error", (error) ->
|
||||
console.error "error starting MockOverleafAPI:", error.message
|
||||
console.error "error starting MockV1Api:", error.message
|
||||
process.exit(1)
|
||||
|
||||
MockV1Api.run()
|
||||
|
||||
@@ -40,6 +40,12 @@ class User
|
||||
@referal_id = user?.referal_id
|
||||
callback(null, @password)
|
||||
|
||||
setFeatures: (features, callback = (error) ->) ->
|
||||
update = {}
|
||||
for key, value of features
|
||||
update["features.#{key}"] = value
|
||||
UserModel.update { _id: @id }, update, callback
|
||||
|
||||
logout: (callback = (error) ->) ->
|
||||
@getCsrfToken (error) =>
|
||||
return callback(error) if error?
|
||||
|
||||
95
services/web/test/acceptance/config/settings.test.coffee
Normal file
95
services/web/test/acceptance/config/settings.test.coffee
Normal file
@@ -0,0 +1,95 @@
|
||||
module.exports =
|
||||
enableSubscriptions: true
|
||||
|
||||
features: features =
|
||||
v1_free:
|
||||
collaborators: 1
|
||||
dropbox: false
|
||||
versioning: false
|
||||
github: true
|
||||
templates: false
|
||||
references: false
|
||||
referencesSearch: false
|
||||
mendeley: true
|
||||
compileTimeout: 60
|
||||
compileGroup: "standard"
|
||||
trackChanges: false
|
||||
personal:
|
||||
collaborators: 1
|
||||
dropbox: false
|
||||
versioning: false
|
||||
github: false
|
||||
templates: false
|
||||
references: false
|
||||
referencesSearch: false
|
||||
mendeley: false
|
||||
compileTimeout: 60
|
||||
compileGroup: "standard"
|
||||
trackChanges: false
|
||||
collaborator:
|
||||
collaborators: 10
|
||||
dropbox: true
|
||||
versioning: true
|
||||
github: true
|
||||
templates: true
|
||||
references: true
|
||||
referencesSearch: true
|
||||
mendeley: true
|
||||
compileTimeout: 180
|
||||
compileGroup: "priority"
|
||||
trackChanges: true
|
||||
professional:
|
||||
collaborators: -1
|
||||
dropbox: true
|
||||
versioning: true
|
||||
github: true
|
||||
templates: true
|
||||
references: true
|
||||
referencesSearch: true
|
||||
mendeley: true
|
||||
compileTimeout: 180
|
||||
compileGroup: "priority"
|
||||
trackChanges: true
|
||||
|
||||
defaultFeatures: features.personal
|
||||
defaultPlanCode: 'personal'
|
||||
|
||||
plans: plans = [{
|
||||
planCode: "v1_free"
|
||||
name: "V1 Free"
|
||||
price: 0
|
||||
features: features.v1_free
|
||||
},{
|
||||
planCode: "personal"
|
||||
name: "Personal"
|
||||
price: 0
|
||||
features: features.personal
|
||||
},{
|
||||
planCode: "collaborator"
|
||||
name: "Collaborator"
|
||||
price: 1500
|
||||
features: features.collaborator
|
||||
},{
|
||||
planCode: "professional"
|
||||
name: "Professional"
|
||||
price: 3000
|
||||
features: features.professional
|
||||
}]
|
||||
|
||||
bonus_features:
|
||||
1:
|
||||
collaborators: 2
|
||||
dropbox: false
|
||||
versioning: false
|
||||
3:
|
||||
collaborators: 4
|
||||
dropbox: false
|
||||
versioning: false
|
||||
6:
|
||||
collaborators: 4
|
||||
dropbox: true
|
||||
versioning: true
|
||||
9:
|
||||
collaborators: -1
|
||||
dropbox: true
|
||||
versioning: true
|
||||
@@ -4,12 +4,12 @@ require('chai').should()
|
||||
sinon = require('sinon')
|
||||
modulePath = require('path').join __dirname, '../../../../app/js/Features/Referal/ReferalAllocator.js'
|
||||
|
||||
describe 'Referalallocator', ->
|
||||
describe 'ReferalAllocator', ->
|
||||
|
||||
beforeEach ->
|
||||
@ReferalAllocator = SandboxedModule.require modulePath, requires:
|
||||
'../../models/User': User: @User = {}
|
||||
"../Subscription/SubscriptionLocator": @SubscriptionLocator = {}
|
||||
"../Subscription/FeaturesUpdater": @FeaturesUpdater = {}
|
||||
"settings-sharelatex": @Settings = {}
|
||||
'logger-sharelatex':
|
||||
log:->
|
||||
@@ -26,7 +26,7 @@ describe 'Referalallocator', ->
|
||||
@referal_source = "bonus"
|
||||
@User.update = sinon.stub().callsArgWith 3, null
|
||||
@User.findOne = sinon.stub().callsArgWith 1, null, { _id: @user_id }
|
||||
@ReferalAllocator.assignBonus = sinon.stub().callsArg 1
|
||||
@FeaturesUpdater.refreshFeatures = sinon.stub().yields()
|
||||
@ReferalAllocator.allocate @referal_id, @new_user_id, @referal_source, @referal_medium, @callback
|
||||
|
||||
it 'should update the referring user with the refered users id', ->
|
||||
@@ -44,8 +44,8 @@ describe 'Referalallocator', ->
|
||||
.calledWith( referal_id: @referal_id )
|
||||
.should.equal true
|
||||
|
||||
it "shoudl assign the user their bonus", ->
|
||||
@ReferalAllocator.assignBonus
|
||||
it "should refresh the user's subscription", ->
|
||||
@FeaturesUpdater.refreshFeatures
|
||||
.calledWith(@user_id)
|
||||
.should.equal true
|
||||
|
||||
@@ -57,7 +57,7 @@ describe 'Referalallocator', ->
|
||||
@referal_source = "public_share"
|
||||
@User.update = sinon.stub().callsArgWith 3, null
|
||||
@User.findOne = sinon.stub().callsArgWith 1, null, { _id: @user_id }
|
||||
@ReferalAllocator.assignBonus = sinon.stub().callsArg 1
|
||||
@FeaturesUpdater.refreshFeatures = sinon.stub().yields()
|
||||
@ReferalAllocator.allocate @referal_id, @new_user_id, @referal_source, @referal_medium, @callback
|
||||
|
||||
it 'should not update the referring user with the refered users id', ->
|
||||
@@ -69,118 +69,7 @@ describe 'Referalallocator', ->
|
||||
.should.equal true
|
||||
|
||||
it "should not assign the user a bonus", ->
|
||||
@ReferalAllocator.assignBonus.called.should.equal false
|
||||
@FeaturesUpdater.refreshFeatures.called.should.equal false
|
||||
|
||||
it "should call the callback", ->
|
||||
@callback.called.should.equal true
|
||||
|
||||
describe "assignBonus", ->
|
||||
beforeEach ->
|
||||
@refered_user_count = 3
|
||||
@Settings.bonus_features =
|
||||
"3":
|
||||
collaborators: 3
|
||||
dropbox: false
|
||||
versioning: false
|
||||
stubbedUser = {
|
||||
refered_user_count: @refered_user_count,
|
||||
features:{collaborators:1, dropbox:false, versioning:false}
|
||||
}
|
||||
|
||||
@User.findOne = sinon.stub().callsArgWith 1, null, stubbedUser
|
||||
@User.update = sinon.stub().callsArgWith 2, null
|
||||
@ReferalAllocator.assignBonus @user_id, @callback
|
||||
|
||||
it "should get the users number of refered user", ->
|
||||
@User.findOne
|
||||
.calledWith(_id: @user_id)
|
||||
.should.equal true
|
||||
|
||||
it "should update the user to bonus features", ->
|
||||
@User.update
|
||||
.calledWith({
|
||||
_id: @user_id
|
||||
}, {
|
||||
$set:
|
||||
features:
|
||||
@Settings.bonus_features[@refered_user_count.toString()]
|
||||
})
|
||||
.should.equal true
|
||||
|
||||
it "should call the callback", ->
|
||||
@callback.called.should.equal true
|
||||
|
||||
describe "when there is nothing to assign", ->
|
||||
|
||||
beforeEach ->
|
||||
@ReferalAllocator._calculateBonuses = sinon.stub().returns({})
|
||||
@stubbedUser =
|
||||
refered_user_count:4
|
||||
features:{collaborators:3, versioning:true, dropbox:false}
|
||||
@Settings.bonus_features =
|
||||
"4":
|
||||
collaborators:3
|
||||
versioning:true
|
||||
dropbox:false
|
||||
@User.findOne = sinon.stub().callsArgWith 1, null, @stubbedUser
|
||||
@User.update = sinon.stub().callsArgWith 2, null
|
||||
|
||||
it "should not call update if there are no bonuses to apply", (done)->
|
||||
@ReferalAllocator.assignBonus @user_id, (err)=>
|
||||
@User.update.called.should.equal false
|
||||
done()
|
||||
|
||||
describe "when the user has better features already", ->
|
||||
|
||||
beforeEach ->
|
||||
@refered_user_count = 3
|
||||
@stubbedUser =
|
||||
refered_user_count:4
|
||||
features:
|
||||
collaborators:3
|
||||
dropbox:false
|
||||
versioning:false
|
||||
@Settings.bonus_features =
|
||||
"4":
|
||||
collaborators: 10
|
||||
dropbox: true
|
||||
versioning: false
|
||||
|
||||
@User.findOne = sinon.stub().callsArgWith 1, null, @stubbedUser
|
||||
@User.update = sinon.stub().callsArgWith 2, null
|
||||
|
||||
it "should not set in in mongo when the feature is better", (done)->
|
||||
@ReferalAllocator.assignBonus @user_id, =>
|
||||
@User.update.calledWith({_id: @user_id }, {$set: features:{dropbox:true, versioning:false, collaborators:10} }).should.equal true
|
||||
done()
|
||||
|
||||
it "should not overright if the user has -1 users", (done)->
|
||||
@stubbedUser.features.collaborators = -1
|
||||
@ReferalAllocator.assignBonus @user_id, =>
|
||||
@User.update.calledWith({_id: @user_id }, {$set: features:{dropbox:true, versioning:false, collaborators:-1} }).should.equal true
|
||||
done()
|
||||
|
||||
describe "when the user is not at a bonus level", ->
|
||||
beforeEach ->
|
||||
@refered_user_count = 0
|
||||
@Settings.bonus_features =
|
||||
"1":
|
||||
collaborators: 3
|
||||
dropbox: false
|
||||
versioning: false
|
||||
@User.findOne = sinon.stub().callsArgWith 1, null, { refered_user_count: @refered_user_count }
|
||||
@User.update = sinon.stub().callsArgWith 2, null
|
||||
@ReferalAllocator.assignBonus @user_id, @callback
|
||||
|
||||
it "should get the users number of refered user", ->
|
||||
@User.findOne
|
||||
.calledWith(_id: @user_id)
|
||||
.should.equal true
|
||||
|
||||
it "should not update the user to bonus features", ->
|
||||
@User.update.called.should.equal false
|
||||
|
||||
it "should call the callback", ->
|
||||
@callback.called.should.equal true
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
SandboxedModule = require('sandboxed-module')
|
||||
assert = require('assert')
|
||||
require('chai').should()
|
||||
sinon = require('sinon')
|
||||
modulePath = require('path').join __dirname, '../../../../app/js/Features/Referal/ReferalFeatures.js'
|
||||
|
||||
describe 'ReferalFeatures', ->
|
||||
|
||||
beforeEach ->
|
||||
@ReferalFeatures = SandboxedModule.require modulePath, requires:
|
||||
'../../models/User': User: @User = {}
|
||||
"settings-sharelatex": @Settings = {}
|
||||
'logger-sharelatex':
|
||||
log:->
|
||||
err:->
|
||||
@callback = sinon.stub()
|
||||
@referal_id = "referal-id-123"
|
||||
@referal_medium = "twitter"
|
||||
@user_id = "user-id-123"
|
||||
@new_user_id = "new-user-id-123"
|
||||
|
||||
describe "getBonusFeatures", ->
|
||||
beforeEach ->
|
||||
@refered_user_count = 3
|
||||
@Settings.bonus_features =
|
||||
"3":
|
||||
collaborators: 3
|
||||
dropbox: false
|
||||
versioning: false
|
||||
stubbedUser = {
|
||||
refered_user_count: @refered_user_count,
|
||||
features:{collaborators:1, dropbox:false, versioning:false}
|
||||
}
|
||||
|
||||
@User.findOne = sinon.stub().callsArgWith 1, null, stubbedUser
|
||||
@ReferalFeatures.getBonusFeatures @user_id, @callback
|
||||
|
||||
it "should get the users number of refered user", ->
|
||||
@User.findOne
|
||||
.calledWith(_id: @user_id)
|
||||
.should.equal true
|
||||
|
||||
it "should call the callback with the features", ->
|
||||
@callback.calledWith(null, @Settings.bonus_features[3]).should.equal true
|
||||
|
||||
describe "when the user is not at a bonus level", ->
|
||||
beforeEach ->
|
||||
@refered_user_count = 0
|
||||
@Settings.bonus_features =
|
||||
"1":
|
||||
collaborators: 3
|
||||
dropbox: false
|
||||
versioning: false
|
||||
@User.findOne = sinon.stub().callsArgWith 1, null, { refered_user_count: @refered_user_count }
|
||||
@ReferalFeatures.getBonusFeatures @user_id, @callback
|
||||
|
||||
it "should get the users number of refered user", ->
|
||||
@User.findOne
|
||||
.calledWith(_id: @user_id)
|
||||
.should.equal true
|
||||
|
||||
it "should call the callback with no features", ->
|
||||
@callback.calledWith(null, {}).should.equal true
|
||||
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
SandboxedModule = require('sandboxed-module')
|
||||
should = require('chai').should()
|
||||
expect = require('chai').expect
|
||||
sinon = require 'sinon'
|
||||
modulePath = "../../../../app/js/Features/Subscription/FeaturesUpdater"
|
||||
assert = require("chai").assert
|
||||
ObjectId = require('mongoose').Types.ObjectId
|
||||
|
||||
describe "FeaturesUpdater", ->
|
||||
|
||||
beforeEach ->
|
||||
@user_id = ObjectId().toString()
|
||||
|
||||
@FeaturesUpdater = SandboxedModule.require modulePath, requires:
|
||||
'./UserFeaturesUpdater': @UserFeaturesUpdater = {}
|
||||
'./SubscriptionLocator': @SubscriptionLocator = {}
|
||||
'./PlansLocator': @PlansLocator = {}
|
||||
"logger-sharelatex": log:->
|
||||
'settings-sharelatex': @Settings = {}
|
||||
"../Referal/ReferalFeatures" : @ReferalFeatures = {}
|
||||
"./V1SubscriptionManager": @V1SubscriptionManager = {}
|
||||
|
||||
describe "refreshFeatures", ->
|
||||
beforeEach ->
|
||||
@UserFeaturesUpdater.updateFeatures = sinon.stub().yields()
|
||||
@FeaturesUpdater._getIndividualFeatures = sinon.stub().yields(null, { 'individual': 'features' })
|
||||
@FeaturesUpdater._getGroupFeatureSets = sinon.stub().yields(null, [{ 'group': 'features' }, { 'group': 'features2' }])
|
||||
@FeaturesUpdater._getV1Features = sinon.stub().yields(null, { 'v1': 'features' })
|
||||
@ReferalFeatures.getBonusFeatures = sinon.stub().yields(null, { 'bonus': 'features' })
|
||||
@FeaturesUpdater._mergeFeatures = sinon.stub().returns({'merged': 'features'})
|
||||
@callback = sinon.stub()
|
||||
@FeaturesUpdater.refreshFeatures @user_id, @callback
|
||||
|
||||
it "should get the individual features", ->
|
||||
@FeaturesUpdater._getIndividualFeatures
|
||||
.calledWith(@user_id)
|
||||
.should.equal true
|
||||
|
||||
it "should get the group features", ->
|
||||
@FeaturesUpdater._getGroupFeatureSets
|
||||
.calledWith(@user_id)
|
||||
.should.equal true
|
||||
|
||||
it "should get the v1 features", ->
|
||||
@FeaturesUpdater._getV1Features
|
||||
.calledWith(@user_id)
|
||||
.should.equal true
|
||||
|
||||
it "should get the bonus features", ->
|
||||
@ReferalFeatures.getBonusFeatures
|
||||
.calledWith(@user_id)
|
||||
.should.equal true
|
||||
|
||||
it "should merge from the default features", ->
|
||||
@FeaturesUpdater._mergeFeatures.calledWith(@Settings.defaultFeatures).should.equal true
|
||||
|
||||
it "should merge the individual features", ->
|
||||
@FeaturesUpdater._mergeFeatures.calledWith(sinon.match.any, { 'individual': 'features' }).should.equal true
|
||||
|
||||
it "should merge the group features", ->
|
||||
@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 v1 features", ->
|
||||
@FeaturesUpdater._mergeFeatures.calledWith(sinon.match.any, { 'v1': 'features' }).should.equal true
|
||||
|
||||
it "should merge the bonus features", ->
|
||||
@FeaturesUpdater._mergeFeatures.calledWith(sinon.match.any, { 'bonus': 'features' }).should.equal true
|
||||
|
||||
it "should update the user with the merged features", ->
|
||||
@UserFeaturesUpdater.updateFeatures
|
||||
.calledWith(@user_id, {'merged': 'features'})
|
||||
.should.equal true
|
||||
|
||||
describe "_mergeFeatures", ->
|
||||
it "should prefer priority over standard for compileGroup", ->
|
||||
expect(@FeaturesUpdater._mergeFeatures({
|
||||
compileGroup: 'priority'
|
||||
}, {
|
||||
compileGroup: 'standard'
|
||||
})).to.deep.equal({
|
||||
compileGroup: 'priority'
|
||||
})
|
||||
expect(@FeaturesUpdater._mergeFeatures({
|
||||
compileGroup: 'standard'
|
||||
}, {
|
||||
compileGroup: 'priority'
|
||||
})).to.deep.equal({
|
||||
compileGroup: 'priority'
|
||||
})
|
||||
expect(@FeaturesUpdater._mergeFeatures({
|
||||
compileGroup: 'priority'
|
||||
}, {
|
||||
compileGroup: 'priority'
|
||||
})).to.deep.equal({
|
||||
compileGroup: 'priority'
|
||||
})
|
||||
expect(@FeaturesUpdater._mergeFeatures({
|
||||
compileGroup: 'standard'
|
||||
}, {
|
||||
compileGroup: 'standard'
|
||||
})).to.deep.equal({
|
||||
compileGroup: 'standard'
|
||||
})
|
||||
|
||||
it "should prefer -1 over any other for collaborators", ->
|
||||
expect(@FeaturesUpdater._mergeFeatures({
|
||||
collaborators: -1
|
||||
}, {
|
||||
collaborators: 10
|
||||
})).to.deep.equal({
|
||||
collaborators: -1
|
||||
})
|
||||
expect(@FeaturesUpdater._mergeFeatures({
|
||||
collaborators: 10
|
||||
}, {
|
||||
collaborators: -1
|
||||
})).to.deep.equal({
|
||||
collaborators: -1
|
||||
})
|
||||
expect(@FeaturesUpdater._mergeFeatures({
|
||||
collaborators: 4
|
||||
}, {
|
||||
collaborators: 10
|
||||
})).to.deep.equal({
|
||||
collaborators: 10
|
||||
})
|
||||
|
||||
it "should prefer the higher of compileTimeout", ->
|
||||
expect(@FeaturesUpdater._mergeFeatures({
|
||||
compileTimeout: 20
|
||||
}, {
|
||||
compileTimeout: 10
|
||||
})).to.deep.equal({
|
||||
compileTimeout: 20
|
||||
})
|
||||
expect(@FeaturesUpdater._mergeFeatures({
|
||||
compileTimeout: 10
|
||||
}, {
|
||||
compileTimeout: 20
|
||||
})).to.deep.equal({
|
||||
compileTimeout: 20
|
||||
})
|
||||
|
||||
it "should prefer the true over false for other keys", ->
|
||||
expect(@FeaturesUpdater._mergeFeatures({
|
||||
github: true
|
||||
}, {
|
||||
github: false
|
||||
})).to.deep.equal({
|
||||
github: true
|
||||
})
|
||||
expect(@FeaturesUpdater._mergeFeatures({
|
||||
github: false
|
||||
}, {
|
||||
github: true
|
||||
})).to.deep.equal({
|
||||
github: true
|
||||
})
|
||||
expect(@FeaturesUpdater._mergeFeatures({
|
||||
github: true
|
||||
}, {
|
||||
github: true
|
||||
})).to.deep.equal({
|
||||
github: true
|
||||
})
|
||||
expect(@FeaturesUpdater._mergeFeatures({
|
||||
github: false
|
||||
}, {
|
||||
github: false
|
||||
})).to.deep.equal({
|
||||
github: false
|
||||
})
|
||||
@@ -75,6 +75,7 @@ describe "SubscriptionController", ->
|
||||
"./SubscriptionDomainHandler":@SubscriptionDomainHandler
|
||||
"../User/UserGetter": @UserGetter
|
||||
"./RecurlyWrapper": @RecurlyWrapper = {}
|
||||
"./FeaturesUpdater": @FeaturesUpdater = {}
|
||||
|
||||
|
||||
@res = new MockResponse()
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
SandboxedModule = require('sandboxed-module')
|
||||
should = require('chai').should()
|
||||
expect = require('chai').expect
|
||||
sinon = require 'sinon'
|
||||
modulePath = "../../../../app/js/Features/Subscription/SubscriptionUpdater"
|
||||
assert = require("chai").assert
|
||||
@@ -22,6 +23,7 @@ describe "SubscriptionUpdater", ->
|
||||
save: sinon.stub().callsArgWith(0)
|
||||
freeTrial:{}
|
||||
planCode:"student_or_something"
|
||||
@user_id = @adminuser_id
|
||||
|
||||
@groupSubscription =
|
||||
admin_id: @adminUser._id
|
||||
@@ -48,15 +50,15 @@ describe "SubscriptionUpdater", ->
|
||||
@Settings =
|
||||
freeTrialPlanCode: "collaborator"
|
||||
defaultPlanCode: "personal"
|
||||
defaultFeatures: { "default": "features" }
|
||||
|
||||
@UserFeaturesUpdater =
|
||||
updateFeatures : sinon.stub().callsArgWith(2)
|
||||
updateFeatures : sinon.stub().yields()
|
||||
|
||||
@PlansLocator =
|
||||
findLocalPlanInSettings: sinon.stub().returns({})
|
||||
|
||||
@ReferalAllocator = assignBonus:sinon.stub().callsArgWith(1)
|
||||
@ReferalAllocator.cock = true
|
||||
@ReferalFeatures = getBonusFeatures: sinon.stub().callsArgWith(1)
|
||||
@Modules = {hooks: {fire: sinon.stub().callsArgWith(2, null, null)}}
|
||||
@SubscriptionUpdater = SandboxedModule.require modulePath, requires:
|
||||
'../../models/Subscription': Subscription:@SubscriptionModel
|
||||
@@ -65,8 +67,7 @@ describe "SubscriptionUpdater", ->
|
||||
'./PlansLocator': @PlansLocator
|
||||
"logger-sharelatex": log:->
|
||||
'settings-sharelatex': @Settings
|
||||
"../Referal/ReferalAllocator" : @ReferalAllocator
|
||||
'../../infrastructure/Modules': @Modules
|
||||
"./FeaturesUpdater": @FeaturesUpdater = {}
|
||||
|
||||
|
||||
describe "syncSubscription", ->
|
||||
@@ -97,7 +98,7 @@ describe "SubscriptionUpdater", ->
|
||||
|
||||
describe "_updateSubscriptionFromRecurly", ->
|
||||
beforeEach ->
|
||||
@SubscriptionUpdater._setUsersMinimumFeatures = sinon.stub().callsArgWith(1)
|
||||
@FeaturesUpdater.refreshFeatures = sinon.stub().callsArgWith(1)
|
||||
|
||||
it "should update the subscription with token etc when not expired", (done)->
|
||||
@SubscriptionUpdater._updateSubscriptionFromRecurly @recurlySubscription, @subscription, (err)=>
|
||||
@@ -108,7 +109,7 @@ describe "SubscriptionUpdater", ->
|
||||
assert.equal(@subscription.freeTrial.expiresAt, undefined)
|
||||
assert.equal(@subscription.freeTrial.planCode, undefined)
|
||||
@subscription.save.called.should.equal true
|
||||
@SubscriptionUpdater._setUsersMinimumFeatures.calledWith(@adminUser._id).should.equal true
|
||||
@FeaturesUpdater.refreshFeatures.calledWith(@adminUser._id).should.equal true
|
||||
done()
|
||||
|
||||
it "should remove the recurlySubscription_id when expired", (done)->
|
||||
@@ -117,15 +118,15 @@ describe "SubscriptionUpdater", ->
|
||||
@SubscriptionUpdater._updateSubscriptionFromRecurly @recurlySubscription, @subscription, (err)=>
|
||||
assert.equal(@subscription.recurlySubscription_id, undefined)
|
||||
@subscription.save.called.should.equal true
|
||||
@SubscriptionUpdater._setUsersMinimumFeatures.calledWith(@adminUser._id).should.equal true
|
||||
@FeaturesUpdater.refreshFeatures.calledWith(@adminUser._id).should.equal true
|
||||
done()
|
||||
|
||||
it "should update all the users features", (done)->
|
||||
@SubscriptionUpdater._updateSubscriptionFromRecurly @recurlySubscription, @subscription, (err)=>
|
||||
@SubscriptionUpdater._setUsersMinimumFeatures.calledWith(@adminUser._id).should.equal true
|
||||
@SubscriptionUpdater._setUsersMinimumFeatures.calledWith(@allUserIds[0]).should.equal true
|
||||
@SubscriptionUpdater._setUsersMinimumFeatures.calledWith(@allUserIds[1]).should.equal true
|
||||
@SubscriptionUpdater._setUsersMinimumFeatures.calledWith(@allUserIds[2]).should.equal true
|
||||
@FeaturesUpdater.refreshFeatures.calledWith(@adminUser._id).should.equal true
|
||||
@FeaturesUpdater.refreshFeatures.calledWith(@allUserIds[0]).should.equal true
|
||||
@FeaturesUpdater.refreshFeatures.calledWith(@allUserIds[1]).should.equal true
|
||||
@FeaturesUpdater.refreshFeatures.calledWith(@allUserIds[2]).should.equal true
|
||||
done()
|
||||
|
||||
it "should set group to true and save how many members can be added to group", (done)->
|
||||
@@ -152,6 +153,9 @@ describe "SubscriptionUpdater", ->
|
||||
done()
|
||||
|
||||
describe "addUserToGroup", ->
|
||||
beforeEach ->
|
||||
@FeaturesUpdater.refreshFeatures = sinon.stub().callsArgWith(1)
|
||||
|
||||
it "should add the users id to the group as a set", (done)->
|
||||
@SubscriptionUpdater.addUserToGroup @adminUser._id, @otherUserId, =>
|
||||
searchOps =
|
||||
@@ -163,12 +167,12 @@ describe "SubscriptionUpdater", ->
|
||||
|
||||
it "should update the users features", (done)->
|
||||
@SubscriptionUpdater.addUserToGroup @adminUser._id, @otherUserId, =>
|
||||
@UserFeaturesUpdater.updateFeatures.calledWith(@otherUserId, @subscription.planCode).should.equal true
|
||||
@FeaturesUpdater.refreshFeatures.calledWith(@otherUserId).should.equal true
|
||||
done()
|
||||
|
||||
describe "removeUserFromGroup", ->
|
||||
beforeEach ->
|
||||
@SubscriptionUpdater._setUsersMinimumFeatures = sinon.stub().callsArgWith(1)
|
||||
@FeaturesUpdater.refreshFeatures = sinon.stub().callsArgWith(1)
|
||||
|
||||
it "should pull the users id from the group", (done)->
|
||||
@SubscriptionUpdater.removeUserFromGroup @adminUser._id, @otherUserId, =>
|
||||
@@ -181,69 +185,7 @@ describe "SubscriptionUpdater", ->
|
||||
|
||||
it "should update the users features", (done)->
|
||||
@SubscriptionUpdater.removeUserFromGroup @adminUser._id, @otherUserId, =>
|
||||
@SubscriptionUpdater._setUsersMinimumFeatures.calledWith(@otherUserId).should.equal true
|
||||
done()
|
||||
|
||||
describe "_setUsersMinimumFeatures", ->
|
||||
|
||||
it "should call updateFeatures with the subscription if set", (done)->
|
||||
@SubscriptionLocator.getUsersSubscription.callsArgWith(1, null, @subscription)
|
||||
@SubscriptionLocator.getGroupSubscriptionMemberOf.callsArgWith(1, null)
|
||||
|
||||
@SubscriptionUpdater._setUsersMinimumFeatures @adminUser._id, (err)=>
|
||||
args = @UserFeaturesUpdater.updateFeatures.args[0]
|
||||
assert.equal args[0], @adminUser._id
|
||||
assert.equal args[1], @subscription.planCode
|
||||
done()
|
||||
|
||||
it "should call updateFeatures with the group subscription if set", (done)->
|
||||
@SubscriptionLocator.getUsersSubscription.callsArgWith(1, null)
|
||||
@SubscriptionLocator.getGroupSubscriptionMemberOf.callsArgWith(1, null, @groupSubscription)
|
||||
|
||||
@SubscriptionUpdater._setUsersMinimumFeatures @adminUser._id, (err)=>
|
||||
args = @UserFeaturesUpdater.updateFeatures.args[0]
|
||||
assert.equal args[0], @adminUser._id
|
||||
assert.equal args[1], @groupSubscription.planCode
|
||||
done()
|
||||
|
||||
it "should call updateFeatures with the overleaf subscription if set", (done)->
|
||||
@SubscriptionLocator.getUsersSubscription.callsArgWith(1, null)
|
||||
@SubscriptionLocator.getGroupSubscriptionMemberOf.callsArgWith(1, null, null)
|
||||
@Modules.hooks.fire = sinon.stub().callsArgWith(2, null, ['ol_pro'])
|
||||
|
||||
@SubscriptionUpdater._setUsersMinimumFeatures @adminUser._id, (err)=>
|
||||
args = @UserFeaturesUpdater.updateFeatures.args[0]
|
||||
assert.equal args[0], @adminUser._id
|
||||
assert.equal args[1], 'ol_pro'
|
||||
done()
|
||||
|
||||
it "should call not call updateFeatures with users subscription if the subscription plan code is the default one (downgraded)", (done)->
|
||||
@subscription.planCode = @Settings.defaultPlanCode
|
||||
@SubscriptionLocator.getUsersSubscription.callsArgWith(1, null, @subscription)
|
||||
@SubscriptionLocator.getGroupSubscriptionMemberOf.callsArgWith(1, null, @groupSubscription)
|
||||
@Modules.hooks.fire = sinon.stub().callsArgWith(2, null, null)
|
||||
@SubscriptionUpdater._setUsersMinimumFeatures @adminuser_id, (err)=>
|
||||
args = @UserFeaturesUpdater.updateFeatures.args[0]
|
||||
assert.equal args[0], @adminUser._id
|
||||
assert.equal args[1], @groupSubscription.planCode
|
||||
done()
|
||||
|
||||
|
||||
it "should call updateFeatures with default if there are no subscriptions for user", (done)->
|
||||
@SubscriptionLocator.getUsersSubscription.callsArgWith(1, null)
|
||||
@SubscriptionLocator.getGroupSubscriptionMemberOf.callsArgWith(1, null)
|
||||
@Modules.hooks.fire = sinon.stub().callsArgWith(2, null, null)
|
||||
@SubscriptionUpdater._setUsersMinimumFeatures @adminuser_id, (err)=>
|
||||
args = @UserFeaturesUpdater.updateFeatures.args[0]
|
||||
assert.equal args[0], @adminUser._id
|
||||
assert.equal args[1], @Settings.defaultPlanCode
|
||||
done()
|
||||
|
||||
it "should call assignBonus", (done)->
|
||||
@SubscriptionLocator.getUsersSubscription.callsArgWith(1, null)
|
||||
@SubscriptionLocator.getGroupSubscriptionMemberOf.callsArgWith(1, null)
|
||||
@SubscriptionUpdater._setUsersMinimumFeatures @adminuser_id, (err)=>
|
||||
@ReferalAllocator.assignBonus.calledWith(@adminuser_id).should.equal true
|
||||
@FeaturesUpdater.refreshFeatures.calledWith(@otherUserId).should.equal true
|
||||
done()
|
||||
|
||||
describe "deleteSubscription", ->
|
||||
@@ -255,7 +197,7 @@ describe "SubscriptionUpdater", ->
|
||||
member_ids: [ ObjectId(), ObjectId(), ObjectId() ]
|
||||
}
|
||||
@SubscriptionLocator.getSubscription = sinon.stub().yields(null, @subscription)
|
||||
@SubscriptionUpdater._setUsersMinimumFeatures = sinon.stub().yields()
|
||||
@FeaturesUpdater.refreshFeatures = sinon.stub().yields()
|
||||
@SubscriptionUpdater.deleteSubscription @subscription_id, done
|
||||
|
||||
it "should look up the subscription", ->
|
||||
@@ -269,22 +211,12 @@ describe "SubscriptionUpdater", ->
|
||||
.should.equal true
|
||||
|
||||
it "should downgrade the admin_id", ->
|
||||
@SubscriptionUpdater._setUsersMinimumFeatures
|
||||
@FeaturesUpdater.refreshFeatures
|
||||
.calledWith(@subscription.admin_id)
|
||||
.should.equal true
|
||||
|
||||
it "should downgrade all of the members", ->
|
||||
for user_id in @subscription.member_ids
|
||||
@SubscriptionUpdater._setUsersMinimumFeatures
|
||||
@FeaturesUpdater.refreshFeatures
|
||||
.calledWith(user_id)
|
||||
.should.equal true
|
||||
|
||||
describe 'refreshSubscription', ->
|
||||
beforeEach ->
|
||||
@SubscriptionUpdater._setUsersMinimumFeatures = sinon.stub()
|
||||
.callsArgWith(1, null)
|
||||
|
||||
it 'should call to _setUsersMinimumFeatures', ->
|
||||
@SubscriptionUpdater.refreshSubscription(@adminUser._id, ()->)
|
||||
@SubscriptionUpdater._setUsersMinimumFeatures.callCount.should.equal 1
|
||||
@SubscriptionUpdater._setUsersMinimumFeatures.calledWith(@adminUser._id).should.equal true
|
||||
|
||||
@@ -4,31 +4,20 @@ sinon = require 'sinon'
|
||||
modulePath = "../../../../app/js/Features/Subscription/UserFeaturesUpdater"
|
||||
assert = require("chai").assert
|
||||
|
||||
|
||||
describe "UserFeaturesUpdater", ->
|
||||
|
||||
beforeEach ->
|
||||
|
||||
@User =
|
||||
update: sinon.stub().callsArgWith(2)
|
||||
|
||||
@PlansLocator =
|
||||
findLocalPlanInSettings : sinon.stub()
|
||||
|
||||
@UserFeaturesUpdater = SandboxedModule.require modulePath, requires:
|
||||
'../../models/User': User:@User
|
||||
"logger-sharelatex": log:->
|
||||
'./PlansLocator': @PlansLocator
|
||||
|
||||
describe "updateFeatures", ->
|
||||
|
||||
it "should send the users features", (done)->
|
||||
user_id = "5208dd34438842e2db000005"
|
||||
plan_code = "student"
|
||||
@features = features:{versioning:true, collaborators:10}
|
||||
@PlansLocator.findLocalPlanInSettings = sinon.stub().returns(@features)
|
||||
@UserFeaturesUpdater.updateFeatures user_id, plan_code, (err, features)=>
|
||||
@features = {versioning:true, collaborators:10}
|
||||
@UserFeaturesUpdater.updateFeatures user_id, @features, (err, features)=>
|
||||
update = {"features.versioning":true, "features.collaborators":10}
|
||||
@User.update.calledWith({"_id":user_id}, update).should.equal true
|
||||
features.should.deep.equal @features.features
|
||||
features.should.deep.equal @features
|
||||
done()
|
||||
@@ -0,0 +1,128 @@
|
||||
should = require('chai').should()
|
||||
SandboxedModule = require('sandboxed-module')
|
||||
assert = require('assert')
|
||||
path = require('path')
|
||||
modulePath = path.join __dirname, '../../../../app/js/Features/Subscription/V1SubscriptionManager'
|
||||
sinon = require("sinon")
|
||||
expect = require("chai").expect
|
||||
|
||||
|
||||
describe 'V1SubscriptionManager', ->
|
||||
beforeEach ->
|
||||
@V1SubscriptionManager = SandboxedModule.require modulePath, requires:
|
||||
"../User/UserGetter": @UserGetter = {}
|
||||
"logger-sharelatex":
|
||||
log: sinon.stub()
|
||||
err: sinon.stub()
|
||||
warn: sinon.stub()
|
||||
"settings-sharelatex":
|
||||
overleaf:
|
||||
host: @host = "http://overleaf.example.com"
|
||||
"request": @request = sinon.stub()
|
||||
@V1SubscriptionManager._v1PlanRequest = sinon.stub()
|
||||
@userId = 'abcd'
|
||||
@v1UserId = 42
|
||||
@user =
|
||||
_id: @userId
|
||||
email: 'user@example.com'
|
||||
overleaf:
|
||||
id: @v1UserId
|
||||
|
||||
describe 'getPlanCodeFromV1', ->
|
||||
beforeEach ->
|
||||
@responseBody =
|
||||
id: 32,
|
||||
plan_name: 'pro'
|
||||
@UserGetter.getUser = sinon.stub()
|
||||
.yields(null, @user)
|
||||
@V1SubscriptionManager._v1PlanRequest = sinon.stub()
|
||||
.yields(null, @responseBody)
|
||||
@call = (cb) =>
|
||||
@V1SubscriptionManager.getPlanCodeFromV1 @userId, cb
|
||||
|
||||
describe 'when all goes well', ->
|
||||
|
||||
it 'should call getUser', (done) ->
|
||||
@call (err, planCode) =>
|
||||
expect(
|
||||
@UserGetter.getUser.callCount
|
||||
).to.equal 1
|
||||
expect(
|
||||
@UserGetter.getUser.calledWith(@userId)
|
||||
).to.equal true
|
||||
done()
|
||||
|
||||
it 'should call _v1PlanRequest', (done) ->
|
||||
@call (err, planCode) =>
|
||||
expect(
|
||||
@V1SubscriptionManager._v1PlanRequest.callCount
|
||||
).to.equal 1
|
||||
expect(
|
||||
@V1SubscriptionManager._v1PlanRequest.calledWith(
|
||||
@v1UserId
|
||||
)
|
||||
).to.equal true
|
||||
done()
|
||||
|
||||
it 'should produce a plan-code without error', (done) ->
|
||||
@call (err, planCode) =>
|
||||
expect(err).to.not.exist
|
||||
expect(planCode).to.equal 'v1_pro'
|
||||
done()
|
||||
|
||||
describe 'when the plan_name from v1 is null', ->
|
||||
beforeEach ->
|
||||
@responseBody.plan_name = null
|
||||
|
||||
it 'should produce a null plan-code without error', (done) ->
|
||||
@call (err, planCode) =>
|
||||
expect(err).to.not.exist
|
||||
expect(planCode).to.equal null
|
||||
done()
|
||||
|
||||
describe 'when getUser produces an error', ->
|
||||
beforeEach ->
|
||||
@UserGetter.getUser = sinon.stub()
|
||||
.yields(new Error('woops'))
|
||||
|
||||
it 'should not call _v1PlanRequest', (done) ->
|
||||
@call (err, planCode) =>
|
||||
expect(
|
||||
@V1SubscriptionManager._v1PlanRequest.callCount
|
||||
).to.equal 0
|
||||
done()
|
||||
|
||||
it 'should produce an error', (done) ->
|
||||
@call (err, planCode) =>
|
||||
expect(err).to.exist
|
||||
expect(planCode).to.not.exist
|
||||
done()
|
||||
|
||||
describe 'when getUser does not find a user', ->
|
||||
beforeEach ->
|
||||
@UserGetter.getUser = sinon.stub()
|
||||
.yields(null, null)
|
||||
|
||||
it 'should not call _v1PlanRequest', (done) ->
|
||||
@call (err, planCode) =>
|
||||
expect(
|
||||
@V1SubscriptionManager._v1PlanRequest.callCount
|
||||
).to.equal 0
|
||||
done()
|
||||
|
||||
it 'should produce a null plan-code, without error', (done) ->
|
||||
@call (err, planCode) =>
|
||||
expect(err).to.not.exist
|
||||
expect(planCode).to.not.exist
|
||||
done()
|
||||
|
||||
describe 'when the request to v1 fails', ->
|
||||
beforeEach ->
|
||||
@V1SubscriptionManager._v1PlanRequest = sinon.stub()
|
||||
.yields(new Error('woops'))
|
||||
|
||||
it 'should produce an error', (done) ->
|
||||
@call (err, planCode) =>
|
||||
expect(err).to.exist
|
||||
expect(planCode).to.not.exist
|
||||
done()
|
||||
Reference in New Issue
Block a user