Merge pull request #5087 from overleaf/em-promisify-subscription-updater

Promisify FeaturesUpdater and SubscriptionHandler

GitOrigin-RevId: 1a9725afa119c0eaee3d975a11197b6f702f1307
This commit is contained in:
Eric Mc Sween
2021-09-21 08:03:10 +00:00
committed by Copybot
parent 8b1665a97c
commit 0f7bfe9173
16 changed files with 1323 additions and 1792 deletions
@@ -106,6 +106,8 @@ describe('InstitutionsManager', function () {
},
'../Subscription/FeaturesUpdater': {
refreshFeatures: this.refreshFeatures,
},
'../Subscription/FeaturesHelper': {
isFeatureSetBetter: (this.isFeatureSetBetter = sinon.stub()),
},
'../User/UserGetter': this.UserGetter,
@@ -0,0 +1,126 @@
const SandboxedModule = require('sandboxed-module')
const { expect } = require('chai')
const MODULE_PATH = '../../../../app/src/Features/Subscription/FeaturesHelper'
describe('FeaturesHelper', function () {
beforeEach(function () {
this.FeaturesHelper = SandboxedModule.require(MODULE_PATH)
})
describe('mergeFeatures', function () {
it('should prefer priority over standard for compileGroup', function () {
expect(
this.FeaturesHelper.mergeFeatures(
{ compileGroup: 'priority' },
{ compileGroup: 'standard' }
)
).to.deep.equal({ compileGroup: 'priority' })
expect(
this.FeaturesHelper.mergeFeatures(
{ compileGroup: 'standard' },
{ compileGroup: 'priority' }
)
).to.deep.equal({ compileGroup: 'priority' })
expect(
this.FeaturesHelper.mergeFeatures(
{ compileGroup: 'priority' },
{ compileGroup: 'priority' }
)
).to.deep.equal({ compileGroup: 'priority' })
expect(
this.FeaturesHelper.mergeFeatures(
{ compileGroup: 'standard' },
{ compileGroup: 'standard' }
)
).to.deep.equal({ compileGroup: 'standard' })
})
it('should prefer -1 over any other for collaborators', function () {
expect(
this.FeaturesHelper.mergeFeatures(
{ collaborators: -1 },
{ collaborators: 10 }
)
).to.deep.equal({ collaborators: -1 })
expect(
this.FeaturesHelper.mergeFeatures(
{ collaborators: 10 },
{ collaborators: -1 }
)
).to.deep.equal({ collaborators: -1 })
expect(
this.FeaturesHelper.mergeFeatures(
{ collaborators: 4 },
{ collaborators: 10 }
)
).to.deep.equal({ collaborators: 10 })
})
it('should prefer the higher of compileTimeout', function () {
expect(
this.FeaturesHelper.mergeFeatures(
{ compileTimeout: 20 },
{ compileTimeout: 10 }
)
).to.deep.equal({ compileTimeout: 20 })
expect(
this.FeaturesHelper.mergeFeatures(
{ compileTimeout: 10 },
{ compileTimeout: 20 }
)
).to.deep.equal({ compileTimeout: 20 })
})
it('should prefer the true over false for other keys', function () {
expect(
this.FeaturesHelper.mergeFeatures({ github: true }, { github: false })
).to.deep.equal({ github: true })
expect(
this.FeaturesHelper.mergeFeatures({ github: false }, { github: true })
).to.deep.equal({ github: true })
expect(
this.FeaturesHelper.mergeFeatures({ github: true }, { github: true })
).to.deep.equal({ github: true })
expect(
this.FeaturesHelper.mergeFeatures({ github: false }, { github: false })
).to.deep.equal({ github: false })
})
})
describe('isFeatureSetBetter', function () {
it('simple comparisons', function () {
const result1 = this.FeaturesHelper.isFeatureSetBetter(
{ dropbox: true },
{ dropbox: false }
)
expect(result1).to.be.true
const result2 = this.FeaturesHelper.isFeatureSetBetter(
{ dropbox: false },
{ dropbox: true }
)
expect(result2).to.be.false
})
it('compound comparisons with same features', function () {
const result1 = this.FeaturesHelper.isFeatureSetBetter(
{ collaborators: 9, dropbox: true },
{ collaborators: 10, dropbox: true }
)
expect(result1).to.be.false
const result2 = this.FeaturesHelper.isFeatureSetBetter(
{ collaborators: -1, dropbox: true },
{ collaborators: 10, dropbox: true }
)
expect(result2).to.be.true
const result3 = this.FeaturesHelper.isFeatureSetBetter(
{ collaborators: -1, compileTimeout: 60, dropbox: true },
{ collaborators: 10, compileTimeout: 60, dropbox: true }
)
expect(result3).to.be.true
})
})
})
@@ -1,556 +1,244 @@
const SandboxedModule = require('sandboxed-module')
const { assert, expect } = require('chai')
const { expect } = require('chai')
const sinon = require('sinon')
const modulePath = '../../../../app/src/Features/Subscription/FeaturesUpdater'
const { ObjectId } = require('mongodb')
const MODULE_PATH = '../../../../app/src/Features/Subscription/FeaturesUpdater'
describe('FeaturesUpdater', function () {
beforeEach(function () {
this.user_id = ObjectId().toString()
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.FeaturesUpdater = SandboxedModule.require(modulePath, {
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 = {}),
'./PlansLocator': (this.PlansLocator = {}),
'@overleaf/settings': (this.Settings = {
features: {
personal: {
collaborators: 1,
dropbox: false,
compileTimeout: 60,
compileGroup: 'standard',
},
collaborator: {
collaborators: 10,
dropbox: true,
compileTimeout: 240,
compileGroup: 'priority',
},
professional: {
collaborators: -1,
dropbox: true,
compileTimeout: 240,
compileGroup: 'priority',
},
},
}),
'../Referal/ReferalFeatures': (this.ReferalFeatures = {}),
'./V1SubscriptionManager': (this.V1SubscriptionManager = {}),
'../Institutions/InstitutionsFeatures': (this.InstitutionsFeatures = {}),
'../User/UserGetter': (this.UserGetter = {}),
'../Analytics/AnalyticsManager': (this.AnalyticsManager = {
setUserPropertyForUser: sinon.stub(),
}),
'../../infrastructure/Modules': (this.Modules = {
hooks: { fire: sinon.stub() },
}),
'./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 () {
beforeEach(function () {
this.user = {
_id: this.user_id,
features: {},
}
this.UserFeaturesUpdater.updateFeatures = sinon
.stub()
.yields(null, { some: 'features' }, true)
this.FeaturesUpdater._getIndividualFeatures = sinon
.stub()
.yields(null, { individual: 'features' })
this.FeaturesUpdater._getGroupFeatureSets = sinon
.stub()
.yields(null, [{ group: 'features' }, { group: 'features2' }])
this.InstitutionsFeatures.getInstitutionsFeatures = sinon
.stub()
.yields(null, { institutions: 'features' })
this.FeaturesUpdater._getV1Features = sinon
.stub()
.yields(null, { v1: 'features' })
this.ReferalFeatures.getBonusFeatures = sinon
.stub()
.yields(null, { bonus: 'features' })
this.FeaturesUpdater._mergeFeatures = sinon
.stub()
.returns({ merged: 'features' })
this.UserGetter.getUser = sinon.stub().yields(null, this.user)
this.callback = sinon.stub()
})
it('should return features and featuresChanged', function () {
this.FeaturesUpdater.refreshFeatures(
this.user_id,
'test',
(err, features, featuresChanged) => {
expect(err).to.not.exist
expect(features).to.exist
expect(featuresChanged).to.exist
}
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(function () {
this.FeaturesUpdater.refreshFeatures(
this.user_id,
'test',
this.callback
beforeEach(async function () {
await this.FeaturesUpdater.promises.refreshFeatures(
this.user._id,
'test'
)
})
it('should get the individual features', function () {
this.FeaturesUpdater._getIndividualFeatures
.calledWith(this.user_id)
.should.equal(true)
})
it('should get the group features', function () {
this.FeaturesUpdater._getGroupFeatureSets
.calledWith(this.user_id)
.should.equal(true)
})
it('should get the institution features', function () {
this.InstitutionsFeatures.getInstitutionsFeatures
.calledWith(this.user_id)
.should.equal(true)
})
it('should get the v1 features', function () {
this.FeaturesUpdater._getV1Features
.calledWith(this.user_id)
.should.equal(true)
})
it('should get the bonus features', function () {
this.ReferalFeatures.getBonusFeatures
.calledWith(this.user_id)
.should.equal(true)
})
it('should merge from the default features', function () {
this.FeaturesUpdater._mergeFeatures
.calledWith(this.Settings.defaultFeatures)
.should.equal(true)
})
it('should merge the individual features', function () {
this.FeaturesUpdater._mergeFeatures
.calledWith(sinon.match.any, { individual: 'features' })
.should.equal(true)
})
it('should merge the group features', function () {
this.FeaturesUpdater._mergeFeatures
.calledWith(sinon.match.any, { group: 'features' })
.should.equal(true)
this.FeaturesUpdater._mergeFeatures
.calledWith(sinon.match.any, { group: 'features2' })
.should.equal(true)
})
it('should merge the institutions features', function () {
this.FeaturesUpdater._mergeFeatures
.calledWith(sinon.match.any, { institutions: 'features' })
.should.equal(true)
})
it('should merge the v1 features', function () {
this.FeaturesUpdater._mergeFeatures
.calledWith(sinon.match.any, { v1: 'features' })
.should.equal(true)
})
it('should merge the bonus features', function () {
this.FeaturesUpdater._mergeFeatures
.calledWith(sinon.match.any, { bonus: 'features' })
.should.equal(true)
})
it('should update the user with the merged features', function () {
this.UserFeaturesUpdater.updateFeatures
.calledWith(this.user_id, { merged: 'features' })
.should.equal(true)
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('analytics user properties', function () {
it('should send the corresponding feature set user property', function () {
this.FeaturesUpdater._mergeFeatures = sinon
.stub()
.returns(this.Settings.features.personal)
this.FeaturesUpdater.refreshFeatures(
this.user_id,
'test',
this.callback
)
sinon.assert.calledWith(
this.AnalyticsManager.setUserPropertyForUser,
this.user_id,
'feature-set',
'personal'
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 () {
this.FeaturesUpdater._mergeFeatures = sinon
.stub()
.returns({ dropbox: true, feature: 'some' })
this.FeaturesUpdater.refreshFeatures(
this.user_id,
'test',
this.callback
)
sinon.assert.calledWith(
this.AnalyticsManager.setUserPropertyForUser,
this.user_id,
this.user._id,
'feature-set',
'mixed'
)
})
})
describe('when losing dropbox feature', function () {
beforeEach(function () {
this.user = {
_id: this.user_id,
features: { dropbox: true },
}
this.UserGetter.getUser = sinon.stub().yields(null, this.user)
this.FeaturesUpdater._mergeFeatures = sinon
.stub()
.returns({ dropbox: false })
this.FeaturesUpdater.refreshFeatures(
this.user_id,
'test',
this.callback
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 () {
this.Modules.hooks.fire
.calledWith('removeDropbox', this.user._id, 'test')
.should.equal(true)
})
})
})
describe('_mergeFeatures', function () {
it('should prefer priority over standard for compileGroup', function () {
expect(
this.FeaturesUpdater._mergeFeatures(
{
compileGroup: 'priority',
},
{
compileGroup: 'standard',
}
expect(this.Modules.promises.hooks.fire).to.have.been.calledWith(
'removeDropbox',
this.user._id,
'test'
)
).to.deep.equal({
compileGroup: 'priority',
})
expect(
this.FeaturesUpdater._mergeFeatures(
{
compileGroup: 'standard',
},
{
compileGroup: 'priority',
}
)
).to.deep.equal({
compileGroup: 'priority',
})
expect(
this.FeaturesUpdater._mergeFeatures(
{
compileGroup: 'priority',
},
{
compileGroup: 'priority',
}
)
).to.deep.equal({
compileGroup: 'priority',
})
expect(
this.FeaturesUpdater._mergeFeatures(
{
compileGroup: 'standard',
},
{
compileGroup: 'standard',
}
)
).to.deep.equal({
compileGroup: 'standard',
})
})
it('should prefer -1 over any other for collaborators', function () {
expect(
this.FeaturesUpdater._mergeFeatures(
{
collaborators: -1,
},
{
collaborators: 10,
}
)
).to.deep.equal({
collaborators: -1,
})
expect(
this.FeaturesUpdater._mergeFeatures(
{
collaborators: 10,
},
{
collaborators: -1,
}
)
).to.deep.equal({
collaborators: -1,
})
expect(
this.FeaturesUpdater._mergeFeatures(
{
collaborators: 4,
},
{
collaborators: 10,
}
)
).to.deep.equal({
collaborators: 10,
})
})
it('should prefer the higher of compileTimeout', function () {
expect(
this.FeaturesUpdater._mergeFeatures(
{
compileTimeout: 20,
},
{
compileTimeout: 10,
}
)
).to.deep.equal({
compileTimeout: 20,
})
expect(
this.FeaturesUpdater._mergeFeatures(
{
compileTimeout: 10,
},
{
compileTimeout: 20,
}
)
).to.deep.equal({
compileTimeout: 20,
})
})
it('should prefer the true over false for other keys', function () {
expect(
this.FeaturesUpdater._mergeFeatures(
{
github: true,
},
{
github: false,
}
)
).to.deep.equal({
github: true,
})
expect(
this.FeaturesUpdater._mergeFeatures(
{
github: false,
},
{
github: true,
}
)
).to.deep.equal({
github: true,
})
expect(
this.FeaturesUpdater._mergeFeatures(
{
github: true,
},
{
github: true,
}
)
).to.deep.equal({
github: true,
})
expect(
this.FeaturesUpdater._mergeFeatures(
{
github: false,
},
{
github: false,
}
)
).to.deep.equal({
github: false,
})
})
})
describe('doSyncFromV1', function () {
beforeEach(function () {
this.v1UserId = 1
this.user = {
_id: this.user_id,
email: 'user@example.com',
overleaf: {
id: this.v1UserId,
},
}
this.UserGetter.getUser = sinon.stub().callsArgWith(2, null, this.user)
this.FeaturesUpdater.refreshFeatures = sinon.stub().yields(null)
this.call = cb => {
this.FeaturesUpdater.doSyncFromV1(this.v1UserId, cb)
}
})
describe('when all goes well', function () {
it('should call getUser', function (done) {
this.call(() => {
expect(this.UserGetter.getUser.callCount).to.equal(1)
expect(
this.UserGetter.getUser.calledWith({ 'overleaf.id': this.v1UserId })
).to.equal(true)
done()
})
beforeEach(async function () {
await this.FeaturesUpdater.promises.doSyncFromV1(this.v1UserId)
})
it('should call refreshFeatures', function (done) {
this.call(() => {
expect(this.FeaturesUpdater.refreshFeatures.callCount).to.equal(1)
expect(
this.FeaturesUpdater.refreshFeatures.calledWith(this.user_id)
).to.equal(true)
done()
})
})
it('should not produce an error', function (done) {
this.call(err => {
expect(err).to.not.exist
done()
})
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.getUser = sinon
.stub()
.callsArgWith(2, new Error('woops'))
this.UserGetter.promises.getUser.rejects(new Error('woops'))
})
it('should not call refreshFeatures', function () {
expect(this.FeaturesUpdater.refreshFeatures.callCount).to.equal(0)
})
it('should produce an error', function (done) {
this.call(err => {
expect(err).to.exist
done()
})
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(function () {
this.UserGetter.getUser = sinon.stub().callsArgWith(2, null, null)
beforeEach(async function () {
const someOtherId = 987
await this.FeaturesUpdater.promises.doSyncFromV1(someOtherId)
})
it('should not call refreshFeatures', function (done) {
this.call(() => {
expect(this.FeaturesUpdater.refreshFeatures.callCount).to.equal(0)
done()
})
it('should not update the user', function () {
expect(this.UserFeaturesUpdater.promises.updateFeatures).not.to.have
.been.called
})
it('should not produce an error', function (done) {
this.call(err => {
expect(err).to.not.exist
done()
})
})
})
})
describe('isFeatureSetBetter', function () {
it('simple comparisons', function () {
const result1 = this.FeaturesUpdater.isFeatureSetBetter(
{
dropbox: true,
},
{
dropbox: false,
}
)
assert.isTrue(result1)
const result2 = this.FeaturesUpdater.isFeatureSetBetter(
{
dropbox: false,
},
{
dropbox: true,
}
)
assert.isFalse(result2)
})
it('compound comparisons with same features', function () {
const result1 = this.FeaturesUpdater.isFeatureSetBetter(
{
collaborators: 9,
dropbox: true,
},
{
collaborators: 10,
dropbox: true,
}
)
assert.isFalse(result1)
const result2 = this.FeaturesUpdater.isFeatureSetBetter(
{
collaborators: -1,
dropbox: true,
},
{
collaborators: 10,
dropbox: true,
}
)
assert.isTrue(result2)
const result3 = this.FeaturesUpdater.isFeatureSetBetter(
{
collaborators: -1,
compileTimeout: 60,
dropbox: true,
},
{
collaborators: 10,
compileTimeout: 60,
dropbox: true,
}
)
assert.isTrue(result3)
})
})
})
@@ -2,7 +2,8 @@ const SandboxedModule = require('sandboxed-module')
const sinon = require('sinon')
const chai = require('chai')
const { expect } = chai
const modulePath =
const MODULE_PATH =
'../../../../app/src/Features/Subscription/SubscriptionHandler'
const mockRecurlySubscriptions = {
@@ -47,6 +48,8 @@ const mockSubscriptionChanges = {
describe('SubscriptionHandler', function () {
beforeEach(function () {
this.callback = sinon.stub()
this.Settings = {
plans: [
{
@@ -85,6 +88,7 @@ describe('SubscriptionHandler', function () {
getBillingInfo: sinon.stub().yields(),
getAccountPastDueInvoices: sinon.stub().yields(),
attemptInvoiceCollection: sinon.stub().yields(),
listAccountActiveSubscriptions: sinon.stub().yields(null, []),
}
this.RecurlyClient = {
changeSubscriptionByUuid: sinon
@@ -118,7 +122,7 @@ describe('SubscriptionHandler', function () {
shouldPlanChangeAtTermEnd: sinon.stub(),
}
this.SubscriptionHandler = SandboxedModule.require(modulePath, {
this.SubscriptionHandler = SandboxedModule.require(MODULE_PATH, {
requires: {
'./RecurlyWrapper': this.RecurlyWrapper,
'./RecurlyClient': this.RecurlyClient,
@@ -134,23 +138,15 @@ describe('SubscriptionHandler', function () {
'./SubscriptionHelper': this.SubscriptionHelper,
},
})
this.SubscriptionHandler.syncSubscriptionToUser = sinon
.stub()
.callsArgWith(2)
})
describe('createSubscription', function () {
beforeEach(function () {
this.callback = sinon.stub()
this.subscriptionDetails = {
cvv: '123',
number: '12345',
}
this.recurlyTokenIds = { billing: '45555666' }
this.SubscriptionHandler.validateNoSubscriptionInRecurly = sinon
.stub()
.yields(null, true)
})
describe('successfully', function () {
@@ -182,9 +178,9 @@ describe('SubscriptionHandler', function () {
describe('when there is already a subscription in Recurly', function () {
beforeEach(function () {
this.SubscriptionHandler.validateNoSubscriptionInRecurly = sinon
.stub()
.yields(null, false)
this.RecurlyWrapper.listAccountActiveSubscriptions.yields(null, [
this.subscription,
])
this.SubscriptionHandler.createSubscription(
this.user,
this.subscriptionDetails,
@@ -235,9 +231,9 @@ describe('SubscriptionHandler', function () {
callback(null, this.user)
}
this.plan_code = 'collaborator'
this.SubscriptionHelper.shouldPlanChangeAtTermEnd = sinon
.stub()
.returns(shouldPlanChangeAtTermEnd)
this.SubscriptionHelper.shouldPlanChangeAtTermEnd.returns(
shouldPlanChangeAtTermEnd
)
this.LimitationsManager.userHasV2Subscription.callsArgWith(
1,
null,
@@ -277,14 +273,13 @@ describe('SubscriptionHandler', function () {
callback(null, this.user)
}
this.plan_code = 'collaborator'
this.PlansLocator.findLocalPlanInSettings = sinon.stub().returns(null)
this.PlansLocator.findLocalPlanInSettings.returns(null)
this.LimitationsManager.userHasV2Subscription.callsArgWith(
1,
null,
true,
this.subscription
)
this.callback = sinon.stub()
this.SubscriptionHandler.updateSubscription(
this.user,
this.plan_code,
@@ -322,9 +317,7 @@ describe('SubscriptionHandler', function () {
it('should redirect to the subscription dashboard', function () {
this.RecurlyClient.changeSubscriptionByUuid.called.should.equal(false)
this.SubscriptionHandler.syncSubscriptionToUser.called.should.equal(
false
)
this.SubscriptionUpdater.syncSubscription.called.should.equal(false)
})
})
@@ -561,18 +554,11 @@ describe('SubscriptionHandler', function () {
})
describe('validateNoSubscriptionInRecurly', function () {
beforeEach(function () {
this.subscriptions = []
this.RecurlyWrapper.listAccountActiveSubscriptions = sinon
.stub()
.yields(null, this.subscriptions)
this.SubscriptionUpdater.syncSubscription = sinon.stub().yields()
this.callback = sinon.stub()
})
describe('with no subscription in recurly', function () {
describe('with a subscription in recurly', function () {
beforeEach(function () {
this.subscriptions.push((this.subscription = { mock: 'subscription' }))
this.RecurlyWrapper.listAccountActiveSubscriptions.yields(null, [
this.subscription,
])
this.SubscriptionHandler.validateNoSubscriptionInRecurly(
this.user_id,
this.callback
@@ -596,7 +582,7 @@ describe('SubscriptionHandler', function () {
})
})
describe('with a subscription in recurly', function () {
describe('with no subscription in recurly', function () {
beforeEach(function () {
this.SubscriptionHandler.validateNoSubscriptionInRecurly(
this.user_id,
@@ -7,12 +7,14 @@ const { ObjectId } = require('mongodb')
describe('SubscriptionUpdater', function () {
beforeEach(function () {
this.recurlyPlan = { planCode: 'recurly-plan' }
this.recurlySubscription = {
uuid: '1238uoijdasjhd',
plan: {
plan_code: 'kjhsakjds',
plan_code: this.recurlyPlan.planCode,
},
}
this.adminUser = { _id: (this.adminuser_id = '5208dd34438843e2db000007') }
this.otherUserId = '5208dd34438842e2db000005'
this.allUserIds = ['13213', 'dsadas', 'djsaiud89']
@@ -21,7 +23,7 @@ describe('SubscriptionUpdater', function () {
admin_id: this.adminUser._id,
manager_ids: [this.adminUser._id],
member_ids: [],
save: sinon.stub().callsArgWith(0),
save: sinon.stub().resolves(),
planCode: 'student_or_something',
}
this.user_id = this.adminuser_id
@@ -31,7 +33,7 @@ describe('SubscriptionUpdater', function () {
admin_id: this.adminUser._id,
manager_ids: [this.adminUser._id],
member_ids: this.allUserIds,
save: sinon.stub().callsArgWith(0),
save: sinon.stub().resolves(),
groupPlan: true,
planCode: 'group_subscription',
}
@@ -40,17 +42,11 @@ describe('SubscriptionUpdater', function () {
admin_id: this.adminUser._id,
manager_ids: [this.adminUser._id],
member_ids: [this.otherUserId],
save: sinon.stub().callsArgWith(0),
save: sinon.stub().resolves(),
groupPlan: true,
planCode: 'better_group_subscription',
}
this.updateStub = sinon.stub().callsArgWith(2, null)
this.updateManyStub = sinon.stub().callsArgWith(2, null)
this.findOneAndUpdateStub = sinon
.stub()
.callsArgWith(2, null, this.subscription)
const subscription = this.subscription
this.SubscriptionModel = class {
constructor(opts) {
@@ -61,62 +57,93 @@ describe('SubscriptionUpdater', function () {
}
save() {
return subscription
return Promise.resolve(subscription)
}
}
this.SubscriptionModel.deleteOne = sinon.stub().yields()
this.SubscriptionModel.updateOne = this.updateStub
this.SubscriptionModel.updateMany = this.updateManyStub
this.SubscriptionModel.findOneAndUpdate = this.findOneAndUpdateStub
this.SubscriptionModel.deleteOne = sinon
.stub()
.returns({ exec: sinon.stub().resolves() })
this.SubscriptionModel.updateOne = sinon
.stub()
.returns({ exec: sinon.stub().resolves() })
this.SubscriptionModel.updateMany = sinon
.stub()
.returns({ exec: sinon.stub().resolves() })
this.SubscriptionModel.findOneAndUpdate = sinon.stub().returns({
exec: sinon.stub().resolves(this.subscription),
})
this.SubscriptionLocator = {
getUsersSubscription: sinon.stub(),
getGroupSubscriptionMemberOf: sinon.stub(),
getMemberSubscriptions: sinon.stub().yields(null, []),
promises: {
getMemberSubscriptions: sinon.stub().returns([this.groupSubscription]),
getUsersSubscription: sinon.stub(),
getGroupSubscriptionMemberOf: sinon.stub(),
getMemberSubscriptions: sinon.stub().resolves([]),
getSubscription: sinon.stub(),
},
}
this.SubscriptionLocator.promises.getSubscription
.withArgs(this.subscription._id)
.resolves(this.subscription)
this.Settings = {
defaultPlanCode: 'personal',
defaultFeatures: { default: 'features' },
plans: [
this.recurlyPlan,
{ planCode: this.subscription.planCode, features: {} },
{
planCode: this.groupSubscription.planCode,
features: {
collaborators: 10,
compileTimeout: 60,
dropbox: true,
},
},
{
planCode: this.betterGroupSubscription.planCode,
features: {
collaborators: -1,
compileTimeout: 240,
dropbox: true,
},
},
],
}
this.UserFeaturesUpdater = { updateFeatures: sinon.stub().yields() }
this.UserFeaturesUpdater = {
promises: {
updateFeatures: sinon.stub().resolves(),
},
}
this.PlansLocator = { findLocalPlanInSettings: sinon.stub().returns({}) }
this.UserGetter = {
getUsers(memberIds, projection, callback) {
const users = memberIds.map(id => ({ _id: id }))
callback(null, users)
this.ReferalFeatures = {
promises: {
getBonusFeatures: sinon.stub().resolves(),
},
getUser: sinon.stub(),
}
this.ReferalFeatures = { getBonusFeatures: sinon.stub().callsArgWith(1) }
this.Modules = { hooks: { fire: sinon.stub().callsArgWith(2, null, null) } }
this.FeaturesUpdater = {
refreshFeatures: sinon.stub().yields(),
planCodeToFeatures: sinon.stub(),
promises: {
refreshFeatures: sinon.stub().resolves({}),
},
}
this.DeletedSubscription = {
findOneAndUpdate: sinon.stub().yields(),
findOneAndUpdate: sinon.stub().returns({ exec: sinon.stub().resolves() }),
}
this.AnalyticsManager = {
setUserPropertyForUser: sinon.stub(),
}
this.SubscriptionUpdater = SandboxedModule.require(modulePath, {
requires: {
mongodb: { ObjectId },
'../../models/Subscription': {
Subscription: this.SubscriptionModel,
},
'./UserFeaturesUpdater': this.UserFeaturesUpdater,
'./SubscriptionLocator': this.SubscriptionLocator,
'../User/UserGetter': this.UserGetter,
'./PlansLocator': this.PlansLocator,
'@overleaf/settings': this.Settings,
'../../infrastructure/mongodb': { db: {}, ObjectId },
'./FeaturesUpdater': this.FeaturesUpdater,
@@ -129,266 +156,184 @@ describe('SubscriptionUpdater', function () {
})
describe('updateAdmin', function () {
it('should update the subscription admin', function (done) {
it('should update the subscription admin', async function () {
this.subscription.groupPlan = true
this.SubscriptionUpdater.updateAdmin(
await this.SubscriptionUpdater.promises.updateAdmin(
this.subscription,
this.otherUserId,
err => {
if (err != null) {
return done(err)
}
const query = {
_id: ObjectId(this.subscription._id),
customAccount: true,
}
const update = {
$set: { admin_id: ObjectId(this.otherUserId) },
$addToSet: { manager_ids: ObjectId(this.otherUserId) },
}
this.SubscriptionModel.updateOne.should.have.been.calledOnce
this.SubscriptionModel.updateOne.should.have.been.calledWith(
query,
update
)
done()
}
this.otherUserId
)
const query = {
_id: ObjectId(this.subscription._id),
customAccount: true,
}
const update = {
$set: { admin_id: ObjectId(this.otherUserId) },
$addToSet: { manager_ids: ObjectId(this.otherUserId) },
}
this.SubscriptionModel.updateOne.should.have.been.calledOnce
this.SubscriptionModel.updateOne.should.have.been.calledWith(
query,
update
)
})
it('should remove the manager for non-group subscriptions', function (done) {
this.SubscriptionUpdater.updateAdmin(
it('should remove the manager for non-group subscriptions', async function () {
await this.SubscriptionUpdater.promises.updateAdmin(
this.subscription,
this.otherUserId,
err => {
if (err != null) {
return done(err)
}
const query = {
_id: ObjectId(this.subscription._id),
customAccount: true,
}
const update = {
$set: {
admin_id: ObjectId(this.otherUserId),
manager_ids: [ObjectId(this.otherUserId)],
},
}
this.SubscriptionModel.updateOne.should.have.been.calledOnce
this.SubscriptionModel.updateOne.should.have.been.calledWith(
query,
update
)
done()
}
this.otherUserId
)
const query = {
_id: ObjectId(this.subscription._id),
customAccount: true,
}
const update = {
$set: {
admin_id: ObjectId(this.otherUserId),
manager_ids: [ObjectId(this.otherUserId)],
},
}
this.SubscriptionModel.updateOne.should.have.been.calledOnce
this.SubscriptionModel.updateOne.should.have.been.calledWith(
query,
update
)
})
})
describe('syncSubscription', function () {
beforeEach(function () {
this.SubscriptionLocator.getUsersSubscription.callsArgWith(
1,
null,
this.SubscriptionLocator.promises.getUsersSubscription.resolves(
this.subscription
)
this.SubscriptionUpdater.updateSubscriptionFromRecurly = sinon
.stub()
.yields()
})
it('should update the subscription if the user already is admin of one', function (done) {
this.SubscriptionUpdater.syncSubscription(
it('should update the subscription if the user already is admin of one', async function () {
await this.SubscriptionUpdater.promises.syncSubscription(
this.recurlySubscription,
this.adminUser._id,
err => {
if (err != null) {
return done(err)
}
this.SubscriptionLocator.getUsersSubscription
.calledWith(this.adminUser._id)
.should.equal(true)
done()
}
this.adminUser._id
)
this.SubscriptionLocator.promises.getUsersSubscription
.calledWith(this.adminUser._id)
.should.equal(true)
})
it('should not call updateFeatures with group subscription if recurly subscription is not expired', function (done) {
this.SubscriptionUpdater.syncSubscription(
it('should not call updateFeatures with group subscription if recurly subscription is not expired', async function () {
await this.SubscriptionUpdater.promises.syncSubscription(
this.recurlySubscription,
this.adminUser._id,
err => {
if (err != null) {
return done(err)
}
this.SubscriptionLocator.getUsersSubscription
.calledWith(this.adminUser._id)
.should.equal(true)
this.UserFeaturesUpdater.updateFeatures.called.should.equal(false)
done()
}
this.adminUser._id
)
this.SubscriptionLocator.promises.getUsersSubscription
.calledWith(this.adminUser._id)
.should.equal(true)
this.UserFeaturesUpdater.promises.updateFeatures.called.should.equal(
false
)
})
})
describe('updateSubscriptionFromRecurly', function () {
beforeEach(function () {
this.FeaturesUpdater.refreshFeatures = sinon.stub().callsArgWith(2)
})
afterEach(function () {
this.subscription.member_ids = []
})
it('should update the subscription with token etc when not expired', function (done) {
this.SubscriptionUpdater.updateSubscriptionFromRecurly(
it('should update the subscription with token etc when not expired', async function () {
await this.SubscriptionUpdater.promises.updateSubscriptionFromRecurly(
this.recurlySubscription,
this.subscription,
{},
err => {
if (err != null) {
return done(err)
}
this.subscription.recurlySubscription_id.should.equal(
this.recurlySubscription.uuid
)
this.subscription.planCode.should.equal(
this.recurlySubscription.plan.plan_code
)
this.subscription.save.called.should.equal(true)
this.FeaturesUpdater.refreshFeatures
.calledWith(this.adminUser._id)
.should.equal(true)
done()
}
{}
)
this.subscription.recurlySubscription_id.should.equal(
this.recurlySubscription.uuid
)
this.subscription.planCode.should.equal(
this.recurlySubscription.plan.plan_code
)
this.subscription.save.called.should.equal(true)
this.FeaturesUpdater.promises.refreshFeatures
.calledWith(this.adminUser._id)
.should.equal(true)
})
it('should remove the subscription when expired', function (done) {
it('should remove the subscription when expired', async function () {
this.recurlySubscription.state = 'expired'
this.SubscriptionUpdater.updateSubscriptionFromRecurly(
await this.SubscriptionUpdater.promises.updateSubscriptionFromRecurly(
this.recurlySubscription,
this.subscription,
{},
err => {
if (err != null) {
return done(err)
}
done()
}
{}
)
})
it('should update all the users features', function (done) {
it('should update all the users features', async function () {
this.subscription.member_ids = this.allUserIds
this.SubscriptionUpdater.updateSubscriptionFromRecurly(
await this.SubscriptionUpdater.promises.updateSubscriptionFromRecurly(
this.recurlySubscription,
this.subscription,
{},
err => {
if (err != null) {
return done(err)
}
this.FeaturesUpdater.refreshFeatures
.calledWith(this.adminUser._id)
.should.equal(true)
this.FeaturesUpdater.refreshFeatures
.calledWith(this.allUserIds[0])
.should.equal(true)
this.FeaturesUpdater.refreshFeatures
.calledWith(this.allUserIds[1])
.should.equal(true)
this.FeaturesUpdater.refreshFeatures
.calledWith(this.allUserIds[2])
.should.equal(true)
done()
}
{}
)
this.FeaturesUpdater.promises.refreshFeatures
.calledWith(this.adminUser._id)
.should.equal(true)
this.FeaturesUpdater.promises.refreshFeatures
.calledWith(this.allUserIds[0])
.should.equal(true)
this.FeaturesUpdater.promises.refreshFeatures
.calledWith(this.allUserIds[1])
.should.equal(true)
this.FeaturesUpdater.promises.refreshFeatures
.calledWith(this.allUserIds[2])
.should.equal(true)
})
it('should set group to true and save how many members can be added to group', function (done) {
this.PlansLocator.findLocalPlanInSettings
.withArgs(this.recurlySubscription.plan.plan_code)
.returns({ groupPlan: true, membersLimit: 5 })
this.SubscriptionUpdater.updateSubscriptionFromRecurly(
it('should set group to true and save how many members can be added to group', async function () {
this.recurlyPlan.groupPlan = true
this.recurlyPlan.membersLimit = 5
await this.SubscriptionUpdater.promises.updateSubscriptionFromRecurly(
this.recurlySubscription,
this.subscription,
{},
err => {
if (err != null) {
return done(err)
}
this.subscription.membersLimit.should.equal(5)
this.subscription.groupPlan.should.equal(true)
this.subscription.member_ids.should.deep.equal([
this.subscription.admin_id,
])
done()
}
{}
)
this.subscription.membersLimit.should.equal(5)
this.subscription.groupPlan.should.equal(true)
this.subscription.member_ids.should.deep.equal([
this.subscription.admin_id,
])
})
it('should delete and replace subscription when downgrading from group to individual plan', function (done) {
this.PlansLocator.findLocalPlanInSettings
.withArgs(this.recurlySubscription.plan.plan_code)
.returns({ groupPlan: false })
this.SubscriptionUpdater._deleteAndReplaceSubscriptionFromRecurly = sinon
.stub()
.yields()
this.SubscriptionUpdater.updateSubscriptionFromRecurly(
it('should delete and replace subscription when downgrading from group to individual plan', async function () {
this.recurlyPlan.groupPlan = false
await this.SubscriptionUpdater.promises.updateSubscriptionFromRecurly(
this.recurlySubscription,
this.groupSubscription,
{},
err => {
if (err != null) {
return done(err)
}
done()
}
{}
)
})
it('should not set group to true or set groupPlan', function (done) {
this.SubscriptionUpdater.updateSubscriptionFromRecurly(
it('should not set group to true or set groupPlan', async function () {
await this.SubscriptionUpdater.promises.updateSubscriptionFromRecurly(
this.recurlySubscription,
this.subscription,
{},
err => {
if (err != null) {
return done(err)
}
assert.notEqual(this.subscription.membersLimit, 5)
assert.notEqual(this.subscription.groupPlan, true)
done()
}
{}
)
assert.notEqual(this.subscription.membersLimit, 5)
assert.notEqual(this.subscription.groupPlan, true)
})
describe('when the plan allows adding more seats', function () {
beforeEach(function () {
this.membersLimitAddOn = 'add_on1'
this.PlansLocator.findLocalPlanInSettings
.withArgs(this.recurlySubscription.plan.plan_code)
.returns({
groupPlan: true,
membersLimit: 5,
membersLimitAddOn: this.membersLimitAddOn,
})
this.recurlyPlan.groupPlan = true
this.recurlyPlan.membersLimit = 5
this.recurlyPlan.membersLimitAddOn = this.membersLimitAddOn
})
function expectMembersLimit(limit) {
it('should set the membersLimit accordingly', function (done) {
this.SubscriptionUpdater.updateSubscriptionFromRecurly(
it('should set the membersLimit accordingly', async function () {
await this.SubscriptionUpdater.promises.updateSubscriptionFromRecurly(
this.recurlySubscription,
this.subscription,
{},
error => {
if (error) return done(error)
expect(this.subscription.membersLimit).to.equal(limit)
done()
}
{}
)
expect(this.subscription.membersLimit).to.equal(limit)
})
}
@@ -431,117 +376,34 @@ describe('SubscriptionUpdater', function () {
})
describe('addUserToGroup', function () {
beforeEach(function () {
this.FeaturesUpdater.refreshFeatures = sinon.stub().yields(null, {})
this.FeaturesUpdater.isFeatureSetBetter = sinon.stub()
this.FeaturesUpdater.isFeatureSetBetter
.withArgs(
{
collaborators: 10,
compileTimeout: 60,
dropbox: true,
},
{}
)
.returns(true)
this.FeaturesUpdater.isFeatureSetBetter
.withArgs(
{
collaborators: -1,
compileTimeout: 240,
dropbox: true,
},
{
collaborators: 10,
compileTimeout: 60,
dropbox: true,
}
)
.returns(true)
this.FeaturesUpdater.isFeatureSetBetter
.withArgs(
{
collaborators: -1,
compileTimeout: 240,
dropbox: true,
},
{}
)
.returns(true)
this.PlansLocator.findLocalPlanInSettings = sinon.stub()
this.PlansLocator.findLocalPlanInSettings
.withArgs(this.groupSubscription.planCode)
.returns({
planCode: this.groupSubscription.planCode,
features: {
collaborators: 10,
compileTimeout: 60,
dropbox: true,
},
})
this.PlansLocator.findLocalPlanInSettings
.withArgs(this.betterGroupSubscription.planCode)
.returns({
planCode: this.betterGroupSubscription.planCode,
features: {
collaborators: -1,
compileTimeout: 240,
dropbox: true,
},
})
it('should add the user ids to the group as a set', async function () {
await this.SubscriptionUpdater.promises.addUserToGroup(
this.subscription._id,
this.otherUserId
)
const searchOps = { _id: this.subscription._id }
const insertOperation = {
$addToSet: { member_ids: this.otherUserId },
}
this.SubscriptionModel.updateOne
.calledWith(searchOps, insertOperation)
.should.equal(true)
})
it('should add the user ids to the group as a set', function (done) {
this.SubscriptionUpdater.addUserToGroup(
it('should update the users features', async function () {
await this.SubscriptionUpdater.promises.addUserToGroup(
this.subscription._id,
this.otherUserId,
() => {
const searchOps = { _id: this.subscription._id }
const insertOperation = {
$addToSet: { member_ids: this.otherUserId },
}
this.updateStub
.calledWith(searchOps, insertOperation)
.should.equal(true)
done()
}
)
})
it('should update the users features', function (done) {
this.SubscriptionUpdater.addUserToGroup(
this.subscription._id,
this.otherUserId,
() => {
this.FeaturesUpdater.refreshFeatures
.calledWith(this.otherUserId)
.should.equal(true)
done()
}
)
})
it('should set the group plan code user property', function (done) {
this.SubscriptionUpdater.addUserToGroup(
this.subscription._id,
this.otherUserId,
() => {
sinon.assert.calledWith(
this.AnalyticsManager.setUserPropertyForUser,
this.otherUserId,
'group-subscription-plan-code',
'group_subscription'
)
done()
}
this.otherUserId
)
this.FeaturesUpdater.promises.refreshFeatures
.calledWith(this.otherUserId)
.should.equal(true)
})
it('should set the group plan code user property to the best plan with 1 group subscription', async function () {
this.SubscriptionLocator.promises.getMemberSubscriptions = sinon
.stub()
this.SubscriptionLocator.promises.getMemberSubscriptions
.withArgs(this.otherUserId)
.returns([this.groupSubscription])
.resolves([this.groupSubscription])
await this.SubscriptionUpdater.promises.addUserToGroup(
this.groupSubscription._id,
this.otherUserId
@@ -555,10 +417,9 @@ describe('SubscriptionUpdater', function () {
})
it('should set the group plan code user property to the best plan with 2 group subscriptions', async function () {
this.SubscriptionLocator.promises.getMemberSubscriptions = sinon
.stub()
this.SubscriptionLocator.promises.getMemberSubscriptions
.withArgs(this.otherUserId)
.returns([this.groupSubscription, this.betterGroupSubscription])
.resolves([this.groupSubscription, this.betterGroupSubscription])
await this.SubscriptionUpdater.promises.addUserToGroup(
this.betterGroupSubscription._id,
this.otherUserId
@@ -572,10 +433,9 @@ describe('SubscriptionUpdater', function () {
})
it('should set the group plan code user property to the best plan with 2 group subscriptions in reverse order', async function () {
this.SubscriptionLocator.promises.getMemberSubscriptions = sinon
.stub()
this.SubscriptionLocator.promises.getMemberSubscriptions
.withArgs(this.otherUserId)
.returns([this.betterGroupSubscription, this.groupSubscription])
.resolves([this.betterGroupSubscription, this.groupSubscription])
await this.SubscriptionUpdater.promises.addUserToGroup(
this.betterGroupSubscription._id,
this.otherUserId
@@ -591,95 +451,84 @@ describe('SubscriptionUpdater', function () {
describe('removeUserFromGroups', function () {
beforeEach(function () {
this.FeaturesUpdater.refreshFeatures = sinon.stub().yields(null, {})
this.FeaturesUpdater.isFeatureSetBetter = sinon.stub().returns(true)
this.UserGetter.getUser.yields(null, {})
this.fakeSubscriptions = [{ _id: 'fake-id-1' }, { _id: 'fake-id-2' }]
this.SubscriptionLocator.getMemberSubscriptions.yields(
null,
this.SubscriptionLocator.promises.getMemberSubscriptions.resolves(
this.fakeSubscriptions
)
this.SubscriptionLocator.promises.getMemberSubscriptions.returns([])
})
it('should pull the users id from the group', function (done) {
this.SubscriptionUpdater.removeUserFromGroup(
it('should pull the users id from the group', async function () {
await this.SubscriptionUpdater.promises.removeUserFromGroup(
this.subscription._id,
this.otherUserId
)
const removeOperation = { $pull: { member_ids: this.otherUserId } }
this.SubscriptionModel.updateOne
.calledWith({ _id: this.subscription._id }, removeOperation)
.should.equal(true)
})
it('should set the group plan code user property when removing user from group', async function () {
await this.SubscriptionUpdater.promises.removeUserFromGroup(
this.subscription._id,
this.otherUserId
)
sinon.assert.calledWith(
this.AnalyticsManager.setUserPropertyForUser,
this.otherUserId,
() => {
const removeOperation = { $pull: { member_ids: this.otherUserId } }
this.updateStub
.calledWith({ _id: this.subscription._id }, removeOperation)
.should.equal(true)
done()
}
'group-subscription-plan-code',
null
)
})
it('should set the group plan code user property when removing user from group', function (done) {
this.SubscriptionUpdater.removeUserFromGroup(
this.subscription._id,
it('should set the group plan code user property when removing user from all groups', async function () {
await this.SubscriptionUpdater.promises.removeUserFromAllGroups(
this.otherUserId
)
sinon.assert.calledWith(
this.AnalyticsManager.setUserPropertyForUser,
this.otherUserId,
() => {
sinon.assert.calledWith(
this.AnalyticsManager.setUserPropertyForUser,
this.otherUserId,
'group-subscription-plan-code',
null
)
done()
}
'group-subscription-plan-code',
null
)
})
it('should set the group plan code user property when removing user from all groups', function (done) {
this.SubscriptionUpdater.removeUserFromAllGroups(this.otherUserId, () => {
sinon.assert.calledWith(
this.AnalyticsManager.setUserPropertyForUser,
this.otherUserId,
'group-subscription-plan-code',
null
)
done()
})
})
it('should pull the users id from all groups', function (done) {
this.SubscriptionUpdater.removeUserFromAllGroups(this.otherUserId, () => {
const filter = { _id: ['fake-id-1', 'fake-id-2'] }
const removeOperation = { $pull: { member_ids: this.otherUserId } }
sinon.assert.calledWith(this.updateManyStub, filter, removeOperation)
done()
})
})
it('should update the users features', function (done) {
this.SubscriptionUpdater.removeUserFromGroup(
this.subscription._id,
this.otherUserId,
() => {
this.FeaturesUpdater.refreshFeatures
.calledWith(this.otherUserId)
.should.equal(true)
done()
}
it('should pull the users id from all groups', async function () {
await this.SubscriptionUpdater.promises.removeUserFromAllGroups(
this.otherUserId
)
const filter = { _id: ['fake-id-1', 'fake-id-2'] }
const removeOperation = { $pull: { member_ids: this.otherUserId } }
sinon.assert.calledWith(
this.SubscriptionModel.updateMany,
filter,
removeOperation
)
})
it('should update the users features', async function () {
await this.SubscriptionUpdater.promises.removeUserFromGroup(
this.subscription._id,
this.otherUserId
)
this.FeaturesUpdater.promises.refreshFeatures
.calledWith(this.otherUserId)
.should.equal(true)
})
})
describe('deleteSubscription', function () {
beforeEach(function (done) {
beforeEach(async function () {
this.subscription = {
_id: ObjectId().toString(),
mock: 'subscription',
admin_id: ObjectId(),
member_ids: [ObjectId(), ObjectId(), ObjectId()],
}
this.SubscriptionLocator.getSubscription = sinon
.stub()
.yields(null, this.subscription)
this.FeaturesUpdater.refreshFeatures = sinon.stub().yields()
this.SubscriptionUpdater.deleteSubscription(this.subscription, {}, done)
await this.SubscriptionUpdater.promises.deleteSubscription(
this.subscription,
{}
)
})
it('should remove the subscription', function () {
@@ -689,14 +538,14 @@ describe('SubscriptionUpdater', function () {
})
it('should downgrade the admin_id', function () {
this.FeaturesUpdater.refreshFeatures
this.FeaturesUpdater.promises.refreshFeatures
.calledWith(this.subscription.admin_id)
.should.equal(true)
})
it('should downgrade all of the members', function () {
for (const userId of this.subscription.member_ids) {
this.FeaturesUpdater.refreshFeatures
this.FeaturesUpdater.promises.refreshFeatures
.calledWith(userId)
.should.equal(true)
}