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, recurlyStatus: { 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, recurlyStatus: { 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.recurlyStatus.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.recurlyStatus.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, }) }) }) })