| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603 |
- const SandboxedModule = require('sandboxed-module')
- const sinon = require('sinon')
- const { expect } = require('chai')
- const modulePath = require('path').join(
- __dirname,
- '../../../../app/src/Features/Subscription/LimitationsManager'
- )
- describe('LimitationsManager', function () {
- beforeEach(function () {
- this.user = {
- _id: (this.userId = 'user-id'),
- features: { collaborators: 1 },
- }
- this.project = {
- _id: (this.projectId = 'project-id'),
- owner_ref: this.userId,
- }
- this.ProjectGetter = {
- promises: {
- getProject: sinon.stub().callsFake(async (projectId, fields) => {
- if (projectId === this.projectId) {
- return this.project
- } else {
- return null
- }
- }),
- },
- }
- this.UserGetter = {
- promises: {
- getUser: sinon.stub().callsFake(async (userId, filter) => {
- if (userId === this.userId) {
- return this.user
- } else {
- return null
- }
- }),
- },
- }
- this.SubscriptionLocator = {
- promises: {
- getUsersSubscription: sinon.stub().resolves(),
- getSubscription: sinon.stub().resolves(),
- getMemberSubscriptions: sinon.stub().resolves(),
- },
- }
- this.CollaboratorsGetter = {
- promises: {
- getInvitedEditCollaboratorCount: sinon.stub().resolves(0),
- getMemberIdPrivilegeLevel: sinon.stub(),
- },
- }
- this.CollaboratorsInviteGetter = {
- promises: {
- getEditInviteCount: sinon.stub().resolves(0),
- },
- }
- this.LimitationsManager = SandboxedModule.require(modulePath, {
- requires: {
- '../Project/ProjectGetter': this.ProjectGetter,
- '../User/UserGetter': this.UserGetter,
- './SubscriptionLocator': this.SubscriptionLocator,
- '@overleaf/settings': (this.Settings = {}),
- '../Collaborators/CollaboratorsGetter': this.CollaboratorsGetter,
- '../Collaborators/CollaboratorsInviteGetter':
- this.CollaboratorsInviteGetter,
- './V1SubscriptionManager': this.V1SubscriptionManager,
- },
- })
- })
- describe('allowedNumberOfCollaboratorsInProject', function () {
- describe('when the project is owned by a user without a subscription', function () {
- beforeEach(function () {
- this.Settings.defaultFeatures = { collaborators: 23 }
- this.project.owner_ref = this.userId
- delete this.user.features
- })
- it('should return the default number', async function () {
- const result =
- await this.LimitationsManager.promises.allowedNumberOfCollaboratorsInProject(
- this.projectId
- )
- expect(result).to.equal(this.Settings.defaultFeatures.collaborators)
- })
- })
- describe('when the project is owned by a user with a subscription', function () {
- beforeEach(function () {
- this.project.owner_ref = this.userId
- this.user.features = { collaborators: 21 }
- })
- it('should return the number of collaborators the user is allowed', async function () {
- const result =
- await this.LimitationsManager.promises.allowedNumberOfCollaboratorsInProject(
- this.projectId
- )
- expect(result).to.equal(this.user.features.collaborators)
- })
- })
- })
- describe('allowedNumberOfCollaboratorsForUser', function () {
- describe('when the user has no features', function () {
- beforeEach(function () {
- this.Settings.defaultFeatures = { collaborators: 23 }
- delete this.user.features
- })
- it('should return the default number', async function () {
- const result =
- await this.LimitationsManager.promises.allowedNumberOfCollaboratorsForUser(
- this.userId
- )
- expect(result).to.equal(this.Settings.defaultFeatures.collaborators)
- })
- })
- describe('when the user has features', function () {
- beforeEach(async function () {
- this.user.features = { collaborators: 21 }
- this.result =
- await this.LimitationsManager.promises.allowedNumberOfCollaboratorsForUser(
- this.userId
- )
- })
- it('should return the number of collaborators the user is allowed', function () {
- expect(this.result).to.equal(this.user.features.collaborators)
- })
- })
- })
- describe('canAcceptEditCollaboratorInvite', function () {
- describe('when the project has fewer collaborators than allowed', function () {
- beforeEach(function () {
- this.current_number = 1
- this.user.features.collaborators = 2
- this.CollaboratorsGetter.promises.getInvitedEditCollaboratorCount =
- sinon.stub().resolves(this.current_number)
- })
- it('should return true', async function () {
- const result =
- await this.LimitationsManager.promises.canAcceptEditCollaboratorInvite(
- this.projectId
- )
- expect(result).to.be.true
- })
- })
- describe('when accepting the invite would exceed the collaborator limit', function () {
- beforeEach(function () {
- this.current_number = 2
- this.user.features.collaborators = 2
- this.CollaboratorsGetter.promises.getInvitedEditCollaboratorCount =
- sinon.stub().resolves(this.current_number)
- })
- it('should return false', async function () {
- const result =
- await this.LimitationsManager.promises.canAcceptEditCollaboratorInvite(
- this.projectId
- )
- expect(result).to.be.false
- })
- })
- describe('when the project has more collaborators than allowed', function () {
- beforeEach(function () {
- this.current_number = 3
- this.user.features.collaborators = 2
- this.CollaboratorsGetter.promises.getInvitedEditCollaboratorCount =
- sinon.stub().resolves(this.current_number)
- })
- it('should return false', async function () {
- const result =
- await this.LimitationsManager.promises.canAcceptEditCollaboratorInvite(
- this.projectId
- )
- expect(result).to.be.false
- })
- })
- describe('when the project has infinite collaborators', function () {
- beforeEach(function () {
- this.current_number = 100
- this.user.features.collaborators = -1
- this.CollaboratorsGetter.promises.getInvitedEditCollaboratorCount =
- sinon.stub().resolves(this.current_number)
- })
- it('should return true', async function () {
- const result =
- await this.LimitationsManager.promises.canAcceptEditCollaboratorInvite(
- this.projectId
- )
- expect(result).to.be.true
- })
- })
- })
- describe('canAddXEditCollaborators', function () {
- describe('when the project has fewer collaborators than allowed', function () {
- beforeEach(function () {
- this.current_number = 1
- this.user.features.collaborators = 2
- this.invite_count = 0
- this.CollaboratorsGetter.promises.getInvitedEditCollaboratorCount =
- sinon.stub().resolves(this.current_number)
- this.CollaboratorsInviteGetter.promises.getEditInviteCount = sinon
- .stub()
- .resolves(this.invite_count)
- })
- it('should return true', async function () {
- const result =
- await this.LimitationsManager.promises.canAddXEditCollaborators(
- this.projectId,
- 1
- )
- expect(result).to.be.true
- })
- })
- describe('when the project has fewer collaborators and invites than allowed', function () {
- beforeEach(function () {
- this.current_number = 1
- this.user.features.collaborators = 4
- this.invite_count = 1
- this.CollaboratorsGetter.promises.getInvitedEditCollaboratorCount =
- sinon.stub().resolves(this.current_number)
- this.CollaboratorsInviteGetter.promises.getEditInviteCount = sinon
- .stub()
- .resolves(this.invite_count)
- })
- it('should return true', async function () {
- const result =
- await this.LimitationsManager.promises.canAddXEditCollaborators(
- this.projectId,
- 1
- )
- expect(result).to.be.true
- })
- })
- describe('when the project has fewer collaborators than allowed but I want to add more than allowed', function () {
- beforeEach(function () {
- this.current_number = 1
- this.user.features.collaborators = 2
- this.invite_count = 0
- this.CollaboratorsGetter.promises.getInvitedEditCollaboratorCount =
- sinon.stub().resolves(this.current_number)
- this.CollaboratorsInviteGetter.promises.getEditInviteCount = sinon
- .stub()
- .resolves(this.invite_count)
- })
- it('should return false', async function () {
- const result =
- await this.LimitationsManager.promises.canAddXEditCollaborators(
- this.projectId,
- 2
- )
- expect(result).to.be.false
- })
- })
- describe('when the project has more collaborators than allowed', function () {
- beforeEach(function () {
- this.current_number = 3
- this.user.features.collaborators = 2
- this.invite_count = 0
- this.CollaboratorsGetter.promises.getInvitedEditCollaboratorCount =
- sinon.stub().resolves(this.current_number)
- this.CollaboratorsInviteGetter.promises.getEditInviteCount = sinon
- .stub()
- .resolves(this.invite_count)
- })
- it('should return false', async function () {
- const result =
- await this.LimitationsManager.promises.canAddXEditCollaborators(
- this.projectId,
- 1
- )
- expect(result).to.be.false
- })
- })
- describe('when the project has infinite collaborators', function () {
- beforeEach(function () {
- this.current_number = 100
- this.user.features.collaborators = -1
- this.invite_count = 0
- this.CollaboratorsGetter.promises.getInvitedEditCollaboratorCount =
- sinon.stub().resolves(this.current_number)
- this.CollaboratorsInviteGetter.promises.getEditInviteCount = sinon
- .stub()
- .resolves(this.invite_count)
- })
- it('should return true', async function () {
- const result =
- await this.LimitationsManager.promises.canAddXEditCollaborators(
- this.projectId,
- 1
- )
- expect(result).to.be.true
- })
- })
- describe('when the project has more invites than allowed', function () {
- beforeEach(function () {
- this.current_number = 0
- this.user.features.collaborators = 2
- this.invite_count = 2
- this.CollaboratorsGetter.promises.getInvitedEditCollaboratorCount =
- sinon.stub().resolves(this.current_number)
- this.CollaboratorsInviteGetter.promises.getEditInviteCount = sinon
- .stub()
- .resolves(this.invite_count)
- })
- it('should return false', async function () {
- const result =
- await this.LimitationsManager.promises.canAddXEditCollaborators(
- this.projectId,
- 1
- )
- expect(result).to.be.false
- })
- })
- describe('when the project has more invites and collaborators than allowed', function () {
- beforeEach(function () {
- this.current_number = 1
- this.user.features.collaborators = 2
- this.invite_count = 1
- this.CollaboratorsGetter.promises.getInvitedEditCollaboratorCount =
- sinon.stub().resolves(this.current_number)
- this.CollaboratorsInviteGetter.promises.getEditInviteCount = sinon
- .stub()
- .resolves(this.invite_count)
- })
- it('should return false', async function () {
- const result =
- await this.LimitationsManager.promises.canAddXEditCollaborators(
- this.projectId,
- 1
- )
- expect(result).to.be.false
- })
- })
- })
- describe('canChangeCollaboratorPrivilegeLevel', function () {
- beforeEach(function () {
- this.collaboratorId = 'collaborator-id'
- this.CollaboratorsGetter.promises.getMemberIdPrivilegeLevel.resolves(
- 'readOnly'
- )
- })
- describe("when the limit hasn't been reached", function () {
- it('accepts changing a viewer to an editor', async function () {
- const result =
- await this.LimitationsManager.promises.canChangeCollaboratorPrivilegeLevel(
- this.projectId,
- this.collaboratorId,
- 'readAndWrite'
- )
- expect(result).to.be.true
- })
- })
- describe('when the limit has been reached', function () {
- beforeEach(function () {
- this.CollaboratorsGetter.promises.getInvitedEditCollaboratorCount.resolves(
- 1
- )
- })
- it('accepts changing a reviewer to an editor', async function () {
- this.CollaboratorsGetter.promises.getMemberIdPrivilegeLevel.resolves(
- 'review'
- )
- const result =
- await this.LimitationsManager.promises.canChangeCollaboratorPrivilegeLevel(
- this.projectId,
- this.collaboratorId,
- 'readAndWrite'
- )
- expect(result).to.be.true
- })
- it('rejects changing a viewer to a reviewer', async function () {
- const result =
- await this.LimitationsManager.promises.canChangeCollaboratorPrivilegeLevel(
- this.projectId,
- this.collaboratorId,
- 'review'
- )
- expect(result).to.be.false
- })
- })
- })
- describe('userHasSubscription', function () {
- beforeEach(function () {
- this.SubscriptionLocator.promises.getUsersSubscription = sinon
- .stub()
- .resolves()
- })
- it('should return true if the recurly token is set', async function () {
- this.SubscriptionLocator.promises.getUsersSubscription = sinon
- .stub()
- .resolves({
- recurlySubscription_id: '1234',
- })
- const { hasSubscription } =
- await this.LimitationsManager.promises.userHasSubscription(this.user)
- expect(hasSubscription).to.be.true
- })
- it('should return false if the recurly token is not set', async function () {
- this.SubscriptionLocator.promises.getUsersSubscription.resolves({})
- const { hasSubscription } =
- await this.LimitationsManager.promises.userHasSubscription(this.user)
- expect(hasSubscription).to.be.false
- })
- it('should return false if the subscription is undefined', async function () {
- this.SubscriptionLocator.promises.getUsersSubscription.resolves()
- const { hasSubscription } =
- await this.LimitationsManager.promises.userHasSubscription(this.user)
- expect(hasSubscription).to.be.false
- })
- it('should return the subscription', async function () {
- const stubbedSubscription = { freeTrial: {}, token: '' }
- this.SubscriptionLocator.promises.getUsersSubscription.resolves(
- stubbedSubscription
- )
- const { subscription } =
- await this.LimitationsManager.promises.userHasSubscription(this.user)
- expect(subscription).to.deep.equal(stubbedSubscription)
- })
- describe('when user has a custom account', function () {
- beforeEach(function () {
- this.fakeSubscription = { customAccount: true }
- this.SubscriptionLocator.promises.getUsersSubscription.resolves(
- this.fakeSubscription
- )
- })
- it('should return true', async function () {
- const { hasSubscription } =
- await this.LimitationsManager.promises.userHasSubscription(this.user)
- expect(hasSubscription).to.be.true
- })
- it('should return the subscription', async function () {
- const { subscription } =
- await this.LimitationsManager.promises.userHasSubscription(this.user)
- expect(subscription).to.deep.equal(this.fakeSubscription)
- })
- })
- })
- describe('userIsMemberOfGroupSubscription', function () {
- beforeEach(function () {
- this.SubscriptionLocator.promises.getMemberSubscriptions = sinon
- .stub()
- .resolves()
- })
- it('should return false if there are no groups subcriptions', async function () {
- this.SubscriptionLocator.promises.getMemberSubscriptions.resolves([])
- const { isMember } =
- await this.LimitationsManager.promises.userIsMemberOfGroupSubscription(
- this.user
- )
- expect(isMember).to.be.false
- })
- it('should return true if there are no groups subcriptions', async function () {
- const subscriptions = ['mock-subscription']
- this.SubscriptionLocator.promises.getMemberSubscriptions.resolves(
- subscriptions
- )
- const { isMember, subscriptions: retSubscriptions } =
- await this.LimitationsManager.promises.userIsMemberOfGroupSubscription(
- this.user
- )
- expect(isMember).to.be.true
- expect(retSubscriptions).to.deep.equal(subscriptions)
- })
- })
- describe('hasPaidSubscription', function () {
- beforeEach(function () {
- this.SubscriptionLocator.promises.getMemberSubscriptions = sinon
- .stub()
- .resolves([])
- this.SubscriptionLocator.promises.getUsersSubscription = sinon
- .stub()
- .resolves(null)
- })
- it('should return true if userIsMemberOfGroupSubscription', async function () {
- this.SubscriptionLocator.promises.getMemberSubscriptions = sinon
- .stub()
- .resolves([{ _id: '123' }])
- const { hasPaidSubscription } =
- await this.LimitationsManager.promises.hasPaidSubscription(this.user)
- expect(hasPaidSubscription).to.be.true
- })
- it('should return true if userHasSubscription', async function () {
- this.SubscriptionLocator.promises.getUsersSubscription = sinon
- .stub()
- .resolves({ recurlySubscription_id: '123' })
- const { hasPaidSubscription } =
- await this.LimitationsManager.promises.hasPaidSubscription(this.user)
- expect(hasPaidSubscription).to.be.true
- })
- it('should return false if none are true', async function () {
- const { hasPaidSubscription } =
- await this.LimitationsManager.promises.hasPaidSubscription(this.user)
- expect(hasPaidSubscription).to.be.false
- })
- it('should have userHasSubscriptionOrIsGroupMember alias', async function () {
- const { hasPaidSubscription } =
- await this.LimitationsManager.promises.userHasSubscriptionOrIsGroupMember(
- this.user
- )
- expect(hasPaidSubscription).to.be.false
- })
- })
- describe('hasGroupMembersLimitReached', function () {
- beforeEach(function () {
- this.subscriptionId = '12312'
- this.subscription = {
- membersLimit: 3,
- member_ids: ['', ''],
- teamInvites: [
- { email: 'bob@example.com', sentAt: new Date(), token: 'hey' },
- ],
- }
- })
- it('should return true if the limit is hit (including members and invites)', async function () {
- this.SubscriptionLocator.promises.getSubscription.resolves(
- this.subscription
- )
- const { limitReached } =
- await this.LimitationsManager.promises.hasGroupMembersLimitReached(
- this.subscriptionId
- )
- expect(limitReached).to.be.true
- })
- it('should return false if the limit is not hit (including members and invites)', async function () {
- this.subscription.membersLimit = 4
- this.SubscriptionLocator.promises.getSubscription.resolves(
- this.subscription
- )
- const { limitReached } =
- await this.LimitationsManager.promises.hasGroupMembersLimitReached(
- this.subscriptionId
- )
- expect(limitReached).to.be.false
- })
- it('should return true if the limit has been exceded (including members and invites)', async function () {
- this.subscription.membersLimit = 2
- this.SubscriptionLocator.promises.getSubscription.resolves(
- this.subscription
- )
- const { limitReached } =
- await this.LimitationsManager.promises.hasGroupMembersLimitReached(
- this.subscriptionId
- )
- expect(limitReached).to.be.true
- })
- })
- })
|