overleaf/services/web/test/unit/src/Subscription/SubscriptionViewModelBuilderTests.js

438 lines
14 KiB
JavaScript
Raw Normal View History

const SandboxedModule = require('sandboxed-module')
const sinon = require('sinon')
const { assert } = require('chai')
const modulePath =
'../../../../app/src/Features/Subscription/SubscriptionViewModelBuilder'
describe('SubscriptionViewModelBuilder', function () {
beforeEach(function () {
this.user = { _id: '5208dd34438842e2db333333' }
this.recurlySubscription_id = '123abc456def'
this.planCode = 'collaborator_monthly'
this.planFeatures = {
compileGroup: 'priority',
collaborators: -1,
compileTimeout: 240,
}
this.plan = {
planCode: this.planCode,
features: this.planFeatures,
}
this.individualSubscription = {
planCode: this.planCode,
plan: this.plan,
recurlySubscription_id: this.recurlySubscription_id,
recurly: {
state: 'active',
},
}
this.individualCustomSubscription = {
planCode: this.planCode,
plan: this.plan,
recurlySubscription_id: this.recurlySubscription_id,
}
this.groupPlanCode = 'group_collaborator_monthly'
this.groupPlanFeatures = {
compileGroup: 'priority',
collaborators: 10,
compileTimeout: 240,
}
this.groupPlan = {
planCode: this.groupPlanCode,
features: this.groupPlanFeatures,
}
this.groupSubscription = {
planCode: this.groupPlanCode,
plan: this.plan,
recurly: {
state: 'active',
},
}
this.commonsPlanCode = 'commons_license'
this.commonsPlanFeatures = {
compileGroup: 'priority',
collaborators: '-1',
compileTimeout: 240,
}
this.commonsPlan = {
planCode: this.commonsPlanCode,
features: this.commonsPlanFeatures,
}
this.commonsSubscription = {
planCode: this.commonsPlanCode,
plan: this.commonsPlan,
name: 'Digital Science',
}
this.Settings = {
institutionPlanCode: this.commonsPlanCode,
}
this.SubscriptionLocator = {
promises: {
getUsersSubscription: sinon.stub().resolves(),
getMemberSubscriptions: sinon.stub().resolves(),
},
findLocalPlanInSettings: sinon.stub(),
}
this.InstitutionsGetter = {
promises: {
getCurrentInstitutionsWithLicence: sinon.stub().resolves(),
},
}
this.RecurlyWrapper = {
promises: {
getSubscription: sinon.stub().resolves(),
},
}
this.SubscriptionUpdater = {
promises: {
updateSubscriptionFromRecurly: sinon.stub().resolves(),
},
}
this.PlansLocator = {
findLocalPlanInSettings: sinon.stub(),
}
this.SubscriptionViewModelBuilder = SandboxedModule.require(modulePath, {
requires: {
'@overleaf/settings': this.Settings,
'./SubscriptionLocator': this.SubscriptionLocator,
'../Institutions/InstitutionsGetter': this.InstitutionsGetter,
'./RecurlyWrapper': this.RecurlyWrapper,
'./SubscriptionUpdater': this.SubscriptionUpdater,
'./PlansLocator': this.PlansLocator,
'./V1SubscriptionManager': {},
'./SubscriptionFormatters': {},
'../Publishers/PublishersGetter': {},
'./SubscriptionHelper': {},
},
})
this.PlansLocator.findLocalPlanInSettings
.withArgs(this.planCode)
.returns(this.plan)
.withArgs(this.groupPlanCode)
.returns(this.groupPlan)
.withArgs(this.commonsPlanCode)
.returns(this.commonsPlan)
})
describe('getBestSubscription', function () {
it('should return a free plan when user has no subscription or affiliation', async function () {
const usersBestSubscription =
await this.SubscriptionViewModelBuilder.promises.getBestSubscription(
this.user
)
assert.deepEqual(usersBestSubscription, { type: 'free' })
})
describe('with a individual subscription only', function () {
it('should return a individual subscription when user has non-Recurly one', async function () {
this.SubscriptionLocator.promises.getUsersSubscription
.withArgs(this.user)
.resolves(this.individualCustomSubscription)
const usersBestSubscription =
await this.SubscriptionViewModelBuilder.promises.getBestSubscription(
this.user
)
assert.deepEqual(usersBestSubscription, {
type: 'individual',
subscription: this.individualCustomSubscription,
plan: this.plan,
remainingTrialDays: -1,
})
})
it('should return a individual subscription when user has an active one', async function () {
this.SubscriptionLocator.promises.getUsersSubscription
.withArgs(this.user)
.resolves(this.individualSubscription)
const usersBestSubscription =
await this.SubscriptionViewModelBuilder.promises.getBestSubscription(
this.user
)
assert.deepEqual(usersBestSubscription, {
type: 'individual',
subscription: this.individualSubscription,
plan: this.plan,
remainingTrialDays: -1,
})
})
it('should return a individual subscription with remaining free trial days', async function () {
const threeDaysLater = new Date()
threeDaysLater.setDate(threeDaysLater.getDate() + 3)
this.individualSubscription.recurly.trialEndsAt = threeDaysLater
this.SubscriptionLocator.promises.getUsersSubscription
.withArgs(this.user)
.resolves(this.individualSubscription)
const usersBestSubscription =
await this.SubscriptionViewModelBuilder.promises.getBestSubscription(
this.user
)
assert.deepEqual(usersBestSubscription, {
type: 'individual',
subscription: this.individualSubscription,
plan: this.plan,
remainingTrialDays: 3,
})
})
it('should return a individual subscription with free trial on last day', async function () {
const threeHoursLater = new Date()
threeHoursLater.setTime(threeHoursLater.getTime() + 3 * 60 * 60 * 1000)
this.individualSubscription.recurly.trialEndsAt = threeHoursLater
this.SubscriptionLocator.promises.getUsersSubscription
.withArgs(this.user)
.resolves(this.individualSubscription)
const usersBestSubscription =
await this.SubscriptionViewModelBuilder.promises.getBestSubscription(
this.user
)
assert.deepEqual(usersBestSubscription, {
type: 'individual',
subscription: this.individualSubscription,
plan: this.plan,
remainingTrialDays: 1,
})
})
it('should update subscription if recurly data is missing', async function () {
this.individualSubscriptionWithoutRecurly = {
planCode: this.planCode,
plan: this.plan,
recurlySubscription_id: this.recurlySubscription_id,
}
this.recurlySubscription = {
state: 'active',
}
this.SubscriptionLocator.promises.getUsersSubscription
.withArgs(this.user)
.onCall(0)
.resolves(this.individualSubscriptionWithoutRecurly)
.withArgs(this.user)
.onCall(1)
.resolves(this.individualSubscription)
this.RecurlyWrapper.promises.getSubscription
.withArgs(this.individualSubscription.recurlySubscription_id, {
includeAccount: true,
})
.resolves(this.recurlySubscription)
const usersBestSubscription =
await this.SubscriptionViewModelBuilder.promises.getBestSubscription(
this.user
)
sinon.assert.calledWith(
this.RecurlyWrapper.promises.getSubscription,
this.individualSubscriptionWithoutRecurly.recurlySubscription_id,
{ includeAccount: true }
)
sinon.assert.calledWith(
this.SubscriptionUpdater.promises.updateSubscriptionFromRecurly,
this.recurlySubscription,
this.individualSubscriptionWithoutRecurly
)
assert.deepEqual(usersBestSubscription, {
type: 'individual',
subscription: this.individualSubscription,
plan: this.plan,
remainingTrialDays: -1,
})
})
})
it('should return a group subscription when user has one', async function () {
this.SubscriptionLocator.promises.getMemberSubscriptions
.withArgs(this.user)
.resolves([this.groupSubscription])
const usersBestSubscription =
await this.SubscriptionViewModelBuilder.promises.getBestSubscription(
this.user
)
assert.deepEqual(usersBestSubscription, {
type: 'group',
subscription: this.groupSubscription,
plan: this.groupPlan,
remainingTrialDays: -1,
})
})
it('should return a commons subscription when user has an institution affiliation', async function () {
this.InstitutionsGetter.promises.getCurrentInstitutionsWithLicence
.withArgs(this.user._id)
.resolves([this.commonsSubscription])
const usersBestSubscription =
await this.SubscriptionViewModelBuilder.promises.getBestSubscription(
this.user
)
assert.deepEqual(usersBestSubscription, {
type: 'commons',
subscription: this.commonsSubscription,
plan: this.commonsPlan,
})
})
})
describe('with multiple subscriptions', function () {
beforeEach(function () {
this.SubscriptionLocator.promises.getUsersSubscription
.withArgs(this.user)
.resolves(this.individualSubscription)
this.SubscriptionLocator.promises.getMemberSubscriptions
.withArgs(this.user)
.resolves([this.groupSubscription])
this.InstitutionsGetter.promises.getCurrentInstitutionsWithLicence
.withArgs(this.user._id)
.resolves([this.commonsSubscription])
})
it('should return individual when the individual subscription has the best feature set', async function () {
this.commonsPlan.features = {
compileGroup: 'standard',
collaborators: 1,
compileTimeout: 60,
}
const usersBestSubscription =
await this.SubscriptionViewModelBuilder.promises.getBestSubscription(
this.user
)
assert.deepEqual(usersBestSubscription, {
type: 'individual',
subscription: this.individualSubscription,
plan: this.plan,
remainingTrialDays: -1,
})
})
it('should return group when the group subscription has the best feature set', async function () {
this.plan.features = {
compileGroup: 'standard',
collaborators: 1,
compileTimeout: 60,
}
this.commonsPlan.features = {
compileGroup: 'standard',
collaborators: 1,
compileTimeout: 60,
}
const usersBestSubscription =
await this.SubscriptionViewModelBuilder.promises.getBestSubscription(
this.user
)
assert.deepEqual(usersBestSubscription, {
type: 'group',
subscription: this.groupSubscription,
plan: this.groupPlan,
remainingTrialDays: -1,
})
})
it('should return commons when the commons affiliation has the best feature set', async function () {
this.plan.features = {
compileGroup: 'priority',
collaborators: 5,
compileTimeout: 240,
}
this.groupPlan.features = {
compileGroup: 'standard',
collaborators: 1,
compileTimeout: 60,
}
this.commonsPlan.features = {
compileGroup: 'priority',
collaborators: -1,
compileTimeout: 240,
}
const usersBestSubscription =
await this.SubscriptionViewModelBuilder.promises.getBestSubscription(
this.user
)
assert.deepEqual(usersBestSubscription, {
type: 'commons',
subscription: this.commonsSubscription,
plan: this.commonsPlan,
})
})
it('should return individual with equal feature sets', async function () {
this.plan.features = {
compileGroup: 'priority',
collaborators: -1,
compileTimeout: 240,
}
this.groupPlan.features = {
compileGroup: 'priority',
collaborators: -1,
compileTimeout: 240,
}
this.commonsPlan.features = {
compileGroup: 'priority',
collaborators: -1,
compileTimeout: 240,
}
const usersBestSubscription =
await this.SubscriptionViewModelBuilder.promises.getBestSubscription(
this.user
)
assert.deepEqual(usersBestSubscription, {
type: 'individual',
subscription: this.individualSubscription,
plan: this.plan,
remainingTrialDays: -1,
})
})
it('should return group over commons with equal feature sets', async function () {
this.plan.features = {
compileGroup: 'standard',
collaborators: 1,
compileTimeout: 60,
}
this.groupPlan.features = {
compileGroup: 'priority',
collaborators: -1,
compileTimeout: 240,
}
this.commonsPlan.features = {
compileGroup: 'priority',
collaborators: -1,
compileTimeout: 240,
}
const usersBestSubscription =
await this.SubscriptionViewModelBuilder.promises.getBestSubscription(
this.user
)
assert.deepEqual(usersBestSubscription, {
type: 'group',
subscription: this.groupSubscription,
plan: this.groupPlan,
remainingTrialDays: -1,
})
})
})
})