|
|
@@ -0,0 +1,413 @@
|
|
|
+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.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 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,
|
|
|
+ })
|
|
|
+ })
|
|
|
+ })
|
|
|
+})
|