Selaa lähdekoodia

Merge pull request #24357 from overleaf/em-reviewers-limit

Count reviewers towards the collaborator limit in the backend

GitOrigin-RevId: 352fa37023ffbb032e23a474a81897ca871fee69
Eric Mc Sween 1 vuosi sitten
vanhempi
sitoutus
1fb94dee18

+ 13 - 11
services/web/app/src/Features/Collaborators/CollaboratorsController.mjs

@@ -14,7 +14,6 @@ import { hasAdminAccess } from '../Helpers/AdminAuthorizationHelper.js'
 import TokenAccessHandler from '../TokenAccess/TokenAccessHandler.js'
 import ProjectAuditLogHandler from '../Project/ProjectAuditLogHandler.js'
 import LimitationsManager from '../Subscription/LimitationsManager.js'
-import PrivilegeLevels from '../Authorization/PrivilegeLevels.js'
 
 const ObjectId = mongodb.ObjectId
 
@@ -80,17 +79,20 @@ async function setCollaboratorInfo(req, res, next) {
     const userId = req.params.user_id
     const { privilegeLevel } = req.body
 
-    if (privilegeLevel !== PrivilegeLevels.READ_ONLY) {
-      const allowed =
-        await LimitationsManager.promises.canAddXEditCollaborators(projectId, 1)
-      if (!allowed) {
-        return HttpErrorHandler.forbidden(
-          req,
-          res,
-          'edit collaborator limit reached'
-        )
-      }
+    const allowed =
+      await LimitationsManager.promises.canChangeCollaboratorPrivilegeLevel(
+        projectId,
+        userId,
+        privilegeLevel
+      )
+    if (!allowed) {
+      return HttpErrorHandler.forbidden(
+        req,
+        res,
+        'edit collaborator limit reached'
+      )
     }
+
     await CollaboratorsHandler.promises.setCollaboratorPrivilegeLevel(
       projectId,
       userId,

+ 3 - 2
services/web/app/src/Features/Collaborators/CollaboratorsGetter.js

@@ -128,12 +128,13 @@ async function getMemberIdPrivilegeLevel(userId, projectId) {
 }
 
 async function getInvitedEditCollaboratorCount(projectId) {
-  // Only counts invited members with readAndWrite privilege
+  // Counts invited members with editor or reviewer roles
   const members = await getMemberIdsWithPrivilegeLevels(projectId)
   return members.filter(
     m =>
       m.source === Sources.INVITE &&
-      m.privilegeLevel === PrivilegeLevels.READ_AND_WRITE
+      (m.privilegeLevel === PrivilegeLevels.READ_AND_WRITE ||
+        m.privilegeLevel === PrivilegeLevels.REVIEW)
   ).length
 }
 

+ 55 - 0
services/web/app/src/Features/Subscription/LimitationsManager.js

@@ -1,3 +1,5 @@
+// @ts-check
+
 const logger = require('@overleaf/logger')
 const ProjectGetter = require('../Project/ProjectGetter')
 const UserGetter = require('../User/UserGetter')
@@ -5,6 +7,7 @@ const SubscriptionLocator = require('./SubscriptionLocator')
 const Settings = require('@overleaf/settings')
 const CollaboratorsGetter = require('../Collaborators/CollaboratorsGetter')
 const CollaboratorsInvitesGetter = require('../Collaborators/CollaboratorsInviteGetter')
+const PrivilegeLevels = require('../Authorization/PrivilegeLevels')
 const {
   callbackify,
   callbackifyMultiResult,
@@ -58,6 +61,54 @@ async function canAddXEditCollaborators(
   )
 }
 
+/**
+ * Check whether a collaborator can be switched to the given privilege level
+ *
+ * @param {string} projectId
+ * @param {string} userId
+ * @param {'readOnly' | 'review' | 'readAndWrite'} privilegeLevel
+ * @return {Promise<boolean>}
+ */
+async function canChangeCollaboratorPrivilegeLevel(
+  projectId,
+  userId,
+  privilegeLevel
+) {
+  if (privilegeLevel === PrivilegeLevels.READ_ONLY) {
+    return true
+  }
+
+  const currentPrivilegeLevel =
+    await CollaboratorsGetter.promises.getMemberIdPrivilegeLevel(
+      userId,
+      projectId
+    )
+  if (
+    [PrivilegeLevels.READ_AND_WRITE, PrivilegeLevels.REVIEW].includes(
+      currentPrivilegeLevel
+    )
+  ) {
+    // Current collaborator already takes a slot, so changing the privilege
+    // level won't increase the collaborator count
+    return true
+  }
+
+  const allowedNumber = await allowedNumberOfCollaboratorsInProject(projectId)
+  if (allowedNumber < 0) {
+    // -1 means unlimited
+    return true
+  }
+
+  const slotsTaken =
+    await CollaboratorsGetter.promises.getInvitedEditCollaboratorCount(
+      projectId
+    )
+  const inviteCount =
+    await CollaboratorsInvitesGetter.promises.getEditInviteCount(projectId)
+
+  return slotsTaken + inviteCount < allowedNumber
+}
+
 async function hasPaidSubscription(user) {
   const { hasSubscription, subscription } = await userHasSubscription(user)
   const { isMember } = await userIsMemberOfGroupSubscription(user)
@@ -122,6 +173,9 @@ const LimitationsManager = {
     allowedNumberOfCollaboratorsForUser
   ),
   canAddXEditCollaborators: callbackify(canAddXEditCollaborators),
+  canChangeCollaboratorPrivilegeLevel: callbackify(
+    canChangeCollaboratorPrivilegeLevel
+  ),
   hasPaidSubscription: callbackifyMultiResult(hasPaidSubscription, [
     'hasPaidSubscription',
     'subscription',
@@ -150,6 +204,7 @@ const LimitationsManager = {
     allowedNumberOfCollaboratorsForUser,
     canAcceptEditCollaboratorInvite,
     canAddXEditCollaborators,
+    canChangeCollaboratorPrivilegeLevel,
     hasPaidSubscription,
     userHasSubscriptionOrIsGroupMember,
     userHasSubscription,

+ 16 - 11
services/web/test/unit/src/Collaborators/CollaboratorsControllerTests.mjs

@@ -76,6 +76,7 @@ describe('CollaboratorsController', function () {
     this.LimitationsManager = {
       promises: {
         canAddXEditCollaborators: sinon.stub().resolves(),
+        canChangeCollaboratorPrivilegeLevel: sinon.stub().resolves(true),
       },
     }
 
@@ -310,18 +311,17 @@ describe('CollaboratorsController', function () {
       })
 
       describe('when owner can add new edit collaborators', function () {
-        beforeEach(function () {
-          this.LimitationsManager.promises.canAddXEditCollaborators.resolves(
-            true
-          )
-        })
-
         it('should set privilege level after checking collaborators can be added', function (done) {
           this.res.sendStatus = status => {
             expect(status).to.equal(204)
             expect(
-              this.LimitationsManager.promises.canAddXEditCollaborators
-            ).to.have.been.calledWith(this.projectId, 1)
+              this.LimitationsManager.promises
+                .canChangeCollaboratorPrivilegeLevel
+            ).to.have.been.calledWith(
+              this.projectId,
+              this.user._id,
+              'readAndWrite'
+            )
             done()
           }
           this.CollaboratorsController.setCollaboratorInfo(this.req, this.res)
@@ -330,7 +330,7 @@ describe('CollaboratorsController', function () {
 
       describe('when owner cannot add edit collaborators', function () {
         beforeEach(function () {
-          this.LimitationsManager.promises.canAddXEditCollaborators.resolves(
+          this.LimitationsManager.promises.canChangeCollaboratorPrivilegeLevel.resolves(
             false
           )
         })
@@ -340,8 +340,13 @@ describe('CollaboratorsController', function () {
             expect(req).to.equal(this.req)
             expect(res).to.equal(this.res)
             expect(
-              this.LimitationsManager.promises.canAddXEditCollaborators
-            ).to.have.been.calledWith(this.projectId, 1)
+              this.LimitationsManager.promises
+                .canChangeCollaboratorPrivilegeLevel
+            ).to.have.been.calledWith(
+              this.projectId,
+              this.user._id,
+              'readAndWrite'
+            )
             expect(
               this.CollaboratorsHandler.promises.setCollaboratorPrivilegeLevel
             ).to.not.have.been.called

+ 2 - 2
services/web/test/unit/src/Collaborators/CollaboratorsGetterTests.js

@@ -535,12 +535,12 @@ describe('CollaboratorsGetter', function () {
   })
 
   describe('getInvitedEditCollaboratorCount', function () {
-    it('should return the count of invited edit collaborators (token, readAndWrite)', async function () {
+    it('should return the count of invited edit collaborators (readAndWrite, review)', async function () {
       const count =
         await this.CollaboratorsGetter.promises.getInvitedEditCollaboratorCount(
           this.project._id
         )
-      expect(count).to.equal(2)
+      expect(count).to.equal(4)
     })
   })
 

+ 212 - 243
services/web/test/unit/src/Subscription/LimitationsManagerTests.js

@@ -1,5 +1,4 @@
 const SandboxedModule = require('sandboxed-module')
-const assert = require('assert')
 const sinon = require('sinon')
 const { expect } = require('chai')
 const modulePath = require('path').join(
@@ -50,13 +49,14 @@ describe('LimitationsManager', function () {
 
     this.CollaboratorsGetter = {
       promises: {
-        getInvitedEditCollaboratorCount: sinon.stub().resolves(),
+        getInvitedEditCollaboratorCount: sinon.stub().resolves(0),
+        getMemberIdPrivilegeLevel: sinon.stub(),
       },
     }
 
     this.CollaboratorsInviteGetter = {
       promises: {
-        getEditInviteCount: sinon.stub().resolves(),
+        getEditInviteCount: sinon.stub().resolves(0),
       },
     }
 
@@ -76,76 +76,64 @@ describe('LimitationsManager', function () {
 
   describe('allowedNumberOfCollaboratorsInProject', function () {
     describe('when the project is owned by a user without a subscription', function () {
-      beforeEach(function (done) {
+      beforeEach(function () {
         this.Settings.defaultFeatures = { collaborators: 23 }
         this.project.owner_ref = this.userId
         delete this.user.features
-        this.callback = sinon.stub().callsFake(() => done())
-        this.LimitationsManager.allowedNumberOfCollaboratorsInProject(
-          this.projectId,
-          this.callback
-        )
       })
 
-      it('should return the default number', function () {
-        this.callback
-          .calledWith(null, this.Settings.defaultFeatures.collaborators)
-          .should.equal(true)
+      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 (done) {
+      beforeEach(function () {
         this.project.owner_ref = this.userId
         this.user.features = { collaborators: 21 }
-        this.callback = sinon.stub().callsFake(() => done())
-        this.LimitationsManager.allowedNumberOfCollaboratorsInProject(
-          this.projectId,
-          this.callback
-        )
       })
 
-      it('should return the number of collaborators the user is allowed', function () {
-        this.callback
-          .calledWith(null, this.user.features.collaborators)
-          .should.equal(true)
+      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 (done) {
+      beforeEach(function () {
         this.Settings.defaultFeatures = { collaborators: 23 }
         delete this.user.features
-        this.callback = sinon.stub().callsFake(() => done())
-        this.LimitationsManager.allowedNumberOfCollaboratorsForUser(
-          this.userId,
-          this.callback
-        )
       })
 
-      it('should return the default number', function () {
-        this.callback
-          .calledWith(null, this.Settings.defaultFeatures.collaborators)
-          .should.equal(true)
+      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(function (done) {
+      beforeEach(async function () {
         this.user.features = { collaborators: 21 }
-        this.callback = sinon.stub().callsFake(() => done())
-        this.LimitationsManager.allowedNumberOfCollaboratorsForUser(
-          this.userId,
-          this.callback
-        )
+        this.result =
+          await this.LimitationsManager.promises.allowedNumberOfCollaboratorsForUser(
+            this.userId
+          )
       })
 
       it('should return the number of collaborators the user is allowed', function () {
-        this.callback
-          .calledWith(null, this.user.features.collaborators)
-          .should.equal(true)
+        expect(this.result).to.equal(this.user.features.collaborators)
       })
     })
   })
@@ -222,7 +210,7 @@ describe('LimitationsManager', function () {
 
   describe('canAddXEditCollaborators', function () {
     describe('when the project has fewer collaborators than allowed', function () {
-      beforeEach(function (done) {
+      beforeEach(function () {
         this.current_number = 1
         this.user.features.collaborators = 2
         this.invite_count = 0
@@ -231,21 +219,20 @@ describe('LimitationsManager', function () {
         this.CollaboratorsInviteGetter.promises.getEditInviteCount = sinon
           .stub()
           .resolves(this.invite_count)
-        this.callback = sinon.stub().callsFake(() => done())
-        this.LimitationsManager.canAddXEditCollaborators(
-          this.projectId,
-          1,
-          this.callback
-        )
       })
 
-      it('should return true', function () {
-        this.callback.calledWith(null, true).should.equal(true)
+      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 (done) {
+      beforeEach(function () {
         this.current_number = 1
         this.user.features.collaborators = 4
         this.invite_count = 1
@@ -254,21 +241,20 @@ describe('LimitationsManager', function () {
         this.CollaboratorsInviteGetter.promises.getEditInviteCount = sinon
           .stub()
           .resolves(this.invite_count)
-        this.callback = sinon.stub().callsFake(() => done())
-        this.LimitationsManager.canAddXEditCollaborators(
-          this.projectId,
-          1,
-          this.callback
-        )
       })
 
-      it('should return true', function () {
-        this.callback.calledWith(null, true).should.equal(true)
+      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 (done) {
+      beforeEach(function () {
         this.current_number = 1
         this.user.features.collaborators = 2
         this.invite_count = 0
@@ -277,21 +263,20 @@ describe('LimitationsManager', function () {
         this.CollaboratorsInviteGetter.promises.getEditInviteCount = sinon
           .stub()
           .resolves(this.invite_count)
-        this.callback = sinon.stub().callsFake(() => done())
-        this.LimitationsManager.canAddXEditCollaborators(
-          this.projectId,
-          2,
-          this.callback
-        )
       })
 
-      it('should return false', function () {
-        this.callback.calledWith(null, false).should.equal(true)
+      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 (done) {
+      beforeEach(function () {
         this.current_number = 3
         this.user.features.collaborators = 2
         this.invite_count = 0
@@ -300,21 +285,20 @@ describe('LimitationsManager', function () {
         this.CollaboratorsInviteGetter.promises.getEditInviteCount = sinon
           .stub()
           .resolves(this.invite_count)
-        this.callback = sinon.stub().callsFake(() => done())
-        this.LimitationsManager.canAddXEditCollaborators(
-          this.projectId,
-          1,
-          this.callback
-        )
       })
 
-      it('should return false', function () {
-        this.callback.calledWith(null, false).should.equal(true)
+      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 (done) {
+      beforeEach(function () {
         this.current_number = 100
         this.user.features.collaborators = -1
         this.invite_count = 0
@@ -323,21 +307,20 @@ describe('LimitationsManager', function () {
         this.CollaboratorsInviteGetter.promises.getEditInviteCount = sinon
           .stub()
           .resolves(this.invite_count)
-        this.callback = sinon.stub().callsFake(() => done())
-        this.LimitationsManager.canAddXEditCollaborators(
-          this.projectId,
-          1,
-          this.callback
-        )
       })
 
-      it('should return true', function () {
-        this.callback.calledWith(null, true).should.equal(true)
+      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 (done) {
+      beforeEach(function () {
         this.current_number = 0
         this.user.features.collaborators = 2
         this.invite_count = 2
@@ -346,21 +329,20 @@ describe('LimitationsManager', function () {
         this.CollaboratorsInviteGetter.promises.getEditInviteCount = sinon
           .stub()
           .resolves(this.invite_count)
-        this.callback = sinon.stub().callsFake(() => done())
-        this.LimitationsManager.canAddXEditCollaborators(
-          this.projectId,
-          1,
-          this.callback
-        )
       })
 
-      it('should return false', function () {
-        this.callback.calledWith(null, false).should.equal(true)
+      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 (done) {
+      beforeEach(function () {
         this.current_number = 1
         this.user.features.collaborators = 2
         this.invite_count = 1
@@ -369,16 +351,67 @@ describe('LimitationsManager', function () {
         this.CollaboratorsInviteGetter.promises.getEditInviteCount = sinon
           .stub()
           .resolves(this.invite_count)
-        this.callback = sinon.stub().callsFake(() => done())
-        this.LimitationsManager.canAddXEditCollaborators(
-          this.projectId,
-          1,
-          this.callback
+      })
+
+      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('should return false', function () {
-        this.callback.calledWith(null, false).should.equal(true)
+      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
       })
     })
   })
@@ -390,60 +423,39 @@ describe('LimitationsManager', function () {
         .resolves()
     })
 
-    it('should return true if the recurly token is set', function (done) {
+    it('should return true if the recurly token is set', async function () {
       this.SubscriptionLocator.promises.getUsersSubscription = sinon
         .stub()
         .resolves({
           recurlySubscription_id: '1234',
         })
-      this.LimitationsManager.userHasSubscription(
-        this.user,
-        (err, hasSubscription) => {
-          assert.equal(err, null)
-          hasSubscription.should.equal(true)
-          done()
-        }
-      )
+      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', function (done) {
+    it('should return false if the recurly token is not set', async function () {
       this.SubscriptionLocator.promises.getUsersSubscription.resolves({})
-      this.subscription = {}
-      this.LimitationsManager.userHasSubscription(
-        this.user,
-        (err, hasSubscription) => {
-          assert.equal(err, null)
-          hasSubscription.should.equal(false)
-          done()
-        }
-      )
+      const { hasSubscription } =
+        await this.LimitationsManager.promises.userHasSubscription(this.user)
+      expect(hasSubscription).to.be.false
     })
 
-    it('should return false if the subscription is undefined', function (done) {
+    it('should return false if the subscription is undefined', async function () {
       this.SubscriptionLocator.promises.getUsersSubscription.resolves()
-      this.LimitationsManager.userHasSubscription(
-        this.user,
-        (err, hasSubscription) => {
-          assert.equal(err, null)
-          hasSubscription.should.equal(false)
-          done()
-        }
-      )
+      const { hasSubscription } =
+        await this.LimitationsManager.promises.userHasSubscription(this.user)
+      expect(hasSubscription).to.be.false
     })
 
-    it('should return the subscription', function (done) {
+    it('should return the subscription', async function () {
       const stubbedSubscription = { freeTrial: {}, token: '' }
       this.SubscriptionLocator.promises.getUsersSubscription.resolves(
         stubbedSubscription
       )
-      this.LimitationsManager.userHasSubscription(
-        this.user,
-        (err, hasSubOrIsGroupMember, subscription) => {
-          assert.equal(err, null)
-          subscription.should.deep.equal(stubbedSubscription)
-          done()
-        }
-      )
+      const { subscription } =
+        await this.LimitationsManager.promises.userHasSubscription(this.user)
+      expect(subscription).to.deep.equal(stubbedSubscription)
     })
 
     describe('when user has a custom account', function () {
@@ -454,26 +466,16 @@ describe('LimitationsManager', function () {
         )
       })
 
-      it('should return true', function (done) {
-        this.LimitationsManager.userHasSubscription(
-          this.user,
-          (err, hasSubscription, subscription) => {
-            assert.equal(err, null)
-            hasSubscription.should.equal(true)
-            done()
-          }
-        )
+      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', function (done) {
-        this.LimitationsManager.userHasSubscription(
-          this.user,
-          (err, hasSubscription, subscription) => {
-            assert.equal(err, null)
-            subscription.should.deep.equal(this.fakeSubscription)
-            done()
-          }
-        )
+      it('should return the subscription', async function () {
+        const { subscription } =
+          await this.LimitationsManager.promises.userHasSubscription(this.user)
+        expect(subscription).to.deep.equal(this.fakeSubscription)
       })
     })
   })
@@ -485,32 +487,26 @@ describe('LimitationsManager', function () {
         .resolves()
     })
 
-    it('should return false if there are no groups subcriptions', function (done) {
+    it('should return false if there are no groups subcriptions', async function () {
       this.SubscriptionLocator.promises.getMemberSubscriptions.resolves([])
-      this.LimitationsManager.userIsMemberOfGroupSubscription(
-        this.user,
-        (err, isMember) => {
-          assert.equal(err, null)
-          isMember.should.equal(false)
-          done()
-        }
-      )
+      const { isMember } =
+        await this.LimitationsManager.promises.userIsMemberOfGroupSubscription(
+          this.user
+        )
+      expect(isMember).to.be.false
     })
 
-    it('should return true if there are no groups subcriptions', function (done) {
-      let subscriptions
+    it('should return true if there are no groups subcriptions', async function () {
+      const subscriptions = ['mock-subscription']
       this.SubscriptionLocator.promises.getMemberSubscriptions.resolves(
-        (subscriptions = ['mock-subscription'])
-      )
-      this.LimitationsManager.userIsMemberOfGroupSubscription(
-        this.user,
-        (err, isMember, retSubscriptions) => {
-          assert.equal(err, null)
-          isMember.should.equal(true)
-          retSubscriptions.should.equal(subscriptions)
-          done()
-        }
+        subscriptions
       )
+      const { isMember, subscriptions: retSubscriptions } =
+        await this.LimitationsManager.promises.userIsMemberOfGroupSubscription(
+          this.user
+        )
+      expect(isMember).to.be.true
+      expect(retSubscriptions).to.deep.equal(subscriptions)
     })
   })
 
@@ -524,54 +520,36 @@ describe('LimitationsManager', function () {
         .resolves(null)
     })
 
-    it('should return true if userIsMemberOfGroupSubscription', function (done) {
+    it('should return true if userIsMemberOfGroupSubscription', async function () {
       this.SubscriptionLocator.promises.getMemberSubscriptions = sinon
         .stub()
         .resolves([{ _id: '123' }])
-      this.LimitationsManager.hasPaidSubscription(
-        this.user,
-        (err, hasSubOrIsGroupMember) => {
-          assert.equal(err, null)
-          hasSubOrIsGroupMember.should.equal(true)
-          done()
-        }
-      )
+      const { hasPaidSubscription } =
+        await this.LimitationsManager.promises.hasPaidSubscription(this.user)
+      expect(hasPaidSubscription).to.be.true
     })
 
-    it('should return true if userHasSubscription', function (done) {
+    it('should return true if userHasSubscription', async function () {
       this.SubscriptionLocator.promises.getUsersSubscription = sinon
         .stub()
         .resolves({ recurlySubscription_id: '123' })
-      this.LimitationsManager.hasPaidSubscription(
-        this.user,
-        (err, hasSubOrIsGroupMember) => {
-          assert.equal(err, null)
-          hasSubOrIsGroupMember.should.equal(true)
-          done()
-        }
-      )
+      const { hasPaidSubscription } =
+        await this.LimitationsManager.promises.hasPaidSubscription(this.user)
+      expect(hasPaidSubscription).to.be.true
     })
 
-    it('should return false if none are true', function (done) {
-      this.LimitationsManager.hasPaidSubscription(
-        this.user,
-        (err, hasSubOrIsGroupMember) => {
-          assert.equal(err, null)
-          hasSubOrIsGroupMember.should.equal(false)
-          done()
-        }
-      )
+    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', function (done) {
-      this.LimitationsManager.userHasSubscriptionOrIsGroupMember(
-        this.user,
-        (err, hasSubOrIsGroupMember) => {
-          assert.equal(err, null)
-          hasSubOrIsGroupMember.should.equal(false)
-          done()
-        }
-      )
+    it('should have userHasSubscriptionOrIsGroupMember alias', async function () {
+      const { hasPaidSubscription } =
+        await this.LimitationsManager.promises.userHasSubscriptionOrIsGroupMember(
+          this.user
+        )
+      expect(hasPaidSubscription).to.be.false
     })
   })
 
@@ -587,48 +565,39 @@ describe('LimitationsManager', function () {
       }
     })
 
-    it('should return true if the limit is hit (including members and invites)', function (done) {
+    it('should return true if the limit is hit (including members and invites)', async function () {
       this.SubscriptionLocator.promises.getSubscription.resolves(
         this.subscription
       )
-      this.LimitationsManager.hasGroupMembersLimitReached(
-        this.subscriptionId,
-        (err, limitReached) => {
-          assert.equal(err, null)
-          limitReached.should.equal(true)
-          done()
-        }
-      )
+      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)', function (done) {
+    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
       )
-      this.LimitationsManager.hasGroupMembersLimitReached(
-        this.subscriptionId,
-        (err, limitReached) => {
-          assert.equal(err, null)
-          limitReached.should.equal(false)
-          done()
-        }
-      )
+      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)', function (done) {
+    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
       )
-      this.LimitationsManager.hasGroupMembersLimitReached(
-        this.subscriptionId,
-        (err, limitReached) => {
-          assert.equal(err, null)
-          limitReached.should.equal(true)
-          done()
-        }
-      )
+      const { limitReached } =
+        await this.LimitationsManager.promises.hasGroupMembersLimitReached(
+          this.subscriptionId
+        )
+      expect(limitReached).to.be.true
     })
   })
 })