const SandboxedModule = require('sandboxed-module') const { expect } = require('chai') const sinon = require('sinon') const modulePath = '../../../../app/src/Features/Subscription/FeaturesUpdater' const { ObjectId } = require('mongodb') describe('FeaturesUpdater', function() { beforeEach(function() { this.user_id = ObjectId().toString() this.FeaturesUpdater = SandboxedModule.require(modulePath, { requires: { './UserFeaturesUpdater': (this.UserFeaturesUpdater = {}), './SubscriptionLocator': (this.SubscriptionLocator = {}), './PlansLocator': (this.PlansLocator = {}), 'settings-sharelatex': (this.Settings = {}), '../Referal/ReferalFeatures': (this.ReferalFeatures = {}), './V1SubscriptionManager': (this.V1SubscriptionManager = {}), '../Institutions/InstitutionsFeatures': (this.InstitutionsFeatures = {}), '../User/UserGetter': (this.UserGetter = {}), '../../infrastructure/Modules': (this.Modules = { hooks: { fire: sinon.stub() } }) } }) }) 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, (err, features, featuresChanged) => { expect(err).to.not.exist expect(features).to.exist expect(featuresChanged).to.exist } ) }) describe('normally', function() { beforeEach(function() { this.FeaturesUpdater.refreshFeatures(this.user_id, this.callback) }) 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) }) }) 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, this.callback) }) it('should fire module hook to unlink dropbox', function() { this.Modules.hooks.fire .calledWith('removeDropbox', this.user._id) .should.equal(true) }) }) }) describe('_mergeFeatures', function() { it('should prefer priority over standard for compileGroup', function() { expect( this.FeaturesUpdater._mergeFeatures( { compileGroup: 'priority' }, { compileGroup: 'standard' } ) ).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() }) }) 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() }) }) }) describe('when getUser produces an error', function() { beforeEach(function() { this.UserGetter.getUser = sinon .stub() .callsArgWith(2, 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() }) }) }) describe('when getUser does not find a user', function() { beforeEach(function() { this.UserGetter.getUser = sinon.stub().callsArgWith(2, null, null) }) it('should not call refreshFeatures', function(done) { this.call(() => { expect(this.FeaturesUpdater.refreshFeatures.callCount).to.equal(0) done() }) }) it('should not produce an error', function(done) { this.call(err => { expect(err).to.not.exist done() }) }) }) }) })