overleaf/services/web/test/unit/src/Subscription/FeaturesUpdaterTests.js
Eric Mc Sween 0f7bfe9173 Merge pull request #5087 from overleaf/em-promisify-subscription-updater
Promisify FeaturesUpdater and SubscriptionHandler

GitOrigin-RevId: 1a9725afa119c0eaee3d975a11197b6f702f1307
2021-09-21 08:03:10 +00:00

244 lines
7.6 KiB
JavaScript

const SandboxedModule = require('sandboxed-module')
const { expect } = require('chai')
const sinon = require('sinon')
const { ObjectId } = require('mongodb')
const MODULE_PATH = '../../../../app/src/Features/Subscription/FeaturesUpdater'
describe('FeaturesUpdater', function () {
beforeEach(function () {
this.user = {
_id: new ObjectId(),
features: {},
}
this.v1UserId = 12345
this.subscriptions = {
individual: { planCode: 'individual-plan' },
group1: { planCode: 'group-plan-1' },
group2: { planCode: 'group-plan-2' },
noDropbox: { planCode: 'no-dropbox' },
}
this.UserFeaturesUpdater = {
promises: {
updateFeatures: sinon
.stub()
.resolves({ features: { some: 'features' }, featuresChanged: true }),
},
}
this.SubscriptionLocator = {
promises: {
getUserIndividualSubscription: sinon.stub(),
getGroupSubscriptionsMemberOf: sinon.stub(),
},
}
this.SubscriptionLocator.promises.getUserIndividualSubscription
.withArgs(this.user._id)
.resolves(this.subscriptions.individual)
this.SubscriptionLocator.promises.getGroupSubscriptionsMemberOf
.withArgs(this.user._id)
.resolves([this.subscriptions.group1, this.subscriptions.group2])
this.Settings = {
defaultFeatures: { default: 'features' },
plans: [
{ planCode: 'individual-plan', features: { individual: 'features' } },
{ planCode: 'group-plan-1', features: { group1: 'features' } },
{ planCode: 'group-plan-2', features: { group2: 'features' } },
{ planCode: 'v1-plan', features: { v1: 'features' } },
{ planCode: 'no-dropbox', features: { dropbox: false } },
],
features: {
all: {
default: 'features',
individual: 'features',
group1: 'features',
group2: 'features',
institutions: 'features',
v1: 'features',
grandfathered: 'features',
bonus: 'features',
},
},
}
this.ReferalFeatures = {
promises: {
getBonusFeatures: sinon.stub().resolves({ bonus: 'features' }),
},
}
this.V1SubscriptionManager = {
getGrandfatheredFeaturesForV1User: sinon.stub(),
promises: {
getPlanCodeFromV1: sinon.stub(),
},
}
this.V1SubscriptionManager.promises.getPlanCodeFromV1
.withArgs(this.user._id)
.resolves({ planCode: 'v1-plan', v1Id: this.v1UserId })
this.V1SubscriptionManager.getGrandfatheredFeaturesForV1User
.withArgs(this.v1UserId)
.returns({ grandfathered: 'features' })
this.InstitutionsFeatures = {
promises: {
getInstitutionsFeatures: sinon
.stub()
.resolves({ institutions: 'features' }),
},
}
this.UserGetter = {
promises: {
getUser: sinon.stub().resolves(null),
},
}
this.UserGetter.promises.getUser.withArgs(this.user._id).resolves(this.user)
this.UserGetter.promises.getUser
.withArgs({ 'overleaf.id': this.v1UserId })
.resolves(this.user)
this.AnalyticsManager = {
setUserPropertyForUser: sinon.stub(),
}
this.Modules = {
promises: { hooks: { fire: sinon.stub().resolves() } },
}
this.FeaturesHelper = {
mergeFeatures: sinon.stub().callsFake((a, b) => ({ ...a, ...b })),
}
this.FeaturesUpdater = SandboxedModule.require(MODULE_PATH, {
requires: {
'./UserFeaturesUpdater': this.UserFeaturesUpdater,
'./SubscriptionLocator': this.SubscriptionLocator,
'./FeaturesHelper': this.FeaturesHelper,
'@overleaf/settings': this.Settings,
'../Referal/ReferalFeatures': this.ReferalFeatures,
'./V1SubscriptionManager': this.V1SubscriptionManager,
'../Institutions/InstitutionsFeatures': this.InstitutionsFeatures,
'../User/UserGetter': this.UserGetter,
'../Analytics/AnalyticsManager': this.AnalyticsManager,
'../../infrastructure/Modules': this.Modules,
},
})
})
describe('refreshFeatures', function () {
it('should return features and featuresChanged', async function () {
const {
features,
featuresChanged,
} = await this.FeaturesUpdater.promises.refreshFeatures(
this.user._id,
'test'
)
expect(features).to.exist
expect(featuresChanged).to.exist
})
describe('normally', function () {
beforeEach(async function () {
await this.FeaturesUpdater.promises.refreshFeatures(
this.user._id,
'test'
)
})
it('should update the user with the merged features', function () {
expect(
this.UserFeaturesUpdater.promises.updateFeatures
).to.have.been.calledWith(this.user._id, this.Settings.features.all)
})
it('should send the corresponding feature set user property', function () {
expect(
this.AnalyticsManager.setUserPropertyForUser
).to.have.been.calledWith(this.user._id, 'feature-set', 'all')
})
})
describe('with a non-standard feature set', async function () {
beforeEach(async function () {
this.SubscriptionLocator.promises.getGroupSubscriptionsMemberOf
.withArgs(this.user._id)
.resolves(null)
await this.FeaturesUpdater.promises.refreshFeatures(
this.user._id,
'test'
)
})
it('should send mixed feature set user property', function () {
sinon.assert.calledWith(
this.AnalyticsManager.setUserPropertyForUser,
this.user._id,
'feature-set',
'mixed'
)
})
})
describe('when losing dropbox feature', async function () {
beforeEach(async function () {
this.user.features = { dropbox: true }
this.SubscriptionLocator.promises.getUserIndividualSubscription
.withArgs(this.user._id)
.resolves(this.subscriptions.noDropbox)
await this.FeaturesUpdater.promises.refreshFeatures(
this.user._id,
'test'
)
})
it('should fire module hook to unlink dropbox', function () {
expect(this.Modules.promises.hooks.fire).to.have.been.calledWith(
'removeDropbox',
this.user._id,
'test'
)
})
})
})
describe('doSyncFromV1', function () {
describe('when all goes well', function () {
beforeEach(async function () {
await this.FeaturesUpdater.promises.doSyncFromV1(this.v1UserId)
})
it('should update the user with the merged features', function () {
expect(
this.UserFeaturesUpdater.promises.updateFeatures
).to.have.been.calledWith(this.user._id, this.Settings.features.all)
})
})
describe('when getUser produces an error', function () {
beforeEach(function () {
this.UserGetter.promises.getUser.rejects(new Error('woops'))
})
it('should propagate the error', async function () {
const someId = 9090
await expect(this.FeaturesUpdater.promises.doSyncFromV1(someId)).to.be
.rejected
expect(this.UserFeaturesUpdater.promises.updateFeatures).not.to.have
.been.called
})
})
describe('when getUser does not find a user', function () {
beforeEach(async function () {
const someOtherId = 987
await this.FeaturesUpdater.promises.doSyncFromV1(someOtherId)
})
it('should not update the user', function () {
expect(this.UserFeaturesUpdater.promises.updateFeatures).not.to.have
.been.called
})
})
})
})