Explorar el Código

Promisify tests

GitOrigin-RevId: 6f413f4c5ef8d034b4e94afacdf2d7b43c3a8830
Andrew Rumble hace 1 año
padre
commit
2ad9f36706
Se han modificado 25 ficheros con 2873 adiciones y 3348 borrados
  1. 1 0
      services/web/app/src/Features/Project/ProjectEntityUpdateHandler.js
  2. 1 1
      services/web/test/unit/src/Authorization/AuthorizationMiddlewareTests.js
  3. 26 47
      services/web/test/unit/src/BrandVariations/BrandVariationsHandlerTests.js
  4. 48 56
      services/web/test/unit/src/Chat/ChatManagerTests.js
  5. 64 85
      services/web/test/unit/src/Compile/ClsiFormatCheckerTests.js
  6. 21 29
      services/web/test/unit/src/Compile/CompileManagerTests.js
  7. 86 113
      services/web/test/unit/src/Cooldown/CooldownManagerTests.js
  8. 266 243
      services/web/test/unit/src/Docstore/DocstoreManagerTests.js
  9. 310 316
      services/web/test/unit/src/DocumentUpdater/DocumentUpdaterHandlerTests.js
  10. 311 310
      services/web/test/unit/src/FileStore/FileStoreHandlerTests.js
  11. 236 239
      services/web/test/unit/src/Institutions/InstitutionsAPITests.js
  12. 46 87
      services/web/test/unit/src/Institutions/InstitutionsFeaturesTests.js
  13. 76 82
      services/web/test/unit/src/Notifications/NotificationsBuilderTests.js
  14. 68 98
      services/web/test/unit/src/Notifications/NotificationsHandlerTests.js
  15. 9 8
      services/web/test/unit/src/Project/ProjectEntityHandlerTests.js
  16. 295 316
      services/web/test/unit/src/Project/ProjectEntityUpdateHandlerTests.js
  17. 266 356
      services/web/test/unit/src/Project/ProjectLocatorTests.js
  18. 154 213
      services/web/test/unit/src/Project/ProjectRootDocManagerTests.js
  19. 4 9
      services/web/test/unit/src/Publishers/PublishersGetterTests.js
  20. 26 31
      services/web/test/unit/src/Security/LoginRateLimiterTests.js
  21. 34 24
      services/web/test/unit/src/Subscription/RecurlyWrapperTests.js
  22. 250 258
      services/web/test/unit/src/Subscription/TeamInvitesHandlerTests.js
  23. 88 127
      services/web/test/unit/src/Tags/TagsHandlerTests.js
  24. 171 290
      services/web/test/unit/src/Uploads/FileTypeManagerTests.js
  25. 16 10
      services/web/test/unit/src/User/ThirdPartyIdentityManagerTests.js

+ 1 - 0
services/web/app/src/Features/Project/ProjectEntityUpdateHandler.js

@@ -1283,6 +1283,7 @@ const ProjectEntityUpdateHandler = {
     upsertFile,
     upsertFileWithPath,
     appendToDocWithPath: appendToDoc,
+    setMainBibliographyDoc,
   },
 
   async _addDocAndSendToTpds(projectId, folderId, doc, userId) {

+ 1 - 1
services/web/test/unit/src/Authorization/AuthorizationMiddlewareTests.js

@@ -206,7 +206,7 @@ describe('AuthorizationMiddleware', function () {
       expectForbidden()
     })
 
-    describe('for a regular user', function (done) {
+    describe('for a regular user', function () {
       setupPermission('isRestrictedUserForProject', false)
       invokeMiddleware('blockRestrictedUserFromProject')
       expectNext()

+ 26 - 47
services/web/test/unit/src/BrandVariations/BrandVariationsHandlerTests.js

@@ -59,47 +59,34 @@ describe('BrandVariationsHandler', function () {
   })
 
   describe('getBrandVariationById', function () {
-    it('should call the callback with an error when the branding variation id is not provided', function (done) {
-      return this.BrandVariationsHandler.getBrandVariationById(
-        null,
-        (err, brandVariationDetails) => {
-          expect(err).to.be.instanceof(Error)
-          return done()
-        }
-      )
+    it('should reject with an error when the branding variation id is not provided', async function () {
+      await expect(
+        this.BrandVariationsHandler.promises.getBrandVariationById(null)
+      ).to.be.rejected
     })
 
-    it('should call the callback with an error when the request errors', function (done) {
+    it('should reject with an error when the request errors', async function () {
       this.V1Api.request.callsArgWith(1, new Error())
-      return this.BrandVariationsHandler.getBrandVariationById(
-        '12',
-        (err, brandVariationDetails) => {
-          expect(err).to.be.instanceof(Error)
-          return done()
-        }
-      )
+      await expect(
+        this.BrandVariationsHandler.promises.getBrandVariationById('12')
+      ).to.be.rejected
     })
 
-    it('should call the callback with branding details when request succeeds', function (done) {
+    it('should return branding details when request succeeds', async function () {
       this.V1Api.request.callsArgWith(
         1,
         null,
         { statusCode: 200 },
         this.mockedBrandVariationDetails
       )
-      return this.BrandVariationsHandler.getBrandVariationById(
-        '12',
-        (err, brandVariationDetails) => {
-          expect(err).to.not.exist
-          expect(brandVariationDetails).to.deep.equal(
-            this.mockedBrandVariationDetails
-          )
-          return done()
-        }
+      const brandVariationDetails =
+        await this.BrandVariationsHandler.promises.getBrandVariationById('12')
+      expect(brandVariationDetails).to.deep.equal(
+        this.mockedBrandVariationDetails
       )
     })
 
-    it('should transform relative URLs in v1 absolute ones', function (done) {
+    it('should transform relative URLs in v1 absolute ones', async function () {
       this.mockedBrandVariationDetails.logo_url = '/journal-logo.png'
       this.V1Api.request.callsArgWith(
         1,
@@ -107,20 +94,16 @@ describe('BrandVariationsHandler', function () {
         { statusCode: 200 },
         this.mockedBrandVariationDetails
       )
-      return this.BrandVariationsHandler.getBrandVariationById(
-        '12',
-        (err, brandVariationDetails) => {
-          expect(
-            brandVariationDetails.logo_url.startsWith(
-              this.settings.apis.v1.publicUrl
-            )
-          ).to.be.true
-          return done()
-        }
-      )
+      const brandVariationDetails =
+        await this.BrandVariationsHandler.promises.getBrandVariationById('12')
+      expect(
+        brandVariationDetails.logo_url.startsWith(
+          this.settings.apis.v1.publicUrl
+        )
+      ).to.be.true
     })
 
-    it("should sanitize 'submit_button_html'", function (done) {
+    it("should sanitize 'submit_button_html'", async function () {
       this.mockedBrandVariationDetails.submit_button_html =
         '<br class="break"/><strong style="color:#B39500">AGU Journal</strong><iframe>hello</iframe>'
       this.V1Api.request.callsArgWith(
@@ -129,14 +112,10 @@ describe('BrandVariationsHandler', function () {
         { statusCode: 200 },
         this.mockedBrandVariationDetails
       )
-      return this.BrandVariationsHandler.getBrandVariationById(
-        '12',
-        (err, brandVariationDetails) => {
-          expect(brandVariationDetails.submit_button_html).to.equal(
-            '<br /><strong style="color:#B39500">AGU Journal</strong>hello'
-          )
-          return done()
-        }
+      const brandVariationDetails =
+        await this.BrandVariationsHandler.promises.getBrandVariationById('12')
+      expect(brandVariationDetails.submit_button_html).to.equal(
+        '<br /><strong style="color:#B39500">AGU Journal</strong>hello'
       )
     })
   })

+ 48 - 56
services/web/test/unit/src/Chat/ChatManagerTests.js

@@ -47,8 +47,8 @@ describe('ChatManager', function () {
       }))
     })
 
-    it('should inject a user object into messaged and resolved data', function (done) {
-      return this.ChatManager.injectUserInfoIntoThreads(
+    it('should inject a user object into messaged and resolved data', async function () {
+      const threads = await this.ChatManager.promises.injectUserInfoIntoThreads(
         {
           thread1: {
             resolved: true,
@@ -72,64 +72,56 @@ describe('ChatManager', function () {
               },
             ],
           },
-        },
-        (error, threads) => {
-          expect(error).to.be.null
-          expect(threads).to.deep.equal({
-            thread1: {
-              resolved: true,
-              resolved_by_user_id: 'user_id_1',
-              resolved_by_user: { formatted: 'user_1' },
-              messages: [
-                {
-                  user_id: 'user_id_1',
-                  user: { formatted: 'user_1' },
-                  content: 'foo',
-                },
-                {
-                  user_id: 'user_id_2',
-                  user: { formatted: 'user_2' },
-                  content: 'bar',
-                },
-              ],
-            },
-            thread2: {
-              messages: [
-                {
-                  user_id: 'user_id_1',
-                  user: { formatted: 'user_1' },
-                  content: 'baz',
-                },
-              ],
-            },
-          })
-          return done()
         }
       )
+
+      expect(threads).to.deep.equal({
+        thread1: {
+          resolved: true,
+          resolved_by_user_id: 'user_id_1',
+          resolved_by_user: { formatted: 'user_1' },
+          messages: [
+            {
+              user_id: 'user_id_1',
+              user: { formatted: 'user_1' },
+              content: 'foo',
+            },
+            {
+              user_id: 'user_id_2',
+              user: { formatted: 'user_2' },
+              content: 'bar',
+            },
+          ],
+        },
+        thread2: {
+          messages: [
+            {
+              user_id: 'user_id_1',
+              user: { formatted: 'user_1' },
+              content: 'baz',
+            },
+          ],
+        },
+      })
     })
 
-    it('should only need to look up each user once', function (done) {
-      return this.ChatManager.injectUserInfoIntoThreads(
-        [
-          {
-            messages: [
-              {
-                user_id: 'user_id_1',
-                content: 'foo',
-              },
-              {
-                user_id: 'user_id_1',
-                content: 'bar',
-              },
-            ],
-          },
-        ],
-        (error, threads) => {
-          expect(error).to.be.null
-          this.UserInfoManager.getPersonalInfo.calledOnce.should.equal(true)
-          return done()
-        }
-      )
+    it('should only need to look up each user once', async function () {
+      await this.ChatManager.promises.injectUserInfoIntoThreads([
+        {
+          messages: [
+            {
+              user_id: 'user_id_1',
+              content: 'foo',
+            },
+            {
+              user_id: 'user_id_1',
+              content: 'bar',
+            },
+          ],
+        },
+      ])
+
+      this.UserInfoManager.getPersonalInfo.calledOnce.should.equal(true)
     })
   })
 })

+ 64 - 85
services/web/test/unit/src/Compile/ClsiFormatCheckerTests.js

@@ -47,62 +47,53 @@ describe('ClsiFormatChecker', function () {
       ])
     })
 
-    it('should call _checkDocsAreUnderSizeLimit and _checkForConflictingPaths', function (done) {
+    it('should call _checkDocsAreUnderSizeLimit and _checkForConflictingPaths', async function () {
       this.ClsiFormatChecker._checkForConflictingPaths = sinon
         .stub()
         .callsArgWith(1, null)
       this.ClsiFormatChecker._checkDocsAreUnderSizeLimit = sinon
         .stub()
         .callsArgWith(1)
-      return this.ClsiFormatChecker.checkRecoursesForProblems(
-        this.resources,
-        (err, problems) => {
-          this.ClsiFormatChecker._checkForConflictingPaths.called.should.equal(
-            true
-          )
-          this.ClsiFormatChecker._checkDocsAreUnderSizeLimit.called.should.equal(
-            true
-          )
-          return done()
-        }
+      const problems =
+        await this.ClsiFormatChecker.promises.checkRecoursesForProblems(
+          this.resources
+        )
+      this.ClsiFormatChecker._checkForConflictingPaths.called.should.equal(true)
+      this.ClsiFormatChecker._checkDocsAreUnderSizeLimit.called.should.equal(
+        true
       )
     })
 
-    it('should remove undefined errors', function (done) {
+    it('should remove undefined errors', async function () {
       this.ClsiFormatChecker._checkForConflictingPaths = sinon
         .stub()
         .callsArgWith(1, null, [])
       this.ClsiFormatChecker._checkDocsAreUnderSizeLimit = sinon
         .stub()
         .callsArgWith(1, null, {})
-      return this.ClsiFormatChecker.checkRecoursesForProblems(
-        this.resources,
-        (err, problems) => {
-          expect(problems).to.not.exist
-          expect(problems).to.not.exist
-          return done()
-        }
-      )
+      const problems =
+        await this.ClsiFormatChecker.promises.checkRecoursesForProblems(
+          this.resources
+        )
+      expect(problems).to.not.exist
     })
 
-    it('should keep populated arrays', function (done) {
+    it('should keep populated arrays', async function () {
       this.ClsiFormatChecker._checkForConflictingPaths = sinon
         .stub()
         .callsArgWith(1, null, [{ path: 'somewhere/main.tex' }])
       this.ClsiFormatChecker._checkDocsAreUnderSizeLimit = sinon
         .stub()
         .callsArgWith(1, null, {})
-      return this.ClsiFormatChecker.checkRecoursesForProblems(
-        this.resources,
-        (err, problems) => {
-          problems.conflictedPaths[0].path.should.equal('somewhere/main.tex')
-          expect(problems.sizeCheck).to.not.exist
-          return done()
-        }
-      )
+      const problems =
+        await this.ClsiFormatChecker.promises.checkRecoursesForProblems(
+          this.resources
+        )
+      problems.conflictedPaths[0].path.should.equal('somewhere/main.tex')
+      expect(problems.sizeCheck).to.not.exist
     })
 
-    it('should keep populated object', function (done) {
+    it('should keep populated object', async function () {
       this.ClsiFormatChecker._checkForConflictingPaths = sinon
         .stub()
         .callsArgWith(1, null, [])
@@ -112,15 +103,13 @@ describe('ClsiFormatChecker', function () {
           resources: [{ 'a.tex': 'a.tex' }, { 'b.tex': 'b.tex' }],
           totalSize: 1000000,
         })
-      return this.ClsiFormatChecker.checkRecoursesForProblems(
-        this.resources,
-        (err, problems) => {
-          problems.sizeCheck.resources.length.should.equal(2)
-          problems.sizeCheck.totalSize.should.equal(1000000)
-          expect(problems.conflictedPaths).to.not.exist
-          return done()
-        }
-      )
+      const problems =
+        await this.ClsiFormatChecker.promises.checkRecoursesForProblems(
+          this.resources
+        )
+      problems.sizeCheck.resources.length.should.equal(2)
+      problems.sizeCheck.totalSize.should.equal(1000000)
+      expect(problems.conflictedPaths).to.not.exist
     })
 
     describe('_checkForConflictingPaths', function () {
@@ -136,56 +125,50 @@ describe('ClsiFormatChecker', function () {
         })
       })
 
-      it('should flag up when a nested file has folder with same subpath as file elsewhere', function (done) {
+      it('should flag up when a nested file has folder with same subpath as file elsewhere', async function () {
         this.resources.push({
           path: 'stuff/image',
           url: 'http://somwhere.com',
         })
 
-        return this.ClsiFormatChecker._checkForConflictingPaths(
-          this.resources,
-          (err, conflictPathErrors) => {
-            conflictPathErrors.length.should.equal(1)
-            conflictPathErrors[0].path.should.equal('stuff/image')
-            return done()
-          }
-        )
+        const conflictPathErrors =
+          await this.ClsiFormatChecker.promises._checkForConflictingPaths(
+            this.resources
+          )
+        conflictPathErrors.length.should.equal(1)
+        conflictPathErrors[0].path.should.equal('stuff/image')
       })
 
-      it('should flag up when a root level file has folder with same subpath as file elsewhere', function (done) {
+      it('should flag up when a root level file has folder with same subpath as file elsewhere', async function () {
         this.resources.push({
           path: 'stuff',
           content: 'other stuff',
         })
 
-        return this.ClsiFormatChecker._checkForConflictingPaths(
-          this.resources,
-          (err, conflictPathErrors) => {
-            conflictPathErrors.length.should.equal(1)
-            conflictPathErrors[0].path.should.equal('stuff')
-            return done()
-          }
-        )
+        const conflictPathErrors =
+          await this.ClsiFormatChecker.promises._checkForConflictingPaths(
+            this.resources
+          )
+        conflictPathErrors.length.should.equal(1)
+        conflictPathErrors[0].path.should.equal('stuff')
       })
 
-      it('should not flag up when the file is a substring of a path', function (done) {
+      it('should not flag up when the file is a substring of a path', async function () {
         this.resources.push({
           path: 'stuf',
           content: 'other stuff',
         })
 
-        return this.ClsiFormatChecker._checkForConflictingPaths(
-          this.resources,
-          (err, conflictPathErrors) => {
-            conflictPathErrors.length.should.equal(0)
-            return done()
-          }
-        )
+        const conflictPathErrors =
+          await this.ClsiFormatChecker.promises._checkForConflictingPaths(
+            this.resources
+          )
+        conflictPathErrors.length.should.equal(0)
       })
     })
 
     describe('_checkDocsAreUnderSizeLimit', function () {
-      it('should error when there is more than 5mb of data', function (done) {
+      it('should error when there is more than 5mb of data', async function () {
         this.resources.push({
           path: 'massive.tex',
           content: 'hello world'.repeat(833333), // over 5mb limit
@@ -198,19 +181,17 @@ describe('ClsiFormatChecker', function () {
           })
         }
 
-        return this.ClsiFormatChecker._checkDocsAreUnderSizeLimit(
-          this.resources,
-          (err, sizeError) => {
-            sizeError.totalSize.should.equal(16 + 833333 * 11) // 16 is for earlier resources
-            sizeError.resources.length.should.equal(10)
-            sizeError.resources[0].path.should.equal('massive.tex')
-            sizeError.resources[0].size.should.equal(833333 * 11)
-            return done()
-          }
-        )
+        const sizeError =
+          await this.ClsiFormatChecker.promises._checkDocsAreUnderSizeLimit(
+            this.resources
+          )
+        sizeError.totalSize.should.equal(16 + 833333 * 11) // 16 is for earlier resources
+        sizeError.resources.length.should.equal(10)
+        sizeError.resources[0].path.should.equal('massive.tex')
+        sizeError.resources[0].size.should.equal(833333 * 11)
       })
 
-      it('should return nothing when project is correct size', function (done) {
+      it('should return nothing when project is correct size', async function () {
         this.resources.push({
           path: 'massive.tex',
           content: 'x'.repeat(2 * 1000 * 1000),
@@ -223,13 +204,11 @@ describe('ClsiFormatChecker', function () {
           })
         }
 
-        return this.ClsiFormatChecker._checkDocsAreUnderSizeLimit(
-          this.resources,
-          (err, sizeError) => {
-            expect(sizeError).to.not.exist
-            return done()
-          }
-        )
+        const sizeError =
+          await this.ClsiFormatChecker.promises._checkDocsAreUnderSizeLimit(
+            this.resources
+          )
+        expect(sizeError).to.not.exist
       })
     })
   })

+ 21 - 29
services/web/test/unit/src/Compile/CompileManagerTests.js

@@ -139,7 +139,7 @@ describe('CompileManager', function () {
     })
 
     describe('when the project has been recently compiled', function () {
-      it('should return', function (done) {
+      it('should return', async function () {
         this.CompileManager._checkIfAutoCompileLimitHasBeenHit = async (
           isAutoCompile,
           compileGroup
@@ -147,32 +147,27 @@ describe('CompileManager', function () {
         this.CompileManager._checkIfRecentlyCompiled = sinon
           .stub()
           .resolves(true)
-        this.CompileManager.promises
-          .compile(this.project_id, this.user_id, {})
-          .then(({ status }) => {
-            status.should.equal('too-recently-compiled')
-            done()
-          })
-          .catch(error => {
-            // Catch any errors and fail the test
-            true.should.equal(false)
-            done(error)
-          })
+        const { status } = await this.CompileManager.promises.compile(
+          this.project_id,
+          this.user_id,
+          {}
+        )
+        status.should.equal('too-recently-compiled')
       })
     })
 
     describe('should check the rate limit', function () {
-      it('should return', function (done) {
+      it('should return', async function () {
         this.CompileManager._checkIfAutoCompileLimitHasBeenHit = sinon
           .stub()
           .resolves(false)
-        this.CompileManager.promises
-          .compile(this.project_id, this.user_id, {})
-          .then(({ status }) => {
-            expect(status).to.equal('autocompile-backoff')
-            done()
-          })
-          .catch(err => done(err))
+        const { status } = await this.CompileManager.promises.compile(
+          this.project_id,
+          this.user_id,
+          {}
+        )
+
+        expect(status).to.equal('autocompile-backoff')
       })
     })
   })
@@ -250,15 +245,12 @@ describe('CompileManager', function () {
       beforeEach(function () {
         this.features.compileGroup = 'priority'
       })
-      it('should return the default class', function (done) {
-        this.CompileManager.getProjectCompileLimits(
-          this.project_id,
-          (err, { compileBackendClass }) => {
-            if (err) return done(err)
-            expect(compileBackendClass).to.equal('c2d')
-            done()
-          }
-        )
+      it('should return the default class', async function () {
+        const { compileBackendClass } =
+          await this.CompileManager.promises.getProjectCompileLimits(
+            this.project_id
+          )
+        expect(compileBackendClass).to.equal('c2d')
       })
     })
   })

+ 86 - 113
services/web/test/unit/src/Cooldown/CooldownManagerTests.js

@@ -1,15 +1,3 @@
-/* eslint-disable
-    n/handle-callback-err,
-    max-len,
-    no-return-assign,
-*/
-// TODO: This file was created by bulk-decaffeinate.
-// Fix any style issues and re-enable lint.
-/*
- * decaffeinate suggestions:
- * DS102: Remove unnecessary code created because of implicit returns
- * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
- */
 const SandboxedModule = require('sandboxed-module')
 const sinon = require('sinon')
 const { expect } = require('chai')
@@ -23,158 +11,143 @@ describe('CooldownManager', function () {
     this.projectId = 'abcdefg'
     this.rclient = { set: sinon.stub(), get: sinon.stub() }
     this.RedisWrapper = { client: () => this.rclient }
-    return (this.CooldownManager = SandboxedModule.require(modulePath, {
+    this.CooldownManager = SandboxedModule.require(modulePath, {
       requires: {
         '../../infrastructure/RedisWrapper': this.RedisWrapper,
       },
-    }))
+    })
   })
 
   describe('_buildKey', function () {
     it('should build a properly formatted redis key', function () {
-      return expect(this.CooldownManager._buildKey('ABC')).to.equal(
-        'Cooldown:{ABC}'
-      )
+      expect(this.CooldownManager._buildKey('ABC')).to.equal('Cooldown:{ABC}')
     })
   })
 
   describe('isProjectOnCooldown', function () {
-    beforeEach(function () {
-      return (this.call = cb => {
-        return this.CooldownManager.isProjectOnCooldown(this.projectId, cb)
-      })
-    })
-
     describe('when project is on cooldown', function () {
       beforeEach(function () {
-        return (this.rclient.get = sinon.stub().callsArgWith(1, null, '1'))
+        this.rclient.get = sinon.stub().callsArgWith(1, null, '1')
       })
 
-      it('should fetch key from redis', function (done) {
-        return this.call((err, result) => {
-          this.rclient.get.callCount.should.equal(1)
-          this.rclient.get.calledWith('Cooldown:{abcdefg}').should.equal(true)
-          return done()
-        })
+      it('should fetch key from redis', async function () {
+        await this.CooldownManager.promises.isProjectOnCooldown(this.projectId)
+        this.rclient.get.callCount.should.equal(1)
+        this.rclient.get.calledWith('Cooldown:{abcdefg}').should.equal(true)
       })
 
-      it('should not produce an error', function (done) {
-        return this.call((err, result) => {
-          expect(err).to.equal(null)
-          return done()
-        })
-      })
-
-      it('should produce a true result', function (done) {
-        return this.call((err, result) => {
-          expect(result).to.equal(true)
-          return done()
-        })
+      it('should produce a true result', async function () {
+        const result = await this.CooldownManager.promises.isProjectOnCooldown(
+          this.projectId
+        )
+        expect(result).to.equal(true)
       })
     })
 
     describe('when project is not on cooldown', function () {
       beforeEach(function () {
-        return (this.rclient.get = sinon.stub().callsArgWith(1, null, null))
-      })
-
-      it('should fetch key from redis', function (done) {
-        return this.call((err, result) => {
-          this.rclient.get.callCount.should.equal(1)
-          this.rclient.get.calledWith('Cooldown:{abcdefg}').should.equal(true)
-          return done()
-        })
+        this.rclient.get = sinon.stub().callsArgWith(1, null, null)
       })
 
-      it('should not produce an error', function (done) {
-        return this.call((err, result) => {
-          expect(err).to.equal(null)
-          return done()
-        })
+      it('should fetch key from redis', async function () {
+        await this.CooldownManager.promises.isProjectOnCooldown(this.projectId)
+        this.rclient.get.callCount.should.equal(1)
+        this.rclient.get.calledWith('Cooldown:{abcdefg}').should.equal(true)
       })
 
-      it('should produce a false result', function (done) {
-        return this.call((err, result) => {
-          expect(result).to.equal(false)
-          return done()
-        })
+      it('should produce a false result', async function () {
+        const result = await this.CooldownManager.promises.isProjectOnCooldown(
+          this.projectId
+        )
+        expect(result).to.equal(false)
       })
     })
 
     describe('when rclient.get produces an error', function () {
       beforeEach(function () {
-        return (this.rclient.get = sinon
-          .stub()
-          .callsArgWith(1, new Error('woops')))
+        this.rclient.get = sinon.stub().callsArgWith(1, new Error('woops'))
       })
 
-      it('should fetch key from redis', function (done) {
-        return this.call((err, result) => {
-          this.rclient.get.callCount.should.equal(1)
-          this.rclient.get.calledWith('Cooldown:{abcdefg}').should.equal(true)
-          return done()
-        })
+      it('should fetch key from redis', async function () {
+        try {
+          await this.CooldownManager.promises.isProjectOnCooldown(
+            this.projectId
+          )
+        } catch {
+          // ignore errors - expected
+        }
+        this.rclient.get.callCount.should.equal(1)
+        this.rclient.get.calledWith('Cooldown:{abcdefg}').should.equal(true)
       })
 
-      it('should produce an error', function (done) {
-        return this.call((err, result) => {
-          expect(err).to.not.equal(null)
-          expect(err).to.be.instanceof(Error)
-          return done()
-        })
+      it('should produce an error', async function () {
+        let error
+
+        try {
+          await this.CooldownManager.promises.isProjectOnCooldown(
+            this.projectId
+          )
+        } catch (err) {
+          error = err
+        }
+
+        expect(error).to.be.instanceOf(Error)
       })
     })
   })
 
   describe('putProjectOnCooldown', function () {
-    beforeEach(function () {
-      return (this.call = cb => {
-        return this.CooldownManager.putProjectOnCooldown(this.projectId, cb)
-      })
-    })
-
     describe('when rclient.set does not produce an error', function () {
       beforeEach(function () {
-        return (this.rclient.set = sinon.stub().callsArgWith(4, null))
+        this.rclient.set = sinon.stub().callsArgWith(4, null)
       })
 
-      it('should set a key in redis', function (done) {
-        return this.call(err => {
-          this.rclient.set.callCount.should.equal(1)
-          this.rclient.set.calledWith('Cooldown:{abcdefg}').should.equal(true)
-          return done()
-        })
+      it('should set a key in redis', async function () {
+        await this.CooldownManager.promises.putProjectOnCooldown(this.projectId)
+        this.rclient.set.callCount.should.equal(1)
+        this.rclient.set.calledWith('Cooldown:{abcdefg}').should.equal(true)
       })
 
-      it('should not produce an error', function (done) {
-        return this.call(err => {
-          expect(err).to.equal(null)
-          return done()
-        })
+      it('should not produce an error', async function () {
+        let error
+        try {
+          await this.CooldownManager.promises.putProjectOnCooldown(
+            this.projectId
+          )
+        } catch (err) {
+          error = err
+        }
+        expect(error).not.to.exist
       })
     })
 
     describe('when rclient.set produces an error', function () {
       beforeEach(function () {
-        return (this.rclient.set = sinon
-          .stub()
-          .callsArgWith(4, new Error('woops')))
-      })
-
-      it('should set a key in redis', function (done) {
-        return this.call(err => {
-          this.rclient.set.callCount.should.equal(1)
-          this.rclient.set.calledWith('Cooldown:{abcdefg}').should.equal(true)
-          return done()
-        })
-      })
-
-      it('produce an error', function (done) {
-        return this.call(err => {
-          expect(err).to.not.equal(null)
-          expect(err).to.be.instanceof(Error)
-          return done()
-        })
+        this.rclient.set = sinon.stub().callsArgWith(4, new Error('woops'))
+      })
+
+      it('should set a key in redis', async function () {
+        try {
+          await this.CooldownManager.promises.putProjectOnCooldown(
+            this.projectId
+          )
+        } catch {
+          // ignore errors - expected
+        }
+        this.rclient.set.callCount.should.equal(1)
+        this.rclient.set.calledWith('Cooldown:{abcdefg}').should.equal(true)
+      })
+
+      it('produce an error', async function () {
+        let error
+        try {
+          await this.CooldownManager.promises.putProjectOnCooldown(
+            this.projectId
+          )
+        } catch (err) {
+          error = err
+        }
+        expect(error).to.be.instanceOf(Error)
       })
     })
   })

+ 266 - 243
services/web/test/unit/src/Docstore/DocstoreManagerTests.js

@@ -1,6 +1,7 @@
 const sinon = require('sinon')
 const modulePath = '../../../../app/src/Features/Docstore/DocstoreManager'
 const SandboxedModule = require('sandboxed-module')
+const { expect } = require('chai')
 const Errors = require('../../../../app/src/Features/Errors/Errors')
 const tk = require('timekeeper')
 
@@ -26,7 +27,6 @@ describe('DocstoreManager', function () {
 
     this.project_id = 'project-id-123'
     this.doc_id = 'doc-id-123'
-    this.callback = sinon.stub()
   })
 
   describe('deleteDoc', function () {
@@ -39,16 +39,15 @@ describe('DocstoreManager', function () {
         tk.reset()
       })
 
-      beforeEach(function () {
+      beforeEach(async function () {
         this.request.patch = sinon
           .stub()
           .callsArgWith(1, null, { statusCode: 204 }, '')
-        this.DocstoreManager.deleteDoc(
+        await this.DocstoreManager.promises.deleteDoc(
           this.project_id,
           this.doc_id,
           'wombat.tex',
-          new Date(),
-          this.callback
+          new Date()
         )
       })
 
@@ -61,10 +60,6 @@ describe('DocstoreManager', function () {
           })
           .should.equal(true)
       })
-
-      it('should call the callback without an error', function () {
-        this.callback.calledWith(null).should.equal(true)
-      })
     })
 
     describe('with a failed response code', function () {
@@ -72,28 +67,27 @@ describe('DocstoreManager', function () {
         this.request.patch = sinon
           .stub()
           .callsArgWith(1, null, { statusCode: 500 }, '')
-        this.DocstoreManager.deleteDoc(
-          this.project_id,
-          this.doc_id,
-          'main.tex',
-          new Date(),
-          this.callback
-        )
       })
 
-      it('should call the callback with an error', function () {
-        this.callback
-          .calledWith(
-            sinon.match
-              .instanceOf(Error)
-              .and(
-                sinon.match.has(
-                  'message',
-                  'docstore api responded with non-success code: 500'
-                )
-              )
+      it('should reject with an error', async function () {
+        let error
+
+        try {
+          await this.DocstoreManager.promises.deleteDoc(
+            this.project_id,
+            this.doc_id,
+            'main.tex',
+            new Date()
           )
-          .should.equal(true)
+        } catch (err) {
+          error = err
+        }
+
+        expect(error).to.be.instanceOf(Error)
+        expect(error).to.have.property(
+          'message',
+          'docstore api responded with non-success code: 500'
+        )
       })
     })
 
@@ -102,28 +96,26 @@ describe('DocstoreManager', function () {
         this.request.patch = sinon
           .stub()
           .callsArgWith(1, null, { statusCode: 404 }, '')
-        this.DocstoreManager.deleteDoc(
-          this.project_id,
-          this.doc_id,
-          'main.tex',
-          new Date(),
-          this.callback
-        )
       })
 
-      it('should call the callback with an error', function () {
-        this.callback
-          .calledWith(
-            sinon.match
-              .instanceOf(Errors.NotFoundError)
-              .and(
-                sinon.match.has(
-                  'message',
-                  'tried to delete doc not in docstore'
-                )
-              )
+      it('should reject with an error', async function () {
+        let error
+        try {
+          await this.DocstoreManager.promises.deleteDoc(
+            this.project_id,
+            this.doc_id,
+            'main.tex',
+            new Date()
           )
-          .should.equal(true)
+        } catch (err) {
+          error = err
+        }
+
+        expect(error).to.be.instanceOf(Error)
+        expect(error).to.have.property(
+          'message',
+          'tried to delete doc not in docstore'
+        )
       })
     })
   })
@@ -137,8 +129,8 @@ describe('DocstoreManager', function () {
       this.modified = true
     })
 
-    describe('with a successful response code', function () {
-      beforeEach(function () {
+    describe('with a successful response code', async function () {
+      beforeEach(async function () {
         this.request.post = sinon
           .stub()
           .callsArgWith(
@@ -147,13 +139,12 @@ describe('DocstoreManager', function () {
             { statusCode: 204 },
             { modified: this.modified, rev: this.rev }
           )
-        this.DocstoreManager.updateDoc(
+        this.updateDocResponse = await this.DocstoreManager.promises.updateDoc(
           this.project_id,
           this.doc_id,
           this.lines,
           this.version,
-          this.ranges,
-          this.callback
+          this.ranges
         )
       })
 
@@ -171,10 +162,12 @@ describe('DocstoreManager', function () {
           .should.equal(true)
       })
 
-      it('should call the callback with the modified status and revision', function () {
-        this.callback
-          .calledWith(null, this.modified, this.rev)
-          .should.equal(true)
+      it('should return the modified status and revision', function () {
+        expect(this.updateDocResponse).to.haveOwnProperty(
+          'modified',
+          this.modified
+        )
+        expect(this.updateDocResponse).to.haveOwnProperty('rev', this.rev)
       })
     })
 
@@ -183,29 +176,28 @@ describe('DocstoreManager', function () {
         this.request.post = sinon
           .stub()
           .callsArgWith(1, null, { statusCode: 500 }, '')
-        this.DocstoreManager.updateDoc(
-          this.project_id,
-          this.doc_id,
-          this.lines,
-          this.version,
-          this.ranges,
-          this.callback
-        )
       })
 
-      it('should call the callback with an error', function () {
-        this.callback
-          .calledWith(
-            sinon.match
-              .instanceOf(Error)
-              .and(
-                sinon.match.has(
-                  'message',
-                  'docstore api responded with non-success code: 500'
-                )
-              )
+      it('should reject with an error', async function () {
+        let error
+
+        try {
+          await this.DocstoreManager.promises.updateDoc(
+            this.project_id,
+            this.doc_id,
+            this.lines,
+            this.version,
+            this.ranges
           )
-          .should.equal(true)
+        } catch (err) {
+          error = err
+        }
+
+        expect(error).to.be.instanceOf(Error)
+        expect(error).to.have.property(
+          'message',
+          'docstore api responded with non-success code: 500'
+        )
       })
     })
   })
@@ -221,11 +213,14 @@ describe('DocstoreManager', function () {
     })
 
     describe('with a successful response code', function () {
-      beforeEach(function () {
+      beforeEach(async function () {
         this.request.get = sinon
           .stub()
           .callsArgWith(1, null, { statusCode: 204 }, this.doc)
-        this.DocstoreManager.getDoc(this.project_id, this.doc_id, this.callback)
+        this.getDocResponse = await this.DocstoreManager.promises.getDoc(
+          this.project_id,
+          this.doc_id
+        )
       })
 
       it('should get the doc from the docstore api', function () {
@@ -236,10 +231,13 @@ describe('DocstoreManager', function () {
         })
       })
 
-      it('should call the callback with the lines, version and rev', function () {
-        this.callback
-          .calledWith(null, this.lines, this.rev, this.version, this.ranges)
-          .should.equal(true)
+      it('should resolve with the lines, version and rev', function () {
+        expect(this.getDocResponse).to.eql({
+          lines: this.lines,
+          rev: this.rev,
+          version: this.version,
+          ranges: this.ranges,
+        })
       })
     })
 
@@ -248,35 +246,37 @@ describe('DocstoreManager', function () {
         this.request.get = sinon
           .stub()
           .callsArgWith(1, null, { statusCode: 500 }, '')
-        this.DocstoreManager.getDoc(this.project_id, this.doc_id, this.callback)
-      })
-
-      it('should call the callback with an error', function () {
-        this.callback
-          .calledWith(
-            sinon.match
-              .instanceOf(Error)
-              .and(
-                sinon.match.has(
-                  'message',
-                  'docstore api responded with non-success code: 500'
-                )
-              )
+      })
+
+      it('should reject with an error', async function () {
+        let error
+
+        try {
+          await this.DocstoreManager.promises.getDoc(
+            this.project_id,
+            this.doc_id
           )
-          .should.equal(true)
+        } catch (err) {
+          error = err
+        }
+
+        expect(error).to.be.instanceOf(Error)
+        expect(error).to.have.property(
+          'message',
+          'docstore api responded with non-success code: 500'
+        )
       })
     })
 
     describe('with include_deleted=true', function () {
-      beforeEach(function () {
+      beforeEach(async function () {
         this.request.get = sinon
           .stub()
           .callsArgWith(1, null, { statusCode: 204 }, this.doc)
-        this.DocstoreManager.getDoc(
+        this.getDocResponse = await this.DocstoreManager.promises.getDoc(
           this.project_id,
           this.doc_id,
-          { include_deleted: true },
-          this.callback
+          { include_deleted: true }
         )
       })
 
@@ -289,23 +289,27 @@ describe('DocstoreManager', function () {
         })
       })
 
-      it('should call the callback with the lines, version and rev', function () {
-        this.callback
-          .calledWith(null, this.lines, this.rev, this.version, this.ranges)
-          .should.equal(true)
+      it('should resolve with the lines, version and rev', function () {
+        expect(this.getDocResponse).to.eql({
+          lines: this.lines,
+          rev: this.rev,
+          version: this.version,
+          ranges: this.ranges,
+        })
       })
     })
 
     describe('with peek=true', function () {
-      beforeEach(function () {
+      beforeEach(async function () {
         this.request.get = sinon
           .stub()
           .callsArgWith(1, null, { statusCode: 204 }, this.doc)
-        this.DocstoreManager.getDoc(
+        await this.DocstoreManager.promises.getDoc(
           this.project_id,
           this.doc_id,
-          { peek: true },
-          this.callback
+          {
+            peek: true,
+          }
         )
       })
 
@@ -323,24 +327,30 @@ describe('DocstoreManager', function () {
         this.request.get = sinon
           .stub()
           .callsArgWith(1, null, { statusCode: 404 }, '')
-        this.DocstoreManager.getDoc(this.project_id, this.doc_id, this.callback)
       })
 
-      it('should call the callback with an error', function () {
-        this.callback
-          .calledWith(
-            sinon.match
-              .instanceOf(Errors.NotFoundError)
-              .and(sinon.match.has('message', 'doc not found in docstore'))
+      it('should reject with an error', async function () {
+        let error
+
+        try {
+          await this.DocstoreManager.promises.getDoc(
+            this.project_id,
+            this.doc_id
           )
-          .should.equal(true)
+        } catch (err) {
+          error = err
+        }
+
+        expect(error).to.be.instanceOf(Errors.NotFoundError)
+        expect(error).to.have.property('message', 'doc not found in docstore')
       })
     })
   })
 
   describe('getAllDocs', function () {
     describe('with a successful response code', function () {
-      beforeEach(function () {
+      let getAllDocsResult
+      beforeEach(async function () {
         this.request.get = sinon
           .stub()
           .callsArgWith(
@@ -349,7 +359,9 @@ describe('DocstoreManager', function () {
             { statusCode: 204 },
             (this.docs = [{ _id: 'mock-doc-id' }])
           )
-        this.DocstoreManager.getAllDocs(this.project_id, this.callback)
+        getAllDocsResult = await this.DocstoreManager.promises.getAllDocs(
+          this.project_id
+        )
       })
 
       it('should get all the project docs in the docstore api', function () {
@@ -362,8 +374,8 @@ describe('DocstoreManager', function () {
           .should.equal(true)
       })
 
-      it('should call the callback with the docs', function () {
-        this.callback.calledWith(null, this.docs).should.equal(true)
+      it('should return the docs', function () {
+        expect(getAllDocsResult).to.eql(this.docs)
       })
     })
 
@@ -372,35 +384,36 @@ describe('DocstoreManager', function () {
         this.request.get = sinon
           .stub()
           .callsArgWith(1, null, { statusCode: 500 }, '')
-        this.DocstoreManager.getAllDocs(this.project_id, this.callback)
-      })
-
-      it('should call the callback with an error', function () {
-        this.callback
-          .calledWith(
-            sinon.match
-              .instanceOf(Error)
-              .and(
-                sinon.match.has(
-                  'message',
-                  'docstore api responded with non-success code: 500'
-                )
-              )
-          )
-          .should.equal(true)
+      })
+
+      it('should reject with an error', async function () {
+        let error
+
+        try {
+          await this.DocstoreManager.promises.getAllDocs(this.project_id)
+        } catch (err) {
+          error = err
+        }
+
+        expect(error).to.be.instanceOf(Error)
+        expect(error).to.have.property(
+          'message',
+          'docstore api responded with non-success code: 500'
+        )
       })
     })
   })
 
   describe('getAllDeletedDocs', function () {
     describe('with a successful response code', function () {
-      beforeEach(function (done) {
-        this.callback.callsFake(done)
+      let getAllDeletedDocsResponse
+      beforeEach(async function () {
         this.docs = [{ _id: 'mock-doc-id', name: 'foo.tex' }]
         this.request.get = sinon
           .stub()
           .callsArgWith(1, null, { statusCode: 200 }, this.docs)
-        this.DocstoreManager.getAllDeletedDocs(this.project_id, this.callback)
+        getAllDeletedDocsResponse =
+          await this.DocstoreManager.promises.getAllDeletedDocs(this.project_id)
       })
 
       it('should get all the project docs in the docstore api', function () {
@@ -411,48 +424,52 @@ describe('DocstoreManager', function () {
         })
       })
 
-      it('should call the callback with the docs', function () {
-        this.callback.should.have.been.calledWith(null, this.docs)
+      it('should resolve with the docs', function () {
+        expect(getAllDeletedDocsResponse).to.eql(this.docs)
       })
     })
 
     describe('with an error', function () {
-      beforeEach(function (done) {
-        this.callback.callsFake(() => done())
+      beforeEach(async function () {
         this.request.get = sinon
           .stub()
           .callsArgWith(1, new Error('connect failed'))
-        this.DocstoreManager.getAllDocs(this.project_id, this.callback)
       })
 
-      it('should call the callback with an error', function () {
-        this.callback.should.have.been.calledWith(
-          sinon.match
-            .instanceOf(Error)
-            .and(sinon.match.has('message', 'connect failed'))
-        )
+      it('should reject with an error', async function () {
+        let error
+
+        try {
+          await this.DocstoreManager.promises.getAllDocs(this.project_id)
+        } catch (err) {
+          error = err
+        }
+
+        expect(error).to.be.instanceOf(Error)
+        expect(error).to.have.property('message', 'connect failed')
       })
     })
 
     describe('with a failed response code', function () {
-      beforeEach(function (done) {
-        this.callback.callsFake(() => done())
+      beforeEach(function () {
         this.request.get = sinon
           .stub()
           .callsArgWith(1, null, { statusCode: 500 })
-        this.DocstoreManager.getAllDocs(this.project_id, this.callback)
-      })
-
-      it('should call the callback with an error', function () {
-        this.callback.should.have.been.calledWith(
-          sinon.match
-            .instanceOf(Error)
-            .and(
-              sinon.match.has(
-                'message',
-                'docstore api responded with non-success code: 500'
-              )
-            )
+      })
+
+      it('should reject with an error', async function () {
+        let error
+
+        try {
+          await this.DocstoreManager.promises.getAllDocs(this.project_id)
+        } catch (err) {
+          error = err
+        }
+
+        expect(error).to.be.instanceOf(Error)
+        expect(error).to.have.property(
+          'message',
+          'docstore api responded with non-success code: 500'
         )
       })
     })
@@ -460,7 +477,8 @@ describe('DocstoreManager', function () {
 
   describe('getAllRanges', function () {
     describe('with a successful response code', function () {
-      beforeEach(function () {
+      let getAllRangesResult
+      beforeEach(async function () {
         this.request.get = sinon
           .stub()
           .callsArgWith(
@@ -469,7 +487,9 @@ describe('DocstoreManager', function () {
             { statusCode: 204 },
             (this.docs = [{ _id: 'mock-doc-id', ranges: 'mock-ranges' }])
           )
-        this.DocstoreManager.getAllRanges(this.project_id, this.callback)
+        getAllRangesResult = await this.DocstoreManager.promises.getAllRanges(
+          this.project_id
+        )
       })
 
       it('should get all the project doc ranges in the docstore api', function () {
@@ -482,8 +502,8 @@ describe('DocstoreManager', function () {
           .should.equal(true)
       })
 
-      it('should call the callback with the docs', function () {
-        this.callback.calledWith(null, this.docs).should.equal(true)
+      it('should return the docs', async function () {
+        expect(getAllRangesResult).to.eql(this.docs)
       })
     })
 
@@ -492,22 +512,22 @@ describe('DocstoreManager', function () {
         this.request.get = sinon
           .stub()
           .callsArgWith(1, null, { statusCode: 500 }, '')
-        this.DocstoreManager.getAllRanges(this.project_id, this.callback)
-      })
-
-      it('should call the callback with an error', function () {
-        this.callback
-          .calledWith(
-            sinon.match
-              .instanceOf(Error)
-              .and(
-                sinon.match.has(
-                  'message',
-                  'docstore api responded with non-success code: 500'
-                )
-              )
-          )
-          .should.equal(true)
+      })
+
+      it('should reject with an error', async function () {
+        let error
+
+        try {
+          await this.DocstoreManager.promises.getAllRanges(this.project_id)
+        } catch (err) {
+          error = err
+        }
+
+        expect(error).to.be.instanceOf(Error)
+        expect(error).to.have.property(
+          'message',
+          'docstore api responded with non-success code: 500'
+        )
       })
     })
   })
@@ -518,11 +538,12 @@ describe('DocstoreManager', function () {
         this.request.post = sinon
           .stub()
           .callsArgWith(1, null, { statusCode: 204 })
-        this.DocstoreManager.archiveProject(this.project_id, this.callback)
       })
 
-      it('should call the callback', function () {
-        this.callback.called.should.equal(true)
+      it('should resolve', async function () {
+        await expect(
+          this.DocstoreManager.promises.archiveProject(this.project_id)
+        ).to.eventually.be.fulfilled
       })
     })
 
@@ -531,22 +552,22 @@ describe('DocstoreManager', function () {
         this.request.post = sinon
           .stub()
           .callsArgWith(1, null, { statusCode: 500 })
-        this.DocstoreManager.archiveProject(this.project_id, this.callback)
-      })
-
-      it('should call the callback with an error', function () {
-        this.callback
-          .calledWith(
-            sinon.match
-              .instanceOf(Error)
-              .and(
-                sinon.match.has(
-                  'message',
-                  'docstore api responded with non-success code: 500'
-                )
-              )
-          )
-          .should.equal(true)
+      })
+
+      it('should reject with an error', async function () {
+        let error
+
+        try {
+          await this.DocstoreManager.promises.archiveProject(this.project_id)
+        } catch (err) {
+          error = err
+        }
+
+        expect(error).to.be.instanceOf(Error)
+        expect(error).to.have.property(
+          'message',
+          'docstore api responded with non-success code: 500'
+        )
       })
     })
   })
@@ -557,11 +578,12 @@ describe('DocstoreManager', function () {
         this.request.post = sinon
           .stub()
           .callsArgWith(1, null, { statusCode: 204 })
-        this.DocstoreManager.unarchiveProject(this.project_id, this.callback)
       })
 
-      it('should call the callback', function () {
-        this.callback.called.should.equal(true)
+      it('should resolve', async function () {
+        await expect(
+          this.DocstoreManager.promises.unarchiveProject(this.project_id)
+        ).to.eventually.be.fulfilled
       })
     })
 
@@ -570,22 +592,22 @@ describe('DocstoreManager', function () {
         this.request.post = sinon
           .stub()
           .callsArgWith(1, null, { statusCode: 500 })
-        this.DocstoreManager.unarchiveProject(this.project_id, this.callback)
-      })
-
-      it('should call the callback with an error', function () {
-        this.callback
-          .calledWith(
-            sinon.match
-              .instanceOf(Error)
-              .and(
-                sinon.match.has(
-                  'message',
-                  'docstore api responded with non-success code: 500'
-                )
-              )
-          )
-          .should.equal(true)
+      })
+
+      it('should reject with an error', async function () {
+        let error
+
+        try {
+          await this.DocstoreManager.promises.unarchiveProject(this.project_id)
+        } catch (err) {
+          error = err
+        }
+
+        expect(error).to.be.instanceOf(Error)
+        expect(error).to.have.property(
+          'message',
+          'docstore api responded with non-success code: 500'
+        )
       })
     })
   })
@@ -596,11 +618,12 @@ describe('DocstoreManager', function () {
         this.request.post = sinon
           .stub()
           .callsArgWith(1, null, { statusCode: 204 })
-        this.DocstoreManager.destroyProject(this.project_id, this.callback)
       })
 
-      it('should call the callback', function () {
-        this.callback.called.should.equal(true)
+      it('should resolve', async function () {
+        await expect(
+          this.DocstoreManager.promises.destroyProject(this.project_id)
+        ).to.eventually.be.fulfilled
       })
     })
 
@@ -609,22 +632,22 @@ describe('DocstoreManager', function () {
         this.request.post = sinon
           .stub()
           .callsArgWith(1, null, { statusCode: 500 })
-        this.DocstoreManager.destroyProject(this.project_id, this.callback)
-      })
-
-      it('should call the callback with an error', function () {
-        this.callback
-          .calledWith(
-            sinon.match
-              .instanceOf(Error)
-              .and(
-                sinon.match.has(
-                  'message',
-                  'docstore api responded with non-success code: 500'
-                )
-              )
-          )
-          .should.equal(true)
+      })
+
+      it('should reject with an error', async function () {
+        let error
+
+        try {
+          await this.DocstoreManager.promises.destroyProject(this.project_id)
+        } catch (err) {
+          error = err
+        }
+
+        expect(error).to.be.instanceOf(Error)
+        expect(error).to.have.property(
+          'message',
+          'docstore api responded with non-success code: 500'
+        )
       })
     })
   })

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 310 - 316
services/web/test/unit/src/DocumentUpdater/DocumentUpdaterHandlerTests.js


+ 311 - 310
services/web/test/unit/src/FileStore/FileStoreHandlerTests.js

@@ -95,7 +95,7 @@ describe('FileStoreHandler', function () {
       this.request.returns(this.writeStream)
     })
 
-    it('should get the project details', function (done) {
+    it('should get the project details', async function () {
       this.fs.createReadStream.returns({
         pipe() {},
         on(type, cb) {
@@ -104,20 +104,17 @@ describe('FileStoreHandler', function () {
           }
         },
       })
-      this.handler.uploadFileFromDisk(
+      await this.handler.promises.uploadFileFromDisk(
         this.projectId,
         this.fileArgs,
-        this.fsPath,
-        () => {
-          this.ProjectDetailsHandler.getDetails
-            .calledWith(this.projectId)
-            .should.equal(true)
-          done()
-        }
+        this.fsPath
       )
+      this.ProjectDetailsHandler.getDetails
+        .calledWith(this.projectId)
+        .should.equal(true)
     })
 
-    it('should compute the file hash', function (done) {
+    it('should compute the file hash', async function () {
       this.fs.createReadStream.returns({
         pipe() {},
         on(type, cb) {
@@ -126,21 +123,18 @@ describe('FileStoreHandler', function () {
           }
         },
       })
-      this.handler.uploadFileFromDisk(
+      await this.handler.promises.uploadFileFromDisk(
         this.projectId,
         this.fileArgs,
-        this.fsPath,
-        () => {
-          this.FileHashManager.computeHash
-            .calledWith(this.fsPath)
-            .should.equal(true)
-          done()
-        }
+        this.fsPath
       )
+      this.FileHashManager.computeHash
+        .calledWith(this.fsPath)
+        .should.equal(true)
     })
 
     describe('when project-history-blobs feature is enabled', function () {
-      it('should upload the file to the history store as a blob', function (done) {
+      it('should upload the file to the history store as a blob', async function () {
         this.fs.createReadStream.returns({
           pipe() {},
           on(type, cb) {
@@ -150,26 +144,23 @@ describe('FileStoreHandler', function () {
           },
         })
         this.Features.hasFeature.withArgs('project-history-blobs').returns(true)
-        this.handler.uploadFileFromDisk(
+        await this.handler.promises.uploadFileFromDisk(
           this.projectId,
           this.fileArgs,
-          this.fsPath,
-          () => {
-            this.HistoryManager.uploadBlobFromDisk
-              .calledWith(
-                this.historyId,
-                this.hashValue,
-                this.fileSize,
-                this.fsPath
-              )
-              .should.equal(true)
-            done()
-          }
+          this.fsPath
         )
+        this.HistoryManager.uploadBlobFromDisk
+          .calledWith(
+            this.historyId,
+            this.hashValue,
+            this.fileSize,
+            this.fsPath
+          )
+          .should.equal(true)
       })
     })
     describe('when project-history-blobs feature is disabled', function () {
-      it('should not upload the file to the history store as a blob', function (done) {
+      it('should not upload the file to the history store as a blob', async function () {
         this.fs.createReadStream.returns({
           pipe() {},
           on(type, cb) {
@@ -178,15 +169,12 @@ describe('FileStoreHandler', function () {
             }
           },
         })
-        this.handler.uploadFileFromDisk(
+        await this.handler.promises.uploadFileFromDisk(
           this.projectId,
           this.fileArgs,
-          this.fsPath,
-          () => {
-            this.HistoryManager.uploadBlobFromDisk.called.should.equal(false)
-            done()
-          }
+          this.fsPath
         )
+        this.HistoryManager.uploadBlobFromDisk.called.should.equal(false)
       })
     })
 
@@ -214,71 +202,63 @@ describe('FileStoreHandler', function () {
         )
       })
 
-      it('should pipe the read stream to request', function (done) {
+      it('should pipe the read stream to request', function () {
         this.request.returns(this.writeStream)
-        this.fs.createReadStream.returns({
-          on(type, cb) {
-            if (type === 'open') {
-              cb()
-            }
-          },
-          pipe: o => {
-            this.writeStream.should.equal(o)
-            done()
-          },
+        return new Promise((resolve, reject) => {
+          this.fs.createReadStream.returns({
+            on(type, cb) {
+              if (type === 'open') {
+                cb()
+              }
+            },
+            pipe: o => {
+              this.writeStream.should.equal(o)
+              resolve()
+            },
+          })
+          this.handler.promises
+            .uploadFileFromDisk(this.projectId, this.fileArgs, this.fsPath)
+            .catch(reject)
         })
-        this.handler.uploadFileFromDisk(
-          this.projectId,
-          this.fileArgs,
-          this.fsPath,
-          () => {}
-        )
       })
 
-      it('should pass the correct options to request', function (done) {
+      it('should pass the correct options to request', async function () {
         const fileUrl = this.getFileUrl(this.projectId, this.fileId)
         this.fs.createReadStream.returns({
-          pipe() {},
-          on(type, cb) {
+          pipe: sinon.stub(),
+          on: sinon.stub((type, cb) => {
             if (type === 'open') {
               cb()
             }
-          },
+          }),
         })
-        this.handler.uploadFileFromDisk(
+        await this.handler.promises.uploadFileFromDisk(
           this.projectId,
           this.fileArgs,
-          this.fsPath,
-          () => {
-            this.request.args[0][0].method.should.equal('post')
-            this.request.args[0][0].uri.should.equal(fileUrl)
-            done()
-          }
+          this.fsPath
         )
+        this.request.args[0][0].method.should.equal('post')
+        this.request.args[0][0].uri.should.equal(fileUrl)
       })
 
-      it('should callback with the url and fileRef', function (done) {
+      it('should resolve with the url and fileRef', async function () {
         const fileUrl = this.getFileUrl(this.projectId, this.fileId)
         this.fs.createReadStream.returns({
-          pipe() {},
-          on(type, cb) {
+          pipe: sinon.stub(),
+          on: sinon.stub((type, cb) => {
             if (type === 'open') {
               cb()
             }
-          },
+          }),
         })
-        this.handler.uploadFileFromDisk(
+        const { url, fileRef } = await this.handler.promises.uploadFileFromDisk(
           this.projectId,
           this.fileArgs,
-          this.fsPath,
-          (err, url, fileRef) => {
-            expect(err).to.not.exist
-            expect(url).to.equal(fileUrl)
-            expect(fileRef._id).to.equal(this.fileId)
-            expect(fileRef.hash).to.equal(this.hashValue)
-            done()
-          }
+          this.fsPath
         )
+        expect(url).to.equal(fileUrl)
+        expect(fileRef._id).to.equal(this.fileId)
+        expect(fileRef.hash).to.equal(this.hashValue)
       })
       describe('when upload to filestore fails', function () {
         beforeEach(function () {
@@ -289,28 +269,32 @@ describe('FileStoreHandler', function () {
           }
         })
 
-        it('should callback with an error', function (done) {
+        it('should reject with an error', async function () {
           this.fs.createReadStream.callCount = 0
           this.fs.createReadStream.returns({
-            pipe() {},
-            on(type, cb) {
+            pipe: sinon.stub(),
+            on: sinon.stub((type, cb) => {
               if (type === 'open') {
                 cb()
               }
-            },
+            }),
           })
-          this.handler.uploadFileFromDisk(
-            this.projectId,
-            this.fileArgs,
-            this.fsPath,
-            err => {
-              expect(err).to.exist
-              expect(err).to.be.instanceof(Error)
-              expect(this.fs.createReadStream.callCount).to.equal(
-                this.handler.RETRY_ATTEMPTS
-              )
-              done()
-            }
+          let error
+
+          try {
+            await this.handler.promises.uploadFileFromDisk(
+              this.projectId,
+              this.fileArgs,
+              this.fsPath
+            )
+          } catch (err) {
+            error = err
+          }
+
+          expect(error).to.be.instanceOf(Error)
+
+          expect(this.fs.createReadStream.callCount).to.equal(
+            this.handler.RETRY_ATTEMPTS
           )
         })
       })
@@ -319,49 +303,40 @@ describe('FileStoreHandler', function () {
       beforeEach(function () {
         this.Features.hasFeature.withArgs('filestore').returns(false)
       })
-      it('should not open file handle', function (done) {
-        this.handler.uploadFileFromDisk(
+      it('should not open file handle', async function () {
+        await this.handler.promises.uploadFileFromDisk(
           this.projectId,
           this.fileArgs,
-          this.fsPath,
-          () => {
-            expect(this.fs.createReadStream).to.not.have.been.called
-            done()
-          }
+          this.fsPath
         )
+        expect(this.fs.createReadStream).to.not.have.been.called
       })
 
-      it('should not talk to filestore', function (done) {
-        this.handler.uploadFileFromDisk(
+      it('should not talk to filestore', async function () {
+        await this.handler.promises.uploadFileFromDisk(
           this.projectId,
           this.fileArgs,
-          this.fsPath,
-          () => {
-            expect(this.request).to.not.have.been.called
-            done()
-          }
+          this.fsPath
         )
+
+        expect(this.request).to.not.have.been.called
       })
 
-      it('should callback with the url and fileRef', function (done) {
+      it('should resolve with the url and fileRef', async function () {
         const fileUrl = this.getFileUrl(this.projectId, this.fileId)
-        this.handler.uploadFileFromDisk(
+        const { url, fileRef } = await this.handler.promises.uploadFileFromDisk(
           this.projectId,
           this.fileArgs,
-          this.fsPath,
-          (err, url, fileRef) => {
-            expect(err).to.not.exist
-            expect(url).to.equal(fileUrl)
-            expect(fileRef._id).to.equal(this.fileId)
-            expect(fileRef.hash).to.equal(this.hashValue)
-            done()
-          }
+          this.fsPath
         )
+        expect(url).to.equal(fileUrl)
+        expect(fileRef._id).to.equal(this.fileId)
+        expect(fileRef.hash).to.equal(this.hashValue)
       })
     })
 
     describe('symlink', function () {
-      it('should not read file if it is symlink', function (done) {
+      it('should not read file if it is symlink', async function () {
         this.fs.lstat = sinon.stub().callsArgWith(1, null, {
           isFile() {
             return false
@@ -371,28 +346,40 @@ describe('FileStoreHandler', function () {
           },
         })
 
-        this.handler.uploadFileFromDisk(
-          this.projectId,
-          this.fileArgs,
-          this.fsPath,
-          () => {
-            this.fs.createReadStream.called.should.equal(false)
-            done()
-          }
-        )
+        let error
+
+        try {
+          await this.handler.promises.uploadFileFromDisk(
+            this.projectId,
+            this.fileArgs,
+            this.fsPath
+          )
+        } catch (err) {
+          error = err
+        }
+
+        expect(error).to.exist
+
+        this.fs.createReadStream.called.should.equal(false)
       })
 
-      it('should not read file stat returns nothing', function (done) {
+      it('should not read file stat returns nothing', async function () {
         this.fs.lstat = sinon.stub().callsArgWith(1, null, null)
-        this.handler.uploadFileFromDisk(
-          this.projectId,
-          this.fileArgs,
-          this.fsPath,
-          () => {
-            this.fs.createReadStream.called.should.equal(false)
-            done()
-          }
-        )
+        let error
+
+        try {
+          await this.handler.promises.uploadFileFromDisk(
+            this.projectId,
+            this.fileArgs,
+            this.fsPath
+          )
+        } catch (err) {
+          error = err
+        }
+
+        expect(error).to.exist
+
+        this.fs.createReadStream.called.should.equal(false)
       })
     })
   })
@@ -410,13 +397,18 @@ describe('FileStoreHandler', function () {
       })
     })
 
-    it('should return the error if there is one', function (done) {
-      const error = 'my error'
-      this.request.callsArgWith(1, error)
-      this.handler.deleteFile(this.projectId, this.fileId, err => {
-        assert.equal(err, error)
-        done()
-      })
+    it('should reject with the error if there is one', async function () {
+      const expectedError = 'my error'
+      this.request.callsArgWith(1, expectedError)
+      let error
+
+      try {
+        await this.handler.promises.deleteFile(this.projectId, this.fileId)
+      } catch (err) {
+        error = err
+      }
+
+      expect(error).to.equal(expectedError)
     })
   })
 
@@ -425,40 +417,43 @@ describe('FileStoreHandler', function () {
       beforeEach(function () {
         this.Features.hasFeature.withArgs('filestore').returns(true)
       })
-      it('should send a delete request to filestore api', function (done) {
+      it('should send a delete request to filestore api', async function () {
         const projectUrl = this.getProjectUrl(this.projectId)
         this.request.callsArgWith(1, null)
 
-        this.handler.deleteProject(this.projectId, err => {
-          assert.equal(err, undefined)
-          this.request.args[0][0].method.should.equal('delete')
-          this.request.args[0][0].uri.should.equal(projectUrl)
-          done()
-        })
+        await this.handler.promises.deleteProject(this.projectId)
+        this.request.args[0][0].method.should.equal('delete')
+        this.request.args[0][0].uri.should.equal(projectUrl)
       })
 
-      it('should wrap the error if there is one', function (done) {
-        const error = new Error('my error')
-        this.request.callsArgWith(1, error)
-        this.handler.deleteProject(this.projectId, err => {
-          expect(OError.getFullStack(err)).to.match(
-            /something went wrong deleting a project in filestore/
-          )
-          expect(OError.getFullStack(err)).to.match(/my error/)
-          done()
-        })
+      it('should wrap the error if there is one', async function () {
+        const expectedError = new Error('my error')
+        this.request.callsArgWith(1, expectedError)
+        const promise = this.handler.promises.deleteProject(this.projectId)
+        let error
+
+        try {
+          await promise
+        } catch (err) {
+          error = err
+        }
+
+        expect(error).to.exist
+
+        expect(OError.getFullStack(error)).to.match(
+          /something went wrong deleting a project in filestore/
+        )
+        expect(OError.getFullStack(error)).to.match(/my error/)
       })
     })
     describe('when filestore is disabled', function () {
       beforeEach(function () {
         this.Features.hasFeature.withArgs('filestore').returns(false)
       })
-      it('should not send a delete request to filestore api', function (done) {
-        this.handler.deleteProject(this.projectId, err => {
-          assert.equal(err, undefined)
-          this.request.called.should.equal(false)
-          done()
-        })
+      it('should not send a delete request to filestore api', async function () {
+        await this.handler.promises.deleteProject(this.projectId)
+
+        this.request.called.should.equal(false)
       })
     })
   })
@@ -469,18 +464,37 @@ describe('FileStoreHandler', function () {
         this.Features.hasFeature.withArgs('filestore').returns(false)
       })
 
-      it('should callback with a NotFoundError', function (done) {
-        this.handler.getFileStream(this.projectId, this.fileId, {}, err => {
-          expect(err).to.be.instanceof(Errors.NotFoundError)
-          done()
-        })
+      it('should callback with a NotFoundError', async function () {
+        let error
+
+        try {
+          await this.handler.promises.getFileStream(
+            this.projectId,
+            this.fileId,
+            {}
+          )
+        } catch (err) {
+          error = err
+        }
+
+        expect(error).to.be.instanceOf(Errors.NotFoundError)
       })
 
-      it('should not call request', function (done) {
-        this.handler.getFileStream(this.projectId, this.fileId, {}, () => {
-          this.request.called.should.equal(false)
-          done()
-        })
+      it('should not call request', async function () {
+        let error
+
+        try {
+          await this.handler.promises.getFileStream(
+            this.projectId,
+            this.fileId,
+            {}
+          )
+        } catch (err) {
+          error = err
+        }
+
+        expect(error).to.exist
+        this.request.called.should.equal(false)
       })
     })
     describe('when filestore is enabled', function () {
@@ -490,53 +504,36 @@ describe('FileStoreHandler', function () {
         this.Features.hasFeature.withArgs('filestore').returns(true)
       })
 
-      it('should get the stream with the correct params', function (done) {
+      it('should get the stream with the correct params', async function () {
         const fileUrl = this.getFileUrl(this.projectId, this.fileId)
-        this.handler.getFileStream(
+        await this.handler.promises.getFileStream(
           this.projectId,
           this.fileId,
-          this.query,
-          (err, stream) => {
-            if (err) {
-              return done(err)
-            }
-            this.request.args[0][0].method.should.equal('get')
-            this.request.args[0][0].uri.should.equal(
-              fileUrl + '?from=getFileStream'
-            )
-            done()
-          }
+          this.query
+        )
+        this.request.args[0][0].method.should.equal('get')
+        this.request.args[0][0].uri.should.equal(
+          fileUrl + '?from=getFileStream'
         )
       })
 
-      it('should get stream from request', function (done) {
-        this.handler.getFileStream(
+      it('should get stream from request', async function () {
+        const stream = await this.handler.promises.getFileStream(
           this.projectId,
           this.fileId,
-          this.query,
-          (err, stream) => {
-            if (err) {
-              return done(err)
-            }
-            stream.should.equal(this.readStream)
-            done()
-          }
+          this.query
         )
+
+        stream.should.equal(this.readStream)
       })
 
-      it('should add an error handler', function (done) {
-        this.handler.getFileStream(
+      it('should add an error handler', async function () {
+        const stream = await this.handler.promises.getFileStream(
           this.projectId,
           this.fileId,
-          this.query,
-          (err, stream) => {
-            if (err) {
-              return done(err)
-            }
-            stream.on.calledWith('error').should.equal(true)
-            done()
-          }
+          this.query
         )
+        stream.on.calledWith('error').should.equal(true)
       })
 
       describe('when range is specified in query', function () {
@@ -544,22 +541,17 @@ describe('FileStoreHandler', function () {
           this.query = { range: '0-10' }
         })
 
-        it('should add a range header', function (done) {
-          this.handler.getFileStream(
+        it('should add a range header', async function () {
+          await this.handler.promises.getFileStream(
             this.projectId,
             this.fileId,
-            this.query,
-            (err, stream) => {
-              if (err) {
-                return done(err)
-              }
-              this.request.callCount.should.equal(1)
-              const { headers } = this.request.firstCall.args[0]
-              expect(headers).to.have.keys('range')
-              expect(headers.range).to.equal('bytes=0-10')
-              done()
-            }
+            this.query
           )
+
+          this.request.callCount.should.equal(1)
+          const { headers } = this.request.firstCall.args[0]
+          expect(headers).to.have.keys('range')
+          expect(headers.range).to.equal('bytes=0-10')
         })
 
         describe('when range is invalid', function () {
@@ -568,21 +560,15 @@ describe('FileStoreHandler', function () {
               this.query = { range: `${r}` }
             })
 
-            it(`should not add a range header for '${r}'`, function (done) {
-              this.handler.getFileStream(
+            it(`should not add a range header for '${r}'`, async function () {
+              await this.handler.promises.getFileStream(
                 this.projectId,
                 this.fileId,
-                this.query,
-                (err, stream) => {
-                  if (err) {
-                    return done(err)
-                  }
-                  this.request.callCount.should.equal(1)
-                  const { headers } = this.request.firstCall.args[0]
-                  expect(headers).to.not.have.keys('range')
-                  done()
-                }
+                this.query
               )
+              this.request.callCount.should.equal(1)
+              const { headers } = this.request.firstCall.args[0]
+              expect(headers).to.not.have.keys('range')
             })
           })
         })
@@ -591,7 +577,7 @@ describe('FileStoreHandler', function () {
   })
 
   describe('getFileSize', function () {
-    it('returns the file size reported by filestore', function (done) {
+    it('returns the file size reported by filestore', async function () {
       const expectedFileSize = 32432
       const fileUrl =
         this.getFileUrl(this.projectId, this.fileId) + '?from=getFileSize'
@@ -605,40 +591,54 @@ describe('FileStoreHandler', function () {
         },
       })
 
-      this.handler.getFileSize(this.projectId, this.fileId, (err, fileSize) => {
-        if (err) {
-          return done(err)
-        }
-        expect(fileSize).to.equal(expectedFileSize)
-        done()
-      })
+      const fileSize = await this.handler.promises.getFileSize(
+        this.projectId,
+        this.fileId
+      )
+      expect(fileSize).to.equal(expectedFileSize)
     })
 
-    it('throws a NotFoundError on a 404 from filestore', function (done) {
+    it('throws a NotFoundError on a 404 from filestore', async function () {
       this.request.head.yields(null, { statusCode: 404 })
 
-      this.handler.getFileSize(this.projectId, this.fileId, err => {
-        expect(err).to.be.instanceof(Errors.NotFoundError)
-        done()
-      })
+      let error
+
+      try {
+        await this.handler.promises.getFileSize(this.projectId, this.fileId)
+      } catch (err) {
+        error = err
+      }
+
+      expect(error).to.be.instanceOf(Errors.NotFoundError)
     })
 
-    it('throws an error on a non-200 from filestore', function (done) {
+    it('throws an error on a non-200 from filestore', async function () {
       this.request.head.yields(null, { statusCode: 500 })
 
-      this.handler.getFileSize(this.projectId, this.fileId, err => {
-        expect(err).to.be.instanceof(Error)
-        done()
-      })
+      let error
+
+      try {
+        await this.handler.promises.getFileSize(this.projectId, this.fileId)
+      } catch (err) {
+        error = err
+      }
+
+      expect(error).to.be.instanceOf(Error)
     })
 
-    it('rethrows errors from filestore', function (done) {
-      this.request.head.yields(new Error())
+    it('rethrows errors from filestore', async function () {
+      const expectedError = new Error('from filestore')
+      this.request.head.yields(expectedError)
 
-      this.handler.getFileSize(this.projectId, this.fileId, err => {
-        expect(err).to.be.instanceof(Error)
-        done()
-      })
+      let error
+
+      try {
+        await this.handler.promises.getFileSize(this.projectId, this.fileId)
+      } catch (err) {
+        error = err
+      }
+
+      expect(error).to.equal(expectedError)
     })
   })
 
@@ -648,74 +648,75 @@ describe('FileStoreHandler', function () {
       this.newFileId = 'new file id'
     })
 
-    it('should post json', function (done) {
+    it('should post json', async function () {
       const newFileUrl = this.getFileUrl(this.newProjectId, this.newFileId)
       this.request.callsArgWith(1, null, { statusCode: 200 })
 
-      this.handler.copyFile(
+      await this.handler.promises.copyFile(
         this.projectId,
         this.fileId,
         this.newProjectId,
-        this.newFileId,
-        () => {
-          this.request.args[0][0].method.should.equal('put')
-          this.request.args[0][0].uri.should.equal(newFileUrl)
-          this.request.args[0][0].json.source.project_id.should.equal(
-            this.projectId
-          )
-          this.request.args[0][0].json.source.file_id.should.equal(this.fileId)
-          done()
-        }
+        this.newFileId
+      )
+      this.request.args[0][0].method.should.equal('put')
+      this.request.args[0][0].uri.should.equal(newFileUrl)
+      this.request.args[0][0].json.source.project_id.should.equal(
+        this.projectId
       )
+      this.request.args[0][0].json.source.file_id.should.equal(this.fileId)
     })
 
-    it('returns the url', function (done) {
+    it('returns the url', async function () {
       const expectedUrl = this.getFileUrl(this.newProjectId, this.newFileId)
       this.request.callsArgWith(1, null, { statusCode: 200 })
-      this.handler.copyFile(
+      const url = await this.handler.promises.copyFile(
         this.projectId,
         this.fileId,
         this.newProjectId,
-        this.newFileId,
-        (err, url) => {
-          if (err) {
-            return done(err)
-          }
-          url.should.equal(expectedUrl)
-          done()
-        }
+        this.newFileId
       )
+
+      url.should.equal(expectedUrl)
     })
 
-    it('should return the err', function (done) {
-      const error = new Error('error')
-      this.request.callsArgWith(1, error)
-      this.handler.copyFile(
-        this.projectId,
-        this.fileId,
-        this.newProjectId,
-        this.newFileId,
-        err => {
-          err.should.equal(error)
-          done()
-        }
-      )
+    it('should return the err', async function () {
+      const expectedError = new Error('error')
+      this.request.callsArgWith(1, expectedError)
+      let error
+
+      try {
+        await this.handler.promises.copyFile(
+          this.projectId,
+          this.fileId,
+          this.newProjectId,
+          this.newFileId
+        )
+      } catch (err) {
+        error = err
+      }
+
+      expect(error).to.equal(expectedError)
     })
 
-    it('should return an error for a non-success statusCode', function (done) {
+    it('should return an error for a non-success statusCode', async function () {
       this.request.callsArgWith(1, null, { statusCode: 500 })
-      this.handler.copyFile(
-        this.projectId,
-        this.fileId,
-        this.newProjectId,
-        this.newFileId,
-        err => {
-          err.should.be.an('error')
-          err.message.should.equal(
-            'non-ok response from filestore for copyFile: 500'
-          )
-          done()
-        }
+      let error
+
+      try {
+        await this.handler.promises.copyFile(
+          this.projectId,
+          this.fileId,
+          this.newProjectId,
+          this.newFileId
+        )
+      } catch (err) {
+        error = err
+      }
+
+      expect(error).to.be.instanceOf(Error)
+      expect(error).to.have.property(
+        'message',
+        'non-ok response from filestore for copyFile: 500'
       )
     })
   })

+ 236 - 239
services/web/test/unit/src/Institutions/InstitutionsAPITests.js

@@ -45,48 +45,43 @@ describe('InstitutionsAPI', function () {
   })
 
   describe('getInstitutionAffiliations', function () {
-    it('get affiliations', function (done) {
+    it('get affiliations', async function () {
       this.institutionId = 123
       const responseBody = ['123abc', '456def']
       this.request.yields(null, { statusCode: 200 }, responseBody)
-      this.InstitutionsAPI.getInstitutionAffiliations(
-        this.institutionId,
-        (err, body) => {
-          expect(err).not.to.exist
-          this.request.calledOnce.should.equal(true)
-          const requestOptions = this.request.lastCall.args[0]
-          const expectedUrl = `v1.url/api/v2/institutions/${this.institutionId}/affiliations`
-          requestOptions.url.should.equal(expectedUrl)
-          requestOptions.method.should.equal('GET')
-          requestOptions.maxAttempts.should.exist
-          requestOptions.maxAttempts.should.not.equal(0)
-          requestOptions.retryDelay.should.exist
-          expect(requestOptions.body).not.to.exist
-          body.should.equal(responseBody)
-          done()
-        }
-      )
+      const body =
+        await this.InstitutionsAPI.promises.getInstitutionAffiliations(
+          this.institutionId
+        )
+
+      this.request.calledOnce.should.equal(true)
+      const requestOptions = this.request.lastCall.args[0]
+      const expectedUrl = `v1.url/api/v2/institutions/${this.institutionId}/affiliations`
+      requestOptions.url.should.equal(expectedUrl)
+      requestOptions.method.should.equal('GET')
+      requestOptions.maxAttempts.should.exist
+      requestOptions.maxAttempts.should.not.equal(0)
+      requestOptions.retryDelay.should.exist
+      expect(requestOptions.body).not.to.exist
+      body.should.equal(responseBody)
     })
 
-    it('handle empty response', function (done) {
+    it('handle empty response', async function () {
       this.settings.apis.v1.url = ''
 
-      this.InstitutionsAPI.getInstitutionAffiliations(
-        this.institutionId,
-        (err, body) => {
-          expect(err).not.to.exist
-          expect(body).to.be.a('Array')
-          body.length.should.equal(0)
-          done()
-        }
-      )
+      const body =
+        await this.InstitutionsAPI.promises.getInstitutionAffiliations(
+          this.institutionId
+        )
+      expect(body).to.be.a('Array')
+      body.length.should.equal(0)
     })
   })
 
   describe('getLicencesForAnalytics', function () {
     const lag = 'daily'
     const queryDate = '2017-01-07:00:00.000Z'
-    it('should send the request to v1', function (done) {
+    it('should send the request to v1', async function () {
       const v1Result = {
         lag: 'daily',
         date: queryDate,
@@ -96,100 +91,91 @@ describe('InstitutionsAPI', function () {
         },
       }
       this.request.callsArgWith(1, null, { statusCode: 201 }, v1Result)
-      this.InstitutionsAPI.getLicencesForAnalytics(
+      await this.InstitutionsAPI.promises.getLicencesForAnalytics(
         lag,
-        queryDate,
-        (error, result) => {
-          expect(error).not.to.exist
-          const requestOptions = this.request.lastCall.args[0]
-          expect(requestOptions.body.query_date).to.equal(queryDate)
-          expect(requestOptions.body.lag).to.equal(lag)
-          requestOptions.method.should.equal('GET')
-          done()
-        }
+        queryDate
       )
+      const requestOptions = this.request.lastCall.args[0]
+      expect(requestOptions.body.query_date).to.equal(queryDate)
+      expect(requestOptions.body.lag).to.equal(lag)
+      requestOptions.method.should.equal('GET')
     })
-    it('should handle errors', function (done) {
+    it('should handle errors', async function () {
       this.request.callsArgWith(1, null, { statusCode: 500 })
-      this.InstitutionsAPI.getLicencesForAnalytics(
-        lag,
-        queryDate,
-        (error, result) => {
-          expect(error).to.be.instanceof(Errors.V1ConnectionError)
-          done()
-        }
-      )
+      let error
+
+      try {
+        await this.InstitutionsAPI.promises.getLicencesForAnalytics(
+          lag,
+          queryDate
+        )
+      } catch (err) {
+        error = err
+      }
+
+      expect(error).to.be.instanceOf(Errors.V1ConnectionError)
     })
   })
 
   describe('getUserAffiliations', function () {
-    it('get affiliations', function (done) {
+    it('get affiliations', async function () {
       const responseBody = [{ foo: 'bar' }]
       this.request.callsArgWith(1, null, { statusCode: 201 }, responseBody)
-      this.InstitutionsAPI.getUserAffiliations(
-        this.stubbedUser._id,
-        (err, body) => {
-          expect(err).not.to.exist
-          this.request.calledOnce.should.equal(true)
-          const requestOptions = this.request.lastCall.args[0]
-          const expectedUrl = `v1.url/api/v2/users/${this.stubbedUser._id}/affiliations`
-          requestOptions.url.should.equal(expectedUrl)
-          requestOptions.method.should.equal('GET')
-          requestOptions.maxAttempts.should.equal(3)
-          expect(requestOptions.body).not.to.exist
-          body.should.equal(responseBody)
-          done()
-        }
+      const body = await this.InstitutionsAPI.promises.getUserAffiliations(
+        this.stubbedUser._id
       )
+      this.request.calledOnce.should.equal(true)
+      const requestOptions = this.request.lastCall.args[0]
+      const expectedUrl = `v1.url/api/v2/users/${this.stubbedUser._id}/affiliations`
+      requestOptions.url.should.equal(expectedUrl)
+      requestOptions.method.should.equal('GET')
+      requestOptions.maxAttempts.should.equal(3)
+      expect(requestOptions.body).not.to.exist
+      body.should.equal(responseBody)
     })
 
-    it('handle error', function (done) {
+    it('handle error', async function () {
       const body = { errors: 'affiliation error message' }
       this.request.callsArgWith(1, null, { statusCode: 503 }, body)
-      this.InstitutionsAPI.getUserAffiliations(this.stubbedUser._id, err => {
-        expect(err).to.be.instanceof(Errors.V1ConnectionError)
-        done()
-      })
+      let error
+
+      try {
+        await this.InstitutionsAPI.promises.getUserAffiliations(
+          this.stubbedUser._id
+        )
+      } catch (err) {
+        error = err
+      }
+
+      expect(error).to.be.instanceOf(Errors.V1ConnectionError)
     })
 
-    it('handle empty response', function (done) {
+    it('handle empty response', async function () {
       this.settings.apis.v1.url = ''
-      this.InstitutionsAPI.getUserAffiliations(
-        this.stubbedUser._id,
-        (err, body) => {
-          expect(err).not.to.exist
-          expect(body).to.be.a('Array')
-          body.length.should.equal(0)
-          done()
-        }
+      const body = await this.InstitutionsAPI.promises.getUserAffiliations(
+        this.stubbedUser._id
       )
+      expect(body).to.be.a('Array')
+      body.length.should.equal(0)
     })
   })
 
   describe('getUsersNeedingReconfirmationsLapsedProcessed', function () {
-    it('get the list of users', function (done) {
+    it('get the list of users', async function () {
       this.fetchJson.resolves({ statusCode: 200 })
-      this.InstitutionsAPI.getUsersNeedingReconfirmationsLapsedProcessed(
-        error => {
-          expect(error).not.to.exist
-          this.fetchJson.calledOnce.should.equal(true)
-          const requestOptions = this.fetchJson.lastCall.args[1]
-          const expectedUrl = `v1.url/api/v2/institutions/need_reconfirmation_lapsed_processed`
-          this.fetchJson.lastCall.args[0].should.equal(expectedUrl)
-          requestOptions.method.should.equal('GET')
-          done()
-        }
-      )
+      await this.InstitutionsAPI.promises.getUsersNeedingReconfirmationsLapsedProcessed()
+      this.fetchJson.calledOnce.should.equal(true)
+      const requestOptions = this.fetchJson.lastCall.args[1]
+      const expectedUrl = `v1.url/api/v2/institutions/need_reconfirmation_lapsed_processed`
+      this.fetchJson.lastCall.args[0].should.equal(expectedUrl)
+      requestOptions.method.should.equal('GET')
     })
 
-    it('handle error', function (done) {
+    it('handle error', async function () {
       this.fetchJson.throws({ info: { statusCode: 500 } })
-      this.InstitutionsAPI.getUsersNeedingReconfirmationsLapsedProcessed(
-        error => {
-          expect(error).to.exist
-          done()
-        }
-      )
+      await expect(
+        this.InstitutionsAPI.promises.getUsersNeedingReconfirmationsLapsedProcessed()
+      ).to.be.rejected
     })
   })
 
@@ -198,7 +184,7 @@ describe('InstitutionsAPI', function () {
       this.fetchNothing.resolves({ status: 201 })
     })
 
-    it('add affiliation', function (done) {
+    it('add affiliation', async function () {
       const affiliationOptions = {
         university: { id: 1 },
         department: 'Math',
@@ -206,95 +192,104 @@ describe('InstitutionsAPI', function () {
         confirmedAt: new Date(),
         entitlement: true,
       }
-      this.InstitutionsAPI.addAffiliation(
+      await this.InstitutionsAPI.promises.addAffiliation(
         this.stubbedUser._id,
         this.newEmail,
-        affiliationOptions,
-        err => {
-          expect(err).not.to.exist
-          this.fetchNothing.calledOnce.should.equal(true)
-          const requestOptions = this.fetchNothing.lastCall.args[1]
-          const expectedUrl = `v1.url/api/v2/users/${this.stubbedUser._id}/affiliations`
-          expect(this.fetchNothing.lastCall.args[0]).to.equal(expectedUrl)
-          requestOptions.method.should.equal('POST')
-
-          const { json } = requestOptions
-          Object.keys(json).length.should.equal(7)
-          expect(json).to.deep.equal(
-            Object.assign(
-              { email: this.newEmail, rejectIfBlocklisted: undefined },
-              affiliationOptions
-            )
-          )
-          this.markAsReadIpMatcher.calledOnce.should.equal(true)
-          done()
-        }
+        affiliationOptions
+      )
+      this.fetchNothing.calledOnce.should.equal(true)
+      const requestOptions = this.fetchNothing.lastCall.args[1]
+      const expectedUrl = `v1.url/api/v2/users/${this.stubbedUser._id}/affiliations`
+      expect(this.fetchNothing.lastCall.args[0]).to.equal(expectedUrl)
+      requestOptions.method.should.equal('POST')
+
+      const { json } = requestOptions
+      Object.keys(json).length.should.equal(7)
+      expect(json).to.deep.equal(
+        Object.assign(
+          { email: this.newEmail, rejectIfBlocklisted: undefined },
+          affiliationOptions
+        )
       )
+      this.markAsReadIpMatcher.calledOnce.should.equal(true)
     })
 
-    it('handles 422 error', function (done) {
+    it('handles 422 error', async function () {
       const messageFromApi = 'affiliation error message'
       const body = JSON.stringify({ errors: messageFromApi })
       this.fetchNothing.throws({ response: { status: 422 }, body })
-      this.InstitutionsAPI.addAffiliation(
-        this.stubbedUser._id,
-        this.newEmail,
-        {},
-        err => {
-          expect(err).to.exist
-          expect(err).to.be.instanceOf(Errors.InvalidInstitutionalEmailError)
-          err.message.should.have.string(422)
-          err.message.should.have.string(messageFromApi)
-          done()
-        }
-      )
+      let error
+
+      try {
+        await this.InstitutionsAPI.promises.addAffiliation(
+          this.stubbedUser._id,
+          this.newEmail,
+          {}
+        )
+      } catch (err) {
+        error = err
+      }
+
+      expect(error).to.be.instanceOf(Errors.InvalidInstitutionalEmailError)
+      expect(error).to.have.property('message', `422: ${messageFromApi}`)
     })
 
-    it('handles 500 error', function (done) {
+    it('handles 500 error', async function () {
       const body = { errors: 'affiliation error message' }
       this.fetchNothing.throws({ response: { status: 500 }, body })
-      this.InstitutionsAPI.addAffiliation(
-        this.stubbedUser._id,
-        this.newEmail,
-        {},
-        err => {
-          expect(err).to.be.instanceOf(Errors.V1ConnectionError)
-          expect(err.message).to.equal('error getting affiliations from v1')
-          expect(err.info).to.deep.equal({ status: 500, body })
-          done()
-        }
-      )
+      let error
+
+      try {
+        await this.InstitutionsAPI.promises.addAffiliation(
+          this.stubbedUser._id,
+          this.newEmail,
+          {}
+        )
+      } catch (err) {
+        error = err
+      }
+
+      expect(error).to.be.instanceOf(Errors.V1ConnectionError)
+      expect(error.message).to.equal('error getting affiliations from v1')
+      expect(error.info).to.eql({
+        status: 500,
+        body: { errors: 'affiliation error message' },
+      })
     })
 
-    it('uses default error message when no error body in response', function (done) {
+    it('uses default error message when no error body in response', async function () {
       this.fetchNothing.throws({ response: { status: 429 } })
-      this.InstitutionsAPI.addAffiliation(
-        this.stubbedUser._id,
-        this.newEmail,
-        {},
-        err => {
-          expect(err).to.exist
-          expect(err.message).to.equal("Couldn't create affiliation: 429")
-          done()
-        }
+      let error
+
+      try {
+        await this.InstitutionsAPI.promises.addAffiliation(
+          this.stubbedUser._id,
+          this.newEmail,
+          {}
+        )
+      } catch (err) {
+        error = err
+      }
+
+      expect(error).to.be.instanceOf(Error)
+      expect(error).to.have.property(
+        'message',
+        "Couldn't create affiliation: 429"
       )
     })
 
-    it('does not try to mark IP matcher notifications as read if no university passed', function (done) {
+    it('does not try to mark IP matcher notifications as read if no university passed', async function () {
       const affiliationOptions = {
         confirmedAt: new Date(),
       }
 
-      this.InstitutionsAPI.addAffiliation(
+      await this.InstitutionsAPI.promises.addAffiliation(
         this.stubbedUser._id,
         this.newEmail,
-        affiliationOptions,
-        err => {
-          expect(err).not.to.exist
-          expect(this.markAsReadIpMatcher.callCount).to.equal(0)
-          done()
-        }
+        affiliationOptions
       )
+
+      expect(this.markAsReadIpMatcher.callCount).to.equal(0)
     })
   })
 
@@ -303,58 +298,64 @@ describe('InstitutionsAPI', function () {
       this.fetchNothing.throws({ response: { status: 404 } })
     })
 
-    it('remove affiliation', function (done) {
-      this.InstitutionsAPI.removeAffiliation(
+    it('remove affiliation', async function () {
+      await this.InstitutionsAPI.promises.removeAffiliation(
         this.stubbedUser._id,
-        this.newEmail,
-        err => {
-          expect(err).not.to.exist
-          this.fetchNothing.calledOnce.should.equal(true)
-          const requestOptions = this.fetchNothing.lastCall.args[1]
-          const expectedUrl = `v1.url/api/v2/users/${this.stubbedUser._id}/affiliations/remove`
-          this.fetchNothing.lastCall.args[0].should.equal(expectedUrl)
-          requestOptions.method.should.equal('POST')
-          expect(requestOptions.json).to.deep.equal({ email: this.newEmail })
-          done()
-        }
+        this.newEmail
       )
+      this.fetchNothing.calledOnce.should.equal(true)
+      const requestOptions = this.fetchNothing.lastCall.args[1]
+      const expectedUrl = `v1.url/api/v2/users/${this.stubbedUser._id}/affiliations/remove`
+      this.fetchNothing.lastCall.args[0].should.equal(expectedUrl)
+      requestOptions.method.should.equal('POST')
+      expect(requestOptions.json).to.deep.equal({ email: this.newEmail })
     })
 
-    it('handle error', function (done) {
+    it('handle error', async function () {
       this.fetchNothing.throws({ response: { status: 500 } })
-      this.InstitutionsAPI.removeAffiliation(
-        this.stubbedUser._id,
-        this.newEmail,
-        err => {
-          expect(err).to.exist
-          err.message.should.exist
-          done()
-        }
-      )
+      let error
+
+      try {
+        await this.InstitutionsAPI.promises.removeAffiliation(
+          this.stubbedUser._id,
+          this.newEmail
+        )
+      } catch (err) {
+        error = err
+      }
+
+      expect(error).to.exist
+      expect(error).to.have.property('message')
     })
   })
 
   describe('deleteAffiliations', function () {
-    it('delete affiliations', function (done) {
+    it('delete affiliations', async function () {
       this.request.callsArgWith(1, null, { statusCode: 200 })
-      this.InstitutionsAPI.deleteAffiliations(this.stubbedUser._id, err => {
-        expect(err).not.to.exist
-        this.request.calledOnce.should.equal(true)
-        const requestOptions = this.request.lastCall.args[0]
-        const expectedUrl = `v1.url/api/v2/users/${this.stubbedUser._id}/affiliations`
-        requestOptions.url.should.equal(expectedUrl)
-        requestOptions.method.should.equal('DELETE')
-        done()
-      })
+      await this.InstitutionsAPI.promises.deleteAffiliations(
+        this.stubbedUser._id
+      )
+      this.request.calledOnce.should.equal(true)
+      const requestOptions = this.request.lastCall.args[0]
+      const expectedUrl = `v1.url/api/v2/users/${this.stubbedUser._id}/affiliations`
+      requestOptions.url.should.equal(expectedUrl)
+      requestOptions.method.should.equal('DELETE')
     })
 
-    it('handle error', function (done) {
+    it('handle error', async function () {
       const body = { errors: 'affiliation error message' }
       this.request.callsArgWith(1, null, { statusCode: 518 }, body)
-      this.InstitutionsAPI.deleteAffiliations(this.stubbedUser._id, err => {
-        expect(err).to.be.instanceof(Errors.V1ConnectionError)
-        done()
-      })
+      let error
+
+      try {
+        await this.InstitutionsAPI.promises.deleteAffiliations(
+          this.stubbedUser._id
+        )
+      } catch (err) {
+        error = err
+      }
+
+      expect(error).to.be.instanceOf(Errors.V1ConnectionError)
     })
   })
 
@@ -363,61 +364,57 @@ describe('InstitutionsAPI', function () {
       this.request.callsArgWith(1, null, { statusCode: 204 })
     })
 
-    it('endorse affiliation', function (done) {
-      this.InstitutionsAPI.endorseAffiliation(
+    it('endorse affiliation', async function () {
+      await this.InstitutionsAPI.promises.endorseAffiliation(
         this.stubbedUser._id,
         this.newEmail,
         'Student',
-        'Physics',
-        err => {
-          expect(err).not.to.exist
-          this.request.calledOnce.should.equal(true)
-          const requestOptions = this.request.lastCall.args[0]
-          const expectedUrl = `v1.url/api/v2/users/${this.stubbedUser._id}/affiliations/endorse`
-          requestOptions.url.should.equal(expectedUrl)
-          requestOptions.method.should.equal('POST')
-
-          const { body } = requestOptions
-          Object.keys(body).length.should.equal(3)
-          body.email.should.equal(this.newEmail)
-          body.role.should.equal('Student')
-          body.department.should.equal('Physics')
-          done()
-        }
+        'Physics'
       )
+      this.request.calledOnce.should.equal(true)
+      const requestOptions = this.request.lastCall.args[0]
+      const expectedUrl = `v1.url/api/v2/users/${this.stubbedUser._id}/affiliations/endorse`
+      requestOptions.url.should.equal(expectedUrl)
+      requestOptions.method.should.equal('POST')
+
+      const { body } = requestOptions
+      Object.keys(body).length.should.equal(3)
+      body.email.should.equal(this.newEmail)
+      body.role.should.equal('Student')
+      body.department.should.equal('Physics')
     })
   })
 
   describe('sendUsersWithReconfirmationsLapsedProcessed', function () {
     const users = ['abc123', 'def456']
 
-    it('sends the list of users', function (done) {
+    it('sends the list of users', async function () {
       this.request.callsArgWith(1, null, { statusCode: 200 })
-      this.InstitutionsAPI.sendUsersWithReconfirmationsLapsedProcessed(
-        users,
-        error => {
-          expect(error).not.to.exist
-          this.request.calledOnce.should.equal(true)
-          const requestOptions = this.request.lastCall.args[0]
-          const expectedUrl =
-            'v1.url/api/v2/institutions/reconfirmation_lapsed_processed'
-          requestOptions.url.should.equal(expectedUrl)
-          requestOptions.method.should.equal('POST')
-          expect(requestOptions.body).to.deep.equal({ users })
-          done()
-        }
+      await this.InstitutionsAPI.promises.sendUsersWithReconfirmationsLapsedProcessed(
+        users
       )
+      this.request.calledOnce.should.equal(true)
+      const requestOptions = this.request.lastCall.args[0]
+      const expectedUrl =
+        'v1.url/api/v2/institutions/reconfirmation_lapsed_processed'
+      requestOptions.url.should.equal(expectedUrl)
+      requestOptions.method.should.equal('POST')
+      expect(requestOptions.body).to.deep.equal({ users })
     })
 
-    it('handle error', function (done) {
+    it('handle error', async function () {
       this.request.callsArgWith(1, null, { statusCode: 500 })
-      this.InstitutionsAPI.sendUsersWithReconfirmationsLapsedProcessed(
-        users,
-        error => {
-          expect(error).to.exist
-          done()
-        }
-      )
+      let error
+
+      try {
+        await this.InstitutionsAPI.promises.sendUsersWithReconfirmationsLapsedProcessed(
+          users
+        )
+      } catch (err) {
+        error = err
+      }
+
+      expect(error).to.exist
     })
   })
 })

+ 46 - 87
services/web/test/unit/src/Institutions/InstitutionsFeaturesTests.js

@@ -1,17 +1,4 @@
-/* eslint-disable
-    max-len,
-    no-return-assign,
-    no-unused-vars,
-*/
-// TODO: This file was created by bulk-decaffeinate.
-// Fix any style issues and re-enable lint.
-/*
- * decaffeinate suggestions:
- * DS102: Remove unnecessary code created because of implicit returns
- * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
- */
 const SandboxedModule = require('sandboxed-module')
-const assert = require('assert')
 const { expect } = require('chai')
 const sinon = require('sinon')
 const modulePath = require('path').join(
@@ -41,45 +28,39 @@ describe('InstitutionsFeatures', function () {
   })
 
   describe('hasLicence', function () {
-    it('should handle error', function (done) {
+    it('should handle error', async function () {
       this.UserGetter.promises.getUserFullEmails.rejects(new Error('Nope'))
-      return this.InstitutionsFeatures.hasLicence(
-        this.userId,
-        (error, hasLicence) => {
-          expect(error).to.exist
-          return done()
-        }
-      )
+      let error
+
+      try {
+        await this.InstitutionsFeatures.promises.hasLicence(this.userId)
+      } catch (err) {
+        error = err
+      }
+
+      expect(error).to.exist
     })
 
-    it('should return false if user has no paid affiliations', function (done) {
+    it('should return false if user has no paid affiliations', async function () {
       this.UserGetter.promises.getUserFullEmails.resolves(
         this.emailDataWithoutLicense
       )
-      return this.InstitutionsFeatures.hasLicence(
-        this.userId,
-        (error, hasLicence) => {
-          expect(error).to.not.exist
-          expect(hasLicence).to.be.false
-          return done()
-        }
+      const hasLicence = await this.InstitutionsFeatures.promises.hasLicence(
+        this.userId
       )
+      expect(hasLicence).to.be.false
     })
 
-    it('should return true if user has confirmed paid affiliation', function (done) {
+    it('should return true if user has confirmed paid affiliation', async function () {
       const emailData = [
         { emailHasInstitutionLicence: true },
         { emailHasInstitutionLicence: false },
       ]
       this.UserGetter.promises.getUserFullEmails.resolves(emailData)
-      return this.InstitutionsFeatures.hasLicence(
-        this.userId,
-        (error, hasLicence) => {
-          expect(error).to.not.exist
-          expect(hasLicence).to.be.true
-          return done()
-        }
+      const hasLicence = await this.InstitutionsFeatures.promises.hasLicence(
+        this.userId
       )
+      expect(hasLicence).to.be.true
     })
   })
 
@@ -91,84 +72,62 @@ describe('InstitutionsFeatures', function () {
         .returns(this.testFeatures)
     })
 
-    it('should handle error', function (done) {
+    it('should handle error', async function () {
       this.UserGetter.promises.getUserFullEmails.rejects(new Error('Nope'))
-      return this.InstitutionsFeatures.getInstitutionsFeatures(
-        this.userId,
-        (error, features) => {
-          expect(error).to.exist
-          return done()
-        }
-      )
+      await expect(
+        this.InstitutionsFeatures.promises.getInstitutionsFeatures(this.userId)
+      ).to.be.rejected
     })
 
-    it('should return no feaures if user has no plan code', function (done) {
+    it('should return no feaures if user has no plan code', async function () {
       this.UserGetter.promises.getUserFullEmails.resolves(
         this.emailDataWithoutLicense
       )
-      return this.InstitutionsFeatures.getInstitutionsFeatures(
-        this.userId,
-        (error, features) => {
-          expect(error).to.not.exist
-          expect(features).to.deep.equal({})
-          return done()
-        }
-      )
+      const features =
+        await this.InstitutionsFeatures.promises.getInstitutionsFeatures(
+          this.userId
+        )
+      expect(features).to.deep.equal({})
     })
 
-    it('should return feaures if user has affiliations plan code', function (done) {
+    it('should return feaures if user has affiliations plan code', async function () {
       this.UserGetter.promises.getUserFullEmails.resolves(
         this.emailDataWithLicense
       )
-      return this.InstitutionsFeatures.getInstitutionsFeatures(
-        this.userId,
-        (error, features) => {
-          expect(error).to.not.exist
-          expect(features).to.deep.equal(this.testFeatures.features)
-          return done()
-        }
-      )
+      const features =
+        await this.InstitutionsFeatures.promises.getInstitutionsFeatures(
+          this.userId
+        )
+      expect(features).to.deep.equal(this.testFeatures.features)
     })
   })
 
   describe('getInstitutionsPlan', function () {
-    it('should handle error', function (done) {
+    it('should handle error', async function () {
       this.UserGetter.promises.getUserFullEmails.rejects(new Error('Nope'))
-      return this.InstitutionsFeatures.getInstitutionsPlan(
-        this.userId,
-        error => {
-          expect(error).to.exist
-          return done()
-        }
-      )
+      await expect(
+        this.InstitutionsFeatures.promises.getInstitutionsPlan(this.userId)
+      ).to.be.rejected
     })
 
-    it('should return no plan if user has no licence', function (done) {
+    it('should return no plan if user has no licence', async function () {
       this.UserGetter.promises.getUserFullEmails.resolves(
         this.emailDataWithoutLicense
       )
-      return this.InstitutionsFeatures.getInstitutionsPlan(
-        this.userId,
-        (error, plan) => {
-          expect(error).to.not.exist
-          expect(plan).to.equal(null)
-          return done()
-        }
+      const plan = await this.InstitutionsFeatures.promises.getInstitutionsPlan(
+        this.userId
       )
+      expect(plan).to.equal(null)
     })
 
-    it('should return plan if user has licence', function (done) {
+    it('should return plan if user has licence', async function () {
       this.UserGetter.promises.getUserFullEmails.resolves(
         this.emailDataWithLicense
       )
-      return this.InstitutionsFeatures.getInstitutionsPlan(
-        this.userId,
-        (error, plan) => {
-          expect(error).to.not.exist
-          expect(plan).to.equal(this.institutionPlanCode)
-          return done()
-        }
+      const plan = await this.InstitutionsFeatures.promises.getInstitutionsPlan(
+        this.userId
       )
+      expect(plan).to.equal(this.institutionPlanCode)
     })
   })
 })

+ 76 - 82
services/web/test/unit/src/Notifications/NotificationsBuilderTests.js

@@ -22,22 +22,19 @@ describe('NotificationsBuilder', function () {
     })
   })
 
-  describe('dropboxUnlinkedDueToLapsedReconfirmation', function (done) {
-    it('should create the notification', function (done) {
-      this.controller
+  describe('dropboxUnlinkedDueToLapsedReconfirmation', function () {
+    it('should create the notification', async function () {
+      await this.controller.promises
         .dropboxUnlinkedDueToLapsedReconfirmation(userId)
-        .create(error => {
-          expect(error).to.not.exist
-          expect(this.handler.createNotification).to.have.been.calledWith(
-            userId,
-            'drobox-unlinked-due-to-lapsed-reconfirmation',
-            'notification_dropbox_unlinked_due_to_lapsed_reconfirmation',
-            {},
-            null,
-            true
-          )
-          done()
-        })
+        .create()
+      expect(this.handler.createNotification).to.have.been.calledWith(
+        userId,
+        'drobox-unlinked-due-to-lapsed-reconfirmation',
+        'notification_dropbox_unlinked_due_to_lapsed_reconfirmation',
+        {},
+        null,
+        true
+      )
     })
     describe('NotificationsHandler error', function () {
       let anError
@@ -45,19 +42,23 @@ describe('NotificationsBuilder', function () {
         anError = new Error('oops')
         this.handler.createNotification.yields(anError)
       })
-      it('should return errors from NotificationsHandler', function (done) {
-        this.controller
-          .dropboxUnlinkedDueToLapsedReconfirmation(userId)
-          .create(error => {
-            expect(error).to.exist
-            expect(error).to.deep.equal(anError)
-            done()
-          })
+      it('should return errors from NotificationsHandler', async function () {
+        let error
+
+        try {
+          await this.controller.promises
+            .dropboxUnlinkedDueToLapsedReconfirmation(userId)
+            .create()
+        } catch (err) {
+          error = err
+        }
+
+        expect(error).to.equal(anError)
       })
     })
   })
 
-  describe('groupInvitation', function (done) {
+  describe('groupInvitation', function () {
     const subscriptionId = '123123bcabca'
     beforeEach(function () {
       this.invite = {
@@ -67,29 +68,26 @@ describe('NotificationsBuilder', function () {
       }
     })
 
-    it('should create the notification', function (done) {
-      this.controller
+    it('should create the notification', async function () {
+      await this.controller.promises
         .groupInvitation(
           userId,
           subscriptionId,
           this.invite.managedUsersEnabled
         )
-        .create(this.invite, error => {
-          expect(error).to.not.exist
-          expect(this.handler.createNotification).to.have.been.calledWith(
-            userId,
-            `groupInvitation-${subscriptionId}-${userId}`,
-            'notification_group_invitation',
-            {
-              token: this.invite.token,
-              inviterName: this.invite.inviterName,
-              managedUsersEnabled: this.invite.managedUsersEnabled,
-            },
-            null,
-            true
-          )
-          done()
-        })
+        .create(this.invite)
+      expect(this.handler.createNotification).to.have.been.calledWith(
+        userId,
+        `groupInvitation-${subscriptionId}-${userId}`,
+        'notification_group_invitation',
+        {
+          token: this.invite.token,
+          inviterName: this.invite.inviterName,
+          managedUsersEnabled: this.invite.managedUsersEnabled,
+        },
+        null,
+        true
+      )
     })
   })
 
@@ -107,27 +105,25 @@ describe('NotificationsBuilder', function () {
         this.request.callsArgWith(1, null, { statusCode: 200 }, this.body)
       })
 
-      it('should call v1 and create affiliation notifications', function (done) {
+      it('should call v1 and create affiliation notifications', async function () {
         const ip = '192.168.0.1'
-        this.controller.ipMatcherAffiliation(userId).create(ip, callback => {
-          this.request.calledOnce.should.equal(true)
-          const expectedOpts = {
-            institutionId: this.body.id,
-            university_name: this.body.name,
-            content: this.body.enrolment_ad_html,
-            ssoEnabled: false,
-            portalPath: undefined,
-          }
-          this.handler.createNotification
-            .calledWith(
-              userId,
-              `ip-matched-affiliation-${this.body.id}`,
-              'notification_ip_matched_affiliation',
-              expectedOpts
-            )
-            .should.equal(true)
-          done()
-        })
+        await this.controller.promises.ipMatcherAffiliation(userId).create(ip)
+        this.request.calledOnce.should.equal(true)
+        const expectedOpts = {
+          institutionId: this.body.id,
+          university_name: this.body.name,
+          content: this.body.enrolment_ad_html,
+          ssoEnabled: false,
+          portalPath: undefined,
+        }
+        this.handler.createNotification
+          .calledWith(
+            userId,
+            `ip-matched-affiliation-${this.body.id}`,
+            'notification_ip_matched_affiliation',
+            expectedOpts
+          )
+          .should.equal(true)
       })
     })
     describe('without portal and without SSO', function () {
@@ -143,27 +139,25 @@ describe('NotificationsBuilder', function () {
         this.request.callsArgWith(1, null, { statusCode: 200 }, this.body)
       })
 
-      it('should call v1 and create affiliation notifications', function (done) {
+      it('should call v1 and create affiliation notifications', async function () {
         const ip = '192.168.0.1'
-        this.controller.ipMatcherAffiliation(userId).create(ip, callback => {
-          this.request.calledOnce.should.equal(true)
-          const expectedOpts = {
-            institutionId: this.body.id,
-            university_name: this.body.name,
-            content: this.body.enrolment_ad_html,
-            ssoEnabled: true,
-            portalPath: '/edu/stanford',
-          }
-          this.handler.createNotification
-            .calledWith(
-              userId,
-              `ip-matched-affiliation-${this.body.id}`,
-              'notification_ip_matched_affiliation',
-              expectedOpts
-            )
-            .should.equal(true)
-          done()
-        })
+        await this.controller.promises.ipMatcherAffiliation(userId).create(ip)
+        this.request.calledOnce.should.equal(true)
+        const expectedOpts = {
+          institutionId: this.body.id,
+          university_name: this.body.name,
+          content: this.body.enrolment_ad_html,
+          ssoEnabled: true,
+          portalPath: '/edu/stanford',
+        }
+        this.handler.createNotification
+          .calledWith(
+            userId,
+            `ip-matched-affiliation-${this.body.id}`,
+            'notification_ip_matched_affiliation',
+            expectedOpts
+          )
+          .should.equal(true)
       })
     })
   })

+ 68 - 98
services/web/test/unit/src/Notifications/NotificationsHandlerTests.js

@@ -1,16 +1,3 @@
-/* eslint-disable
-    n/handle-callback-err,
-    max-len,
-    no-return-assign,
-    no-unused-vars,
-*/
-// TODO: This file was created by bulk-decaffeinate.
-// Fix any style issues and re-enable lint.
-/*
- * decaffeinate suggestions:
- * DS102: Remove unnecessary code created because of implicit returns
- * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
- */
 const SandboxedModule = require('sandboxed-module')
 const { assert } = require('chai')
 const sinon = require('sinon')
@@ -18,7 +5,6 @@ const modulePath = require('path').join(
   __dirname,
   '../../../../app/src/Features/Notifications/NotificationsHandler.js'
 )
-const _ = require('lodash')
 
 describe('NotificationsHandler', function () {
   const userId = '123nd3ijdks'
@@ -27,18 +13,18 @@ describe('NotificationsHandler', function () {
 
   beforeEach(function () {
     this.request = sinon.stub().callsArgWith(1)
-    return (this.handler = SandboxedModule.require(modulePath, {
+    this.handler = SandboxedModule.require(modulePath, {
       requires: {
         '@overleaf/settings': {
           apis: { notifications: { url: notificationUrl } },
         },
         request: this.request,
       },
-    }))
+    })
   })
 
   describe('getUserNotifications', function () {
-    it('should get unread notifications', function (done) {
+    it('should get unread notifications', async function () {
       const stubbedNotifications = [{ _id: notificationId, user_id: userId }]
       this.request.callsArgWith(
         1,
@@ -46,51 +32,42 @@ describe('NotificationsHandler', function () {
         { statusCode: 200 },
         stubbedNotifications
       )
-      return this.handler.getUserNotifications(
-        userId,
-        (err, unreadNotifications) => {
-          stubbedNotifications.should.deep.equal(unreadNotifications)
-          const getOpts = {
-            uri: `${notificationUrl}/user/${userId}`,
-            json: true,
-            timeout: 1000,
-            method: 'GET',
-          }
-          this.request.calledWith(getOpts).should.equal(true)
-          return done()
-        }
-      )
+      const unreadNotifications =
+        await this.handler.promises.getUserNotifications(userId)
+      stubbedNotifications.should.deep.equal(unreadNotifications)
+      const getOpts = {
+        uri: `${notificationUrl}/user/${userId}`,
+        json: true,
+        timeout: 1000,
+        method: 'GET',
+      }
+      this.request.calledWith(getOpts).should.equal(true)
     })
 
-    it('should return empty arrays if there are no notifications', function () {
+    it('should return empty arrays if there are no notifications', async function () {
       this.request.callsArgWith(1, null, { statusCode: 200 }, null)
-      return this.handler.getUserNotifications(
-        userId,
-        (err, unreadNotifications) => {
-          return unreadNotifications.length.should.equal(0)
-        }
-      )
+      const unreadNotifications =
+        await this.handler.promises.getUserNotifications(userId)
+      unreadNotifications.length.should.equal(0)
     })
   })
 
   describe('markAsRead', function () {
     beforeEach(function () {
-      return (this.key = 'some key here')
+      this.key = 'some key here'
     })
 
-    it('should send a delete request when a delete has been received to mark a notification', function (done) {
-      return this.handler.markAsReadWithKey(userId, this.key, () => {
-        const opts = {
-          uri: `${notificationUrl}/user/${userId}`,
-          json: {
-            key: this.key,
-          },
-          timeout: 1000,
-          method: 'DELETE',
-        }
-        this.request.calledWith(opts).should.equal(true)
-        return done()
-      })
+    it('should send a delete request when a delete has been received to mark a notification', async function () {
+      await this.handler.promises.markAsReadWithKey(userId, this.key)
+      const opts = {
+        uri: `${notificationUrl}/user/${userId}`,
+        json: {
+          key: this.key,
+        },
+        timeout: 1000,
+        method: 'DELETE',
+      }
+      this.request.calledWith(opts).should.equal(true)
     })
   })
 
@@ -99,30 +76,27 @@ describe('NotificationsHandler', function () {
       this.key = 'some key here'
       this.messageOpts = { value: 12344 }
       this.templateKey = 'renderThisHtml'
-      return (this.expiry = null)
+      this.expiry = null
     })
 
-    it('should post the message over', function (done) {
-      return this.handler.createNotification(
+    it('should post the message over', async function () {
+      await this.handler.promises.createNotification(
         userId,
         this.key,
         this.templateKey,
         this.messageOpts,
-        this.expiry,
-        () => {
-          const args = this.request.args[0][0]
-          args.uri.should.equal(`${notificationUrl}/user/${userId}`)
-          args.timeout.should.equal(1000)
-          const expectedJson = {
-            key: this.key,
-            templateKey: this.templateKey,
-            messageOpts: this.messageOpts,
-            forceCreate: true,
-          }
-          assert.deepEqual(args.json, expectedJson)
-          return done()
-        }
+        this.expiry
       )
+      const args = this.request.args[0][0]
+      args.uri.should.equal(`${notificationUrl}/user/${userId}`)
+      args.timeout.should.equal(1000)
+      const expectedJson = {
+        key: this.key,
+        templateKey: this.templateKey,
+        messageOpts: this.messageOpts,
+        forceCreate: true,
+      }
+      assert.deepEqual(args.json, expectedJson)
     })
 
     describe('when expiry date is supplied', function () {
@@ -130,50 +104,46 @@ describe('NotificationsHandler', function () {
         this.key = 'some key here'
         this.messageOpts = { value: 12344 }
         this.templateKey = 'renderThisHtml'
-        return (this.expiry = new Date())
+        this.expiry = new Date()
       })
 
-      it('should post the message over with expiry field', function (done) {
-        return this.handler.createNotification(
+      it('should post the message over with expiry field', async function () {
+        await this.handler.promises.createNotification(
           userId,
           this.key,
           this.templateKey,
           this.messageOpts,
-          this.expiry,
-          () => {
-            const args = this.request.args[0][0]
-            args.uri.should.equal(`${notificationUrl}/user/${userId}`)
-            args.timeout.should.equal(1000)
-            const expectedJson = {
-              key: this.key,
-              templateKey: this.templateKey,
-              messageOpts: this.messageOpts,
-              expires: this.expiry,
-              forceCreate: true,
-            }
-            assert.deepEqual(args.json, expectedJson)
-            return done()
-          }
+          this.expiry
         )
+
+        const args = this.request.args[0][0]
+        args.uri.should.equal(`${notificationUrl}/user/${userId}`)
+        args.timeout.should.equal(1000)
+        const expectedJson = {
+          key: this.key,
+          templateKey: this.templateKey,
+          messageOpts: this.messageOpts,
+          expires: this.expiry,
+          forceCreate: true,
+        }
+        assert.deepEqual(args.json, expectedJson)
       })
     })
   })
 
   describe('markAsReadByKeyOnly', function () {
     beforeEach(function () {
-      return (this.key = 'some key here')
+      this.key = 'some key here'
     })
 
-    it('should send a delete request when a delete has been received to mark a notification', function (done) {
-      return this.handler.markAsReadByKeyOnly(this.key, () => {
-        const opts = {
-          uri: `${notificationUrl}/key/${this.key}`,
-          timeout: 1000,
-          method: 'DELETE',
-        }
-        this.request.calledWith(opts).should.equal(true)
-        return done()
-      })
+    it('should send a delete request when a delete has been received to mark a notification', async function () {
+      await this.handler.promises.markAsReadByKeyOnly(this.key)
+      const opts = {
+        uri: `${notificationUrl}/key/${this.key}`,
+        timeout: 1000,
+        method: 'DELETE',
+      }
+      this.request.calledWith(opts).should.equal(true)
     })
   })
 })

+ 9 - 8
services/web/test/unit/src/Project/ProjectEntityHandlerTests.js

@@ -430,14 +430,15 @@ describe('ProjectEntityHandler', function () {
       })
     })
 
-    it('should call the callback with the lines, version and rev', function (done) {
-      this.ProjectEntityHandler.getDoc(projectId, docId, doc => {
-        this.DocstoreManager.promises.getDoc
-          .calledWith(projectId, docId)
-          .should.equal(true)
-        expect(doc).to.exist
-        done()
-      })
+    it('should call the callback with the lines, version and rev', async function () {
+      const doc = await this.ProjectEntityHandler.promises.getDoc(
+        projectId,
+        docId
+      )
+      this.DocstoreManager.promises.getDoc
+        .calledWith(projectId, docId)
+        .should.equal(true)
+      expect(doc).to.exist
     })
   })
 

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 295 - 316
services/web/test/unit/src/Project/ProjectEntityUpdateHandlerTests.js


+ 266 - 356
services/web/test/unit/src/Project/ProjectLocatorTests.js

@@ -5,7 +5,6 @@ const sinon = require('sinon')
 const Errors = require('../../../../app/src/Features/Errors/Errors')
 
 const project = { _id: '1234566', rootFolder: [] }
-class Project {}
 const rootDoc = { name: 'rootDoc', _id: 'das239djd' }
 const doc1 = { name: 'otherDoc.txt', _id: 'dsad2ddd' }
 const doc2 = { name: 'docname.txt', _id: 'dsad2ddddd' }
@@ -40,9 +39,6 @@ project.rootDoc_id = rootDoc._id
 
 describe('ProjectLocator', function () {
   beforeEach(function () {
-    Project.findById = (projectId, callback) => {
-      callback(null, project)
-    }
     this.ProjectGetter = {
       getProject: sinon.stub().callsArgWith(2, null, project),
     }
@@ -53,7 +49,6 @@ describe('ProjectLocator', function () {
     }
     this.locator = SandboxedModule.require(modulePath, {
       requires: {
-        '../../models/Project': { Project },
         '../../models/User': { User: this.User },
         './ProjectGetter': this.ProjectGetter,
         './ProjectHelper': this.ProjectHelper,
@@ -62,170 +57,152 @@ describe('ProjectLocator', function () {
   })
 
   describe('finding a doc', function () {
-    it('finds one at the root level', function (done) {
-      this.locator.findElement(
-        { project_id: project._id, element_id: doc2._id, type: 'docs' },
-        (err, foundElement, path, parentFolder) => {
-          if (err != null) {
-            return done(err)
-          }
-          foundElement._id.should.equal(doc2._id)
-          path.fileSystem.should.equal(`/${doc2.name}`)
-          parentFolder._id.should.equal(project.rootFolder[0]._id)
-          path.mongo.should.equal('rootFolder.0.docs.1')
-          done()
+    it('finds one at the root level', async function () {
+      const { element, path, folder } = await this.locator.promises.findElement(
+        {
+          project_id: project._id,
+          element_id: doc2._id,
+          type: 'docs',
         }
       )
+      element._id.should.equal(doc2._id)
+      path.fileSystem.should.equal(`/${doc2.name}`)
+      folder._id.should.equal(project.rootFolder[0]._id)
+      path.mongo.should.equal('rootFolder.0.docs.1')
     })
 
-    it('when it is nested', function (done) {
-      this.locator.findElement(
-        { project_id: project._id, element_id: subSubDoc._id, type: 'doc' },
-        (err, foundElement, path, parentFolder) => {
-          if (err != null) {
-            return done(err)
-          }
-          expect(foundElement._id).to.equal(subSubDoc._id)
-          path.fileSystem.should.equal(
-            `/${subFolder.name}/${secondSubFolder.name}/${subSubDoc.name}`
-          )
-          parentFolder._id.should.equal(secondSubFolder._id)
-          path.mongo.should.equal('rootFolder.0.folders.1.folders.0.docs.0')
-          done()
+    it('when it is nested', async function () {
+      const { element, path, folder } = await this.locator.promises.findElement(
+        {
+          project_id: project._id,
+          element_id: subSubDoc._id,
+          type: 'doc',
         }
       )
+      expect(element._id).to.equal(subSubDoc._id)
+      path.fileSystem.should.equal(
+        `/${subFolder.name}/${secondSubFolder.name}/${subSubDoc.name}`
+      )
+      folder._id.should.equal(secondSubFolder._id)
+      path.mongo.should.equal('rootFolder.0.folders.1.folders.0.docs.0')
     })
 
-    it('should give error if element could not be found', function (done) {
-      this.locator.findElement(
-        { project_id: project._id, element_id: 'ddsd432nj42', type: 'docs' },
-        (err, foundElement, path, parentFolder) => {
-          expect(err).to.be.instanceOf(Errors.NotFoundError)
-          expect(err).to.have.property('message', 'entity not found')
-          done()
-        }
+    it('should give error if element could not be found', async function () {
+      await expect(
+        this.locator.promises.findElement({
+          project_id: project._id,
+          element_id: 'ddsd432nj42',
+          type: 'docs',
+        })
       )
+        .to.eventually.be.rejectedWith(Errors.NotFoundError)
+        .and.eventually.have.property('message', 'entity not found')
     })
   })
 
   describe('finding a folder', function () {
-    it('should return root folder when looking for root folder', function (done) {
-      this.locator.findElement(
-        { project_id: project._id, element_id: rootFolder._id, type: 'folder' },
-        (err, foundElement, path, parentFolder) => {
-          if (err != null) {
-            return done(err)
-          }
-          foundElement._id.should.equal(rootFolder._id)
-          done()
-        }
-      )
-    })
-
-    it('when at root', function (done) {
-      this.locator.findElement(
-        { project_id: project._id, element_id: subFolder._id, type: 'folder' },
-        (err, foundElement, path, parentFolder) => {
-          if (err != null) {
-            return done(err)
-          }
-          foundElement._id.should.equal(subFolder._id)
-          path.fileSystem.should.equal(`/${subFolder.name}`)
-          parentFolder._id.should.equal(rootFolder._id)
-          path.mongo.should.equal('rootFolder.0.folders.1')
-          done()
-        }
-      )
-    })
-
-    it('when deeply nested', function (done) {
-      this.locator.findElement(
+    it('should return root folder when looking for root folder', async function () {
+      const { element: foundElement } = await this.locator.promises.findElement(
         {
           project_id: project._id,
-          element_id: secondSubFolder._id,
+          element_id: rootFolder._id,
           type: 'folder',
-        },
-        (err, foundElement, path, parentFolder) => {
-          if (err != null) {
-            return done(err)
-          }
-          foundElement._id.should.equal(secondSubFolder._id)
-          path.fileSystem.should.equal(
-            `/${subFolder.name}/${secondSubFolder.name}`
-          )
-          parentFolder._id.should.equal(subFolder._id)
-          path.mongo.should.equal('rootFolder.0.folders.1.folders.0')
-          done()
         }
       )
+      foundElement._id.should.equal(rootFolder._id)
+    })
+
+    it('when at root', async function () {
+      const {
+        element: foundElement,
+        path,
+        folder: parentFolder,
+      } = await this.locator.promises.findElement({
+        project_id: project._id,
+        element_id: subFolder._id,
+        type: 'folder',
+      })
+      foundElement._id.should.equal(subFolder._id)
+      path.fileSystem.should.equal(`/${subFolder.name}`)
+      parentFolder._id.should.equal(rootFolder._id)
+      path.mongo.should.equal('rootFolder.0.folders.1')
+    })
+
+    it('when deeply nested', async function () {
+      const {
+        element: foundElement,
+        path,
+        folder: parentFolder,
+      } = await this.locator.promises.findElement({
+        project_id: project._id,
+        element_id: secondSubFolder._id,
+        type: 'folder',
+      })
+
+      foundElement._id.should.equal(secondSubFolder._id)
+      path.fileSystem.should.equal(`/${subFolder.name}/${secondSubFolder.name}`)
+      parentFolder._id.should.equal(subFolder._id)
+      path.mongo.should.equal('rootFolder.0.folders.1.folders.0')
     })
   })
 
   describe('finding a file', function () {
-    it('when at root', function (done) {
-      this.locator.findElement(
-        { project_id: project._id, element_id: file1._id, type: 'fileRefs' },
-        (err, foundElement, path, parentFolder) => {
-          if (err != null) {
-            return done(err)
-          }
-          foundElement._id.should.equal(file1._id)
-          path.fileSystem.should.equal(`/${file1.name}`)
-          parentFolder._id.should.equal(rootFolder._id)
-          path.mongo.should.equal('rootFolder.0.fileRefs.0')
-          done()
-        }
-      )
-    })
-
-    it('when deeply nested', function (done) {
-      this.locator.findElement(
-        {
-          project_id: project._id,
-          element_id: subSubFile._id,
-          type: 'fileRefs',
-        },
-        (err, foundElement, path, parentFolder) => {
-          if (err != null) {
-            return done(err)
-          }
-          foundElement._id.should.equal(subSubFile._id)
-          path.fileSystem.should.equal(
-            `/${subFolder.name}/${secondSubFolder.name}/${subSubFile.name}`
-          )
-          parentFolder._id.should.equal(secondSubFolder._id)
-          path.mongo.should.equal('rootFolder.0.folders.1.folders.0.fileRefs.0')
-          done()
-        }
+    it('when at root', async function () {
+      const {
+        element: foundElement,
+        path,
+        folder: parentFolder,
+      } = await this.locator.promises.findElement({
+        project_id: project._id,
+        element_id: file1._id,
+        type: 'fileRefs',
+      })
+      foundElement._id.should.equal(file1._id)
+      path.fileSystem.should.equal(`/${file1.name}`)
+      parentFolder._id.should.equal(rootFolder._id)
+      path.mongo.should.equal('rootFolder.0.fileRefs.0')
+    })
+
+    it('when deeply nested', async function () {
+      const {
+        element: foundElement,
+        path,
+        folder: parentFolder,
+      } = await this.locator.promises.findElement({
+        project_id: project._id,
+        element_id: subSubFile._id,
+        type: 'fileRefs',
+      })
+      foundElement._id.should.equal(subSubFile._id)
+      path.fileSystem.should.equal(
+        `/${subFolder.name}/${secondSubFolder.name}/${subSubFile.name}`
       )
+      parentFolder._id.should.equal(secondSubFolder._id)
+      path.mongo.should.equal('rootFolder.0.folders.1.folders.0.fileRefs.0')
     })
   })
 
   describe('finding an element with wrong element type', function () {
-    it('should add an s onto the element type', function (done) {
-      this.locator.findElement(
-        { project_id: project._id, element_id: subSubDoc._id, type: 'doc' },
-        (err, foundElement, path, parentFolder) => {
-          if (err != null) {
-            return done(err)
-          }
-          foundElement._id.should.equal(subSubDoc._id)
-          done()
+    it('should add an s onto the element type', async function () {
+      const { element: foundElement } = await this.locator.promises.findElement(
+        {
+          project_id: project._id,
+          element_id: subSubDoc._id,
+          type: 'doc',
         }
       )
+      foundElement._id.should.equal(subSubDoc._id)
     })
 
-    it('should convert file to fileRefs', function (done) {
-      this.locator.findElement(
-        { project_id: project._id, element_id: file1._id, type: 'fileRefs' },
-        (err, foundElement, path, parentFolder) => {
-          if (err != null) {
-            return done(err)
-          }
-          foundElement._id.should.equal(file1._id)
-          done()
+    it('should convert file to fileRefs', async function () {
+      const { element: foundElement } = await this.locator.promises.findElement(
+        {
+          project_id: project._id,
+          element_id: file1._id,
+          type: 'fileRefs',
         }
       )
+      foundElement._id.should.equal(file1._id)
     })
   })
 
@@ -242,233 +219,173 @@ describe('ProjectLocator', function () {
       _id: '1234566',
       rootFolder: [rootFolder2],
     }
-    it('should find doc in project', function (done) {
-      this.locator.findElement(
-        { project: project2, element_id: doc3._id, type: 'docs' },
-        (err, foundElement, path, parentFolder) => {
-          if (err != null) {
-            return done(err)
-          }
-          foundElement._id.should.equal(doc3._id)
-          path.fileSystem.should.equal(`/${doc3.name}`)
-          parentFolder._id.should.equal(project2.rootFolder[0]._id)
-          path.mongo.should.equal('rootFolder.0.docs.0')
-          done()
-        }
-      )
+    it('should find doc in project', async function () {
+      const {
+        element: foundElement,
+        path,
+        folder: parentFolder,
+      } = await this.locator.promises.findElement({
+        project: project2,
+        element_id: doc3._id,
+        type: 'docs',
+      })
+      foundElement._id.should.equal(doc3._id)
+      path.fileSystem.should.equal(`/${doc3.name}`)
+      parentFolder._id.should.equal(project2.rootFolder[0]._id)
+      path.mongo.should.equal('rootFolder.0.docs.0')
     })
   })
 
   describe('finding root doc', function () {
-    it('should return root doc when passed project', function (done) {
-      this.locator.findRootDoc(project, (err, doc) => {
-        if (err != null) {
-          return done(err)
-        }
-        doc._id.should.equal(rootDoc._id)
-        done()
-      })
+    it('should return root doc when passed project', async function () {
+      const { element: doc } = await this.locator.promises.findRootDoc(project)
+      doc._id.should.equal(rootDoc._id)
     })
 
-    it('should return root doc when passed project_id', function (done) {
-      this.locator.findRootDoc(project._id, (err, doc) => {
-        if (err != null) {
-          return done(err)
-        }
-        doc._id.should.equal(rootDoc._id)
-        done()
-      })
+    it('should return root doc when passed project_id', async function () {
+      const { element: doc } = await this.locator.promises.findRootDoc(
+        project._id
+      )
+      doc._id.should.equal(rootDoc._id)
     })
 
-    it('should return null when the project has no rootDoc', function (done) {
+    it('should return null when the project has no rootDoc', async function () {
       project.rootDoc_id = null
-      this.locator.findRootDoc(project, (err, doc) => {
-        if (err != null) {
-          return done(err)
-        }
-        expect(doc).to.equal(null)
-        done()
-      })
+      const { element: rootDoc } =
+        await this.locator.promises.findRootDoc(project)
+      expect(rootDoc).to.equal(null)
     })
 
-    it('should return null when the rootDoc_id no longer exists', function (done) {
+    it('should return null when the rootDoc_id no longer exists', async function () {
       project.rootDoc_id = 'doesntexist'
-      this.locator.findRootDoc(project, (err, doc) => {
-        if (err != null) {
-          return done(err)
-        }
-        expect(doc).to.equal(null)
-        done()
-      })
+      const { element: rootDoc } =
+        await this.locator.promises.findRootDoc(project)
+      expect(rootDoc).to.equal(null)
     })
   })
 
   describe('findElementByPath', function () {
-    it('should take a doc path and return the element for a root level document', function (done) {
+    it('should take a doc path and return the element for a root level document', async function () {
       const path = `${doc1.name}`
-      this.locator.findElementByPath(
-        { project, path },
-        (err, element, type, folder) => {
-          if (err != null) {
-            return done(err)
-          }
-          element.should.deep.equal(doc1)
-          expect(type).to.equal('doc')
-          expect(folder).to.equal(rootFolder)
-          done()
-        }
-      )
+      const { element, type, folder } =
+        await this.locator.promises.findElementByPath({
+          project,
+          path,
+        })
+      element.should.deep.equal(doc1)
+      expect(type).to.equal('doc')
+      expect(folder).to.equal(rootFolder)
     })
 
-    it('should take a doc path and return the element for a root level document with a starting slash', function (done) {
+    it('should take a doc path and return the element for a root level document with a starting slash', async function () {
       const path = `/${doc1.name}`
-      this.locator.findElementByPath(
-        { project, path },
-        (err, element, type, folder) => {
-          if (err != null) {
-            return done(err)
-          }
-          element.should.deep.equal(doc1)
-          expect(type).to.equal('doc')
-          expect(folder).to.equal(rootFolder)
-          done()
-        }
-      )
+      const { element, type, folder } =
+        await this.locator.promises.findElementByPath({
+          project,
+          path,
+        })
+      element.should.deep.equal(doc1)
+      expect(type).to.equal('doc')
+      expect(folder).to.equal(rootFolder)
     })
 
-    it('should take a doc path and return the element for a nested document', function (done) {
+    it('should take a doc path and return the element for a nested document', async function () {
       const path = `${subFolder.name}/${secondSubFolder.name}/${subSubDoc.name}`
-      this.locator.findElementByPath(
-        { project, path },
-        (err, element, type, folder) => {
-          if (err != null) {
-            return done(err)
-          }
-          element.should.deep.equal(subSubDoc)
-          expect(type).to.equal('doc')
-          expect(folder).to.equal(secondSubFolder)
-          done()
-        }
-      )
+      const { element, type, folder } =
+        await this.locator.promises.findElementByPath({
+          project,
+          path,
+        })
+      element.should.deep.equal(subSubDoc)
+      expect(type).to.equal('doc')
+      expect(folder).to.equal(secondSubFolder)
     })
 
-    it('should take a file path and return the element for a root level document', function (done) {
+    it('should take a file path and return the element for a root level document', async function () {
       const path = `${file1.name}`
-      this.locator.findElementByPath(
-        { project, path },
-        (err, element, type, folder) => {
-          if (err != null) {
-            return done(err)
-          }
-          element.should.deep.equal(file1)
-          expect(type).to.equal('file')
-          expect(folder).to.equal(rootFolder)
-          done()
-        }
-      )
+      const { element, type, folder } =
+        await this.locator.promises.findElementByPath({
+          project,
+          path,
+        })
+      element.should.deep.equal(file1)
+      expect(type).to.equal('file')
+      expect(folder).to.equal(rootFolder)
     })
 
-    it('should take a file path and return the element for a nested document', function (done) {
+    it('should take a file path and return the element for a nested document', async function () {
       const path = `${subFolder.name}/${secondSubFolder.name}/${subSubFile.name}`
-      this.locator.findElementByPath(
-        { project, path },
-        (err, element, type, folder) => {
-          if (err != null) {
-            return done(err)
-          }
-          element.should.deep.equal(subSubFile)
-          expect(type).to.equal('file')
-          expect(folder).to.equal(secondSubFolder)
-          done()
-        }
-      )
+      const { element, type, folder } =
+        await this.locator.promises.findElementByPath({
+          project,
+          path,
+        })
+      element.should.deep.equal(subSubFile)
+      expect(type).to.equal('file')
+      expect(folder).to.equal(secondSubFolder)
     })
 
-    it('should take a file path and return the element for a nested document case insenstive', function (done) {
+    it('should take a file path and return the element for a nested document case insenstive', async function () {
       const path = `${subFolder.name.toUpperCase()}/${secondSubFolder.name.toUpperCase()}/${subSubFile.name.toUpperCase()}`
-      this.locator.findElementByPath(
-        { project, path },
-        (err, element, type, folder) => {
-          if (err != null) {
-            return done(err)
-          }
-          element.should.deep.equal(subSubFile)
-          expect(type).to.equal('file')
-          expect(folder).to.equal(secondSubFolder)
-          done()
-        }
-      )
+      const { element, type, folder } =
+        await this.locator.promises.findElementByPath({
+          project,
+          path,
+        })
+      element.should.deep.equal(subSubFile)
+      expect(type).to.equal('file')
+      expect(folder).to.equal(secondSubFolder)
     })
 
-    it('should not return elements with a case-insensitive match when exactCaseMatch is true', function (done) {
+    it('should not return elements with a case-insensitive match when exactCaseMatch is true', async function () {
       const path = `${subFolder.name.toUpperCase()}/${secondSubFolder.name.toUpperCase()}/${subSubFile.name.toUpperCase()}`
-      this.locator.findElementByPath(
-        { project, path, exactCaseMatch: true },
-        (err, element, type, folder) => {
-          err.should.not.equal(undefined)
-          expect(element).to.be.undefined
-          expect(type).to.be.undefined
-          done()
-        }
-      )
+      await expect(
+        this.locator.promises.findElementByPath({
+          project,
+          path,
+          exactCaseMatch: true,
+        })
+      ).to.eventually.be.rejected
     })
 
-    it('should take a file path and return the element for a nested folder', function (done) {
+    it('should take a file path and return the element for a nested folder', async function () {
       const path = `${subFolder.name}/${secondSubFolder.name}`
-      this.locator.findElementByPath(
-        { project, path },
-        (err, element, type, folder) => {
-          if (err != null) {
-            return done(err)
-          }
-          element.should.deep.equal(secondSubFolder)
-          expect(type).to.equal('folder')
-          expect(folder).to.equal(subFolder)
-          done()
-        }
-      )
+      const { element, type, folder } =
+        await this.locator.promises.findElementByPath({
+          project,
+          path,
+        })
+      element.should.deep.equal(secondSubFolder)
+      expect(type).to.equal('folder')
+      expect(folder).to.equal(subFolder)
     })
 
-    it('should take a file path and return the root folder', function (done) {
+    it('should take a file path and return the root folder', async function () {
       const path = '/'
-      this.locator.findElementByPath(
-        { project, path },
-        (err, element, type, folder) => {
-          if (err != null) {
-            return done(err)
-          }
-          element.should.deep.equal(rootFolder)
-          expect(type).to.equal('folder')
-          expect(folder).to.equal(null)
-          done()
-        }
-      )
+      const { element, type, folder } =
+        await this.locator.promises.findElementByPath({
+          project,
+          path,
+        })
+      element.should.deep.equal(rootFolder)
+      expect(type).to.equal('folder')
+      expect(folder).to.equal(null)
     })
 
-    it('should return an error if the file can not be found inside know folder', function (done) {
+    it('should return an error if the file can not be found inside know folder', async function () {
       const path = `${subFolder.name}/${secondSubFolder.name}/exist.txt`
-      this.locator.findElementByPath(
-        { project, path },
-        (err, element, type) => {
-          err.should.not.equal(undefined)
-          expect(element).to.be.undefined
-          expect(type).to.be.undefined
-          done()
-        }
-      )
+      await expect(this.locator.promises.findElementByPath({ project, path }))
+        .to.eventually.be.rejected
     })
 
-    it('should return an error if the file can not be found inside unknown folder', function (done) {
+    it('should return an error if the file can not be found inside unknown folder', async function () {
       const path = 'this/does/not/exist.txt'
-      this.locator.findElementByPath(
-        { project, path },
-        (err, element, type) => {
-          err.should.not.equal(undefined)
-          expect(element).to.be.undefined
-          expect(type).to.be.undefined
-          done()
-        }
-      )
+      await expect(
+        this.locator.promises.findElementByPath({
+          project,
+          path,
+        })
+      ).to.eventually.be.rejected
     })
 
     describe('where duplicate folder exists', function () {
@@ -498,18 +415,20 @@ describe('ProjectLocator', function () {
         }
       })
 
-      it('should not call the callback more than once', function (done) {
+      it('should not call the callback more than once', async function () {
         const path = `${this.duplicateFolder.name}/${this.doc.name}`
-        this.locator.findElementByPath({ project: this.project, path }, () =>
-          done()
-        )
+        await this.locator.promises.findElementByPath({
+          project: this.project,
+          path,
+        })
       }) // mocha will throw exception if done called multiple times
 
-      it('should not call the callback more than once when the path is longer than 1 level below the duplicate level', function (done) {
+      it('should not call the callback more than once when the path is longer than 1 level below the duplicate level', async function () {
         const path = `${this.duplicateFolder.name}/1/main.tex`
-        this.locator.findElementByPath({ project: this.project, path }, () =>
-          done()
-        )
+        await this.locator.promises.findElementByPath({
+          project: this.project,
+          path,
+        })
       })
     }) // mocha will throw exception if done called multiple times
 
@@ -526,18 +445,13 @@ describe('ProjectLocator', function () {
         }
       })
 
-      it('should not crash with a null', function (done) {
+      it('should not crash with a null', async function () {
         const path = '/other.tex'
-        this.locator.findElementByPath(
-          { project: this.project, path },
-          (err, element) => {
-            if (err != null) {
-              return done(err)
-            }
-            element.name.should.equal('other.tex')
-            done()
-          }
-        )
+        const { element } = await this.locator.promises.findElementByPath({
+          project: this.project,
+          path,
+        })
+        element.name.should.equal('other.tex')
       })
     })
 
@@ -546,35 +460,31 @@ describe('ProjectLocator', function () {
         this.ProjectGetter = { getProject: sinon.stub().callsArg(2) }
       })
 
-      it('should not crash with a null', function (done) {
+      it('should not crash with a null', async function () {
         const path = '/other.tex'
-        this.locator.findElementByPath(
-          { project_id: project._id, path },
-          (err, element) => {
-            expect(err).to.exist
-            done()
-          }
-        )
+        await expect(
+          this.locator.promises.findElementByPath({
+            project_id: project._id,
+            path,
+          })
+        ).to.be.rejected
       })
     })
 
     describe('with a project_id', function () {
-      it('should take a doc path and return the element for a root level document', function (done) {
+      it('should take a doc path and return the element for a root level document', async function () {
         const path = `${doc1.name}`
-        this.locator.findElementByPath(
-          { project_id: project._id, path },
-          (err, element, type) => {
-            if (err != null) {
-              return done(err)
-            }
-            this.ProjectGetter.getProject
-              .calledWith(project._id, { rootFolder: true, rootDoc_id: true })
-              .should.equal(true)
-            element.should.deep.equal(doc1)
-            expect(type).to.equal('doc')
-            done()
+        const { element, type } = await this.locator.promises.findElementByPath(
+          {
+            project_id: project._id,
+            path,
           }
         )
+        this.ProjectGetter.getProject
+          .calledWith(project._id, { rootFolder: true, rootDoc_id: true })
+          .should.equal(true)
+        element.should.deep.equal(doc1)
+        expect(type).to.equal('doc')
       })
     })
   })

+ 154 - 213
services/web/test/unit/src/Project/ProjectRootDocManagerTests.js

@@ -1,16 +1,3 @@
-/* eslint-disable
-    max-len,
-    no-return-assign,
-    no-unused-vars,
-    no-useless-escape,
-*/
-// TODO: This file was created by bulk-decaffeinate.
-// Fix any style issues and re-enable lint.
-/*
- * decaffeinate suggestions:
- * DS102: Remove unnecessary code created because of implicit returns
- * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
- */
 const { expect } = require('chai')
 const { ObjectId } = require('mongodb-legacy')
 const sinon = require('sinon')
@@ -33,16 +20,13 @@ describe('ProjectRootDocManager', function () {
     this.sl_req_id = 'sl-req-id-123'
     this.callback = sinon.stub()
     this.globbyFiles = ['a.tex', 'b.tex', 'main.tex']
-    this.globby = sinon.stub().returns(
-      new Promise(resolve => {
-        return resolve(this.globbyFiles)
-      })
-    )
+    this.globby = sinon.stub().resolves(this.globbyFiles)
+
     this.fs = {
       readFile: sinon.stub().callsArgWith(2, new Error('file not found')),
       stat: sinon.stub().callsArgWith(1, null, { size: 100 }),
     }
-    return (this.ProjectRootDocManager = SandboxedModule.require(modulePath, {
+    this.ProjectRootDocManager = SandboxedModule.require(modulePath, {
       requires: {
         './ProjectEntityHandler': (this.ProjectEntityHandler = {}),
         './ProjectEntityUpdateHandler': (this.ProjectEntityUpdateHandler = {}),
@@ -56,7 +40,7 @@ describe('ProjectRootDocManager', function () {
         globby: this.globby,
         fs: this.fs,
       },
-    }))
+    })
   })
 
   describe('setRootDocAutomatically', function () {
@@ -67,7 +51,7 @@ describe('ProjectRootDocManager', function () {
         .returns(true)
     })
     describe('when there is a suitable root doc', function () {
-      beforeEach(function (done) {
+      beforeEach(async function () {
         this.docs = {
           '/chapter1.tex': {
             _id: this.docId1,
@@ -98,27 +82,26 @@ describe('ProjectRootDocManager', function () {
         this.ProjectEntityHandler.getAllDocs = sinon
           .stub()
           .callsArgWith(1, null, this.docs)
-        this.ProjectRootDocManager.setRootDocAutomatically(
-          this.project_id,
-          done
+        await this.ProjectRootDocManager.promises.setRootDocAutomatically(
+          this.project_id
         )
       })
 
       it('should check the docs of the project', function () {
-        return this.ProjectEntityHandler.getAllDocs
+        this.ProjectEntityHandler.getAllDocs
           .calledWith(this.project_id)
           .should.equal(true)
       })
 
       it('should set the root doc to the doc containing a documentclass', function () {
-        return this.ProjectEntityUpdateHandler.setRootDoc
+        this.ProjectEntityUpdateHandler.setRootDoc
           .calledWith(this.project_id, this.docId2)
           .should.equal(true)
       })
     })
 
     describe('when the root doc is an Rtex file', function () {
-      beforeEach(function (done) {
+      beforeEach(async function () {
         this.docs = {
           '/chapter1.tex': {
             _id: this.docId1,
@@ -132,21 +115,20 @@ describe('ProjectRootDocManager', function () {
         this.ProjectEntityHandler.getAllDocs = sinon
           .stub()
           .callsArgWith(1, null, this.docs)
-        return this.ProjectRootDocManager.setRootDocAutomatically(
-          this.project_id,
-          done
+        await this.ProjectRootDocManager.promises.setRootDocAutomatically(
+          this.project_id
         )
       })
 
       it('should set the root doc to the doc containing a documentclass', function () {
-        return this.ProjectEntityUpdateHandler.setRootDoc
+        this.ProjectEntityUpdateHandler.setRootDoc
           .calledWith(this.project_id, this.docId2)
           .should.equal(true)
       })
     })
 
     describe('when there is no suitable root doc', function () {
-      beforeEach(function (done) {
+      beforeEach(async function () {
         this.docs = {
           '/chapter1.tex': {
             _id: this.docId1,
@@ -160,16 +142,13 @@ describe('ProjectRootDocManager', function () {
         this.ProjectEntityHandler.getAllDocs = sinon
           .stub()
           .callsArgWith(1, null, this.docs)
-        return this.ProjectRootDocManager.setRootDocAutomatically(
-          this.project_id,
-          done
+        await this.ProjectRootDocManager.promises.setRootDocAutomatically(
+          this.project_id
         )
       })
 
       it('should not set the root doc to the doc containing a documentclass', function () {
-        return this.ProjectEntityUpdateHandler.setRootDoc.called.should.equal(
-          false
-        )
+        this.ProjectEntityUpdateHandler.setRootDoc.called.should.equal(false)
       })
     })
   })
@@ -191,13 +170,13 @@ describe('ProjectRootDocManager', function () {
       this.fs.readFile
         .withArgs('/foo/a/a.tex')
         .callsArgWith(2, null, 'Potato? Potahto. Potootee!')
-      return (this.documentclassContent = '% test\n\\documentclass\n% test')
+      this.documentclassContent = '% test\n\\documentclass\n% test'
     })
 
     describe('when there is a file in a subfolder', function () {
       beforeEach(function () {
         // have to splice globbyFiles weirdly because of the way the stubbed globby method handles references
-        return this.globbyFiles.splice(
+        this.globbyFiles.splice(
           0,
           this.globbyFiles.length,
           'c.tex',
@@ -207,70 +186,56 @@ describe('ProjectRootDocManager', function () {
         )
       })
 
-      it('processes the root folder files first, and then the subfolder, in alphabetical order', function (done) {
-        return this.ProjectRootDocManager.findRootDocFileFromDirectory(
-          '/foo',
-          (error, path) => {
-            expect(error).not.to.exist
-            expect(path).to.equal('a.tex')
-            sinon.assert.callOrder(
-              this.fs.readFile.withArgs('/foo/a.tex'),
-              this.fs.readFile.withArgs('/foo/b.tex'),
-              this.fs.readFile.withArgs('/foo/c.tex'),
-              this.fs.readFile.withArgs('/foo/a/a.tex')
-            )
-            return done()
-          }
+      it('processes the root folder files first, and then the subfolder, in alphabetical order', async function () {
+        const { path } =
+          await this.ProjectRootDocManager.promises.findRootDocFileFromDirectory(
+            '/foo'
+          )
+        expect(path).to.equal('a.tex')
+        sinon.assert.callOrder(
+          this.fs.readFile.withArgs('/foo/a.tex'),
+          this.fs.readFile.withArgs('/foo/b.tex'),
+          this.fs.readFile.withArgs('/foo/c.tex'),
+          this.fs.readFile.withArgs('/foo/a/a.tex')
         )
       })
 
-      it('processes smaller files first', function (done) {
+      it('processes smaller files first', async function () {
         this.fs.stat.withArgs('/foo/c.tex').callsArgWith(1, null, { size: 1 })
-        return this.ProjectRootDocManager.findRootDocFileFromDirectory(
-          '/foo',
-          (error, path) => {
-            expect(error).not.to.exist
-            expect(path).to.equal('c.tex')
-            sinon.assert.callOrder(
-              this.fs.readFile.withArgs('/foo/c.tex'),
-              this.fs.readFile.withArgs('/foo/a.tex'),
-              this.fs.readFile.withArgs('/foo/b.tex'),
-              this.fs.readFile.withArgs('/foo/a/a.tex')
-            )
-            return done()
-          }
+        const { path } =
+          await this.ProjectRootDocManager.promises.findRootDocFileFromDirectory(
+            '/foo'
+          )
+        expect(path).to.equal('c.tex')
+        sinon.assert.callOrder(
+          this.fs.readFile.withArgs('/foo/c.tex'),
+          this.fs.readFile.withArgs('/foo/a.tex'),
+          this.fs.readFile.withArgs('/foo/b.tex'),
+          this.fs.readFile.withArgs('/foo/a/a.tex')
         )
       })
     })
 
     describe('when main.tex contains a documentclass', function () {
       beforeEach(function () {
-        return this.fs.readFile
+        this.fs.readFile
           .withArgs('/foo/main.tex')
           .callsArgWith(2, null, this.documentclassContent)
       })
 
-      it('returns main.tex', function (done) {
-        return this.ProjectRootDocManager.findRootDocFileFromDirectory(
-          '/foo',
-          (error, path, content) => {
-            expect(error).not.to.exist
-            expect(path).to.equal('main.tex')
-            expect(content).to.equal(this.documentclassContent)
-            return done()
-          }
-        )
+      it('returns main.tex', async function () {
+        const { path, content } =
+          await this.ProjectRootDocManager.promises.findRootDocFileFromDirectory(
+            '/foo'
+          )
+        expect(path).to.equal('main.tex')
+        expect(content).to.equal(this.documentclassContent)
       })
 
-      it('processes main.text first and stops processing when it finds the content', function (done) {
-        return this.ProjectRootDocManager.findRootDocFileFromDirectory(
-          '/foo',
-          () => {
-            expect(this.fs.readFile).to.be.calledWith('/foo/main.tex')
-            expect(this.fs.readFile).not.to.be.calledWith('/foo/a.tex')
-            return done()
-          }
-        )
+      it('processes main.text first and stops processing when it finds the content', async function () {
+        await this.ProjectRootDocManager.findRootDocFileFromDirectory('/foo')
+        expect(this.fs.readFile).to.be.calledWith('/foo/main.tex')
+        expect(this.fs.readFile).not.to.be.calledWith('/foo/a.tex')
       })
     })
 
@@ -284,7 +249,7 @@ describe('ProjectRootDocManager', function () {
           .callsArgWith(2, null, 'foo')
       })
 
-      it('returns the first .tex file from the root folder', function (done) {
+      it('returns the first .tex file from the root folder', async function () {
         this.globbyFiles.splice(
           0,
           this.globbyFiles.length,
@@ -293,18 +258,15 @@ describe('ProjectRootDocManager', function () {
           'nested/chapter1a.tex'
         )
 
-        this.ProjectRootDocManager.findRootDocFileFromDirectory(
-          '/foo',
-          (error, path, content) => {
-            expect(error).not.to.exist
-            expect(path).to.equal('a.tex')
-            expect(content).to.equal('foo')
-            return done()
-          }
-        )
+        const { path, content } =
+          await this.ProjectRootDocManager.promises.findRootDocFileFromDirectory(
+            '/foo'
+          )
+        expect(path).to.equal('a.tex')
+        expect(content).to.equal('foo')
       })
 
-      it('returns main.tex file from the root folder', function (done) {
+      it('returns main.tex file from the root folder', async function () {
         this.globbyFiles.splice(
           0,
           this.globbyFiles.length,
@@ -314,227 +276,206 @@ describe('ProjectRootDocManager', function () {
           'nested/chapter1a.tex'
         )
 
-        this.ProjectRootDocManager.findRootDocFileFromDirectory(
-          '/foo',
-          (error, path, content) => {
-            expect(error).not.to.exist
-            expect(path).to.equal('main.tex')
-            expect(content).to.equal('foo')
-            return done()
-          }
-        )
+        const { path, content } =
+          await this.ProjectRootDocManager.promises.findRootDocFileFromDirectory(
+            '/foo'
+          )
+        expect(path).to.equal('main.tex')
+        expect(content).to.equal('foo')
       })
     })
 
     describe('when a.tex contains a documentclass', function () {
       beforeEach(function () {
-        return this.fs.readFile
+        this.fs.readFile
           .withArgs('/foo/a.tex')
           .callsArgWith(2, null, this.documentclassContent)
       })
 
-      it('returns a.tex', function (done) {
-        return this.ProjectRootDocManager.findRootDocFileFromDirectory(
-          '/foo',
-          (error, path, content) => {
-            expect(error).not.to.exist
-            expect(path).to.equal('a.tex')
-            expect(content).to.equal(this.documentclassContent)
-            return done()
-          }
-        )
+      it('returns a.tex', async function () {
+        const { path, content } =
+          await this.ProjectRootDocManager.promises.findRootDocFileFromDirectory(
+            '/foo'
+          )
+        expect(path).to.equal('a.tex')
+        expect(content).to.equal(this.documentclassContent)
       })
 
-      it('processes main.text first and stops processing when it finds the content', function (done) {
-        return this.ProjectRootDocManager.findRootDocFileFromDirectory(
-          '/foo',
-          () => {
-            expect(this.fs.readFile).to.be.calledWith('/foo/main.tex')
-            expect(this.fs.readFile).to.be.calledWith('/foo/a.tex')
-            expect(this.fs.readFile).not.to.be.calledWith('/foo/b.tex')
-            return done()
-          }
+      it('processes main.text first and stops processing when it finds the content', async function () {
+        await this.ProjectRootDocManager.promises.findRootDocFileFromDirectory(
+          '/foo'
         )
+        expect(this.fs.readFile).to.be.calledWith('/foo/main.tex')
+        expect(this.fs.readFile).to.be.calledWith('/foo/a.tex')
+        expect(this.fs.readFile).not.to.be.calledWith('/foo/b.tex')
       })
     })
 
     describe('when there is no documentclass', function () {
-      it('returns with no error', function (done) {
-        return this.ProjectRootDocManager.findRootDocFileFromDirectory(
-          '/foo',
-          error => {
-            expect(error).not.to.exist
-            return done()
-          }
+      it('returns with no error', async function () {
+        await this.ProjectRootDocManager.promises.findRootDocFileFromDirectory(
+          '/foo'
         )
       })
 
-      it('processes all the files', function (done) {
-        return this.ProjectRootDocManager.findRootDocFileFromDirectory(
-          '/foo',
-          () => {
-            expect(this.fs.readFile).to.be.calledWith('/foo/main.tex')
-            expect(this.fs.readFile).to.be.calledWith('/foo/a.tex')
-            expect(this.fs.readFile).to.be.calledWith('/foo/b.tex')
-            return done()
-          }
+      it('processes all the files', async function () {
+        await this.ProjectRootDocManager.promises.findRootDocFileFromDirectory(
+          '/foo'
         )
+        expect(this.fs.readFile).to.be.calledWith('/foo/main.tex')
+        expect(this.fs.readFile).to.be.calledWith('/foo/a.tex')
+        expect(this.fs.readFile).to.be.calledWith('/foo/b.tex')
       })
     })
 
     describe('when there is an error reading a file', function () {
       beforeEach(function () {
-        return this.fs.readFile
+        this.fs.readFile
           .withArgs('/foo/a.tex')
           .callsArgWith(2, new Error('something went wrong'))
       })
 
-      it('returns an error', function (done) {
-        return this.ProjectRootDocManager.findRootDocFileFromDirectory(
-          '/foo',
-          (error, path, content) => {
-            expect(error).to.exist
-            expect(path).not.to.exist
-            expect(content).not.to.exist
-            return done()
-          }
-        )
+      it('returns an error', async function () {
+        let error
+
+        try {
+          await this.ProjectRootDocManager.promises.findRootDocFileFromDirectory(
+            '/foo'
+          )
+        } catch (err) {
+          error = err
+        }
+
+        expect(error).to.exist
       })
     })
   })
 
   describe('setRootDocFromName', function () {
     describe('when there is a suitable root doc', function () {
-      beforeEach(function (done) {
+      beforeEach(async function () {
         this.ProjectEntityHandler.getAllDocPathsFromProjectById = sinon
           .stub()
           .callsArgWith(1, null, this.docPaths)
         this.ProjectEntityUpdateHandler.setRootDoc = sinon
           .stub()
           .callsArgWith(2)
-        return this.ProjectRootDocManager.setRootDocFromName(
+        await this.ProjectRootDocManager.promises.setRootDocFromName(
           this.project_id,
-          '/main.tex',
-          done
+          '/main.tex'
         )
       })
 
       it('should check the docs of the project', function () {
-        return this.ProjectEntityHandler.getAllDocPathsFromProjectById
+        this.ProjectEntityHandler.getAllDocPathsFromProjectById
           .calledWith(this.project_id)
           .should.equal(true)
       })
 
       it('should set the root doc to main.tex', function () {
-        return this.ProjectEntityUpdateHandler.setRootDoc
+        this.ProjectEntityUpdateHandler.setRootDoc
           .calledWith(this.project_id, this.docId2.toString())
           .should.equal(true)
       })
     })
 
     describe('when there is a suitable root doc but the leading slash is missing', function () {
-      beforeEach(function (done) {
+      beforeEach(async function () {
         this.ProjectEntityHandler.getAllDocPathsFromProjectById = sinon
           .stub()
           .callsArgWith(1, null, this.docPaths)
         this.ProjectEntityUpdateHandler.setRootDoc = sinon
           .stub()
           .callsArgWith(2)
-        return this.ProjectRootDocManager.setRootDocFromName(
+        await this.ProjectRootDocManager.promises.setRootDocFromName(
           this.project_id,
-          'main.tex',
-          done
+          'main.tex'
         )
       })
 
       it('should check the docs of the project', function () {
-        return this.ProjectEntityHandler.getAllDocPathsFromProjectById
+        this.ProjectEntityHandler.getAllDocPathsFromProjectById
           .calledWith(this.project_id)
           .should.equal(true)
       })
 
       it('should set the root doc to main.tex', function () {
-        return this.ProjectEntityUpdateHandler.setRootDoc
+        this.ProjectEntityUpdateHandler.setRootDoc
           .calledWith(this.project_id, this.docId2.toString())
           .should.equal(true)
       })
     })
 
     describe('when there is a suitable root doc with a basename match', function () {
-      beforeEach(function (done) {
+      beforeEach(async function () {
         this.ProjectEntityHandler.getAllDocPathsFromProjectById = sinon
           .stub()
           .callsArgWith(1, null, this.docPaths)
         this.ProjectEntityUpdateHandler.setRootDoc = sinon
           .stub()
           .callsArgWith(2)
-        return this.ProjectRootDocManager.setRootDocFromName(
+        await this.ProjectRootDocManager.promises.setRootDocFromName(
           this.project_id,
-          'chapter1a.tex',
-          done
+          'chapter1a.tex'
         )
       })
 
       it('should check the docs of the project', function () {
-        return this.ProjectEntityHandler.getAllDocPathsFromProjectById
+        this.ProjectEntityHandler.getAllDocPathsFromProjectById
           .calledWith(this.project_id)
           .should.equal(true)
       })
 
       it('should set the root doc using the basename', function () {
-        return this.ProjectEntityUpdateHandler.setRootDoc
+        this.ProjectEntityUpdateHandler.setRootDoc
           .calledWith(this.project_id, this.docId3.toString())
           .should.equal(true)
       })
     })
 
     describe('when there is a suitable root doc but the filename is in quotes', function () {
-      beforeEach(function (done) {
+      beforeEach(async function () {
         this.ProjectEntityHandler.getAllDocPathsFromProjectById = sinon
           .stub()
           .callsArgWith(1, null, this.docPaths)
         this.ProjectEntityUpdateHandler.setRootDoc = sinon
           .stub()
           .callsArgWith(2)
-        return this.ProjectRootDocManager.setRootDocFromName(
+        await this.ProjectRootDocManager.promises.setRootDocFromName(
           this.project_id,
-          "'main.tex'",
-          done
+          "'main.tex'"
         )
       })
 
       it('should check the docs of the project', function () {
-        return this.ProjectEntityHandler.getAllDocPathsFromProjectById
+        this.ProjectEntityHandler.getAllDocPathsFromProjectById
           .calledWith(this.project_id)
           .should.equal(true)
       })
 
       it('should set the root doc to main.tex', function () {
-        return this.ProjectEntityUpdateHandler.setRootDoc
+        this.ProjectEntityUpdateHandler.setRootDoc
           .calledWith(this.project_id, this.docId2.toString())
           .should.equal(true)
       })
     })
 
     describe('when there is no suitable root doc', function () {
-      beforeEach(function (done) {
+      beforeEach(async function () {
         this.ProjectEntityHandler.getAllDocPathsFromProjectById = sinon
           .stub()
           .callsArgWith(1, null, this.docPaths)
         this.ProjectEntityUpdateHandler.setRootDoc = sinon
           .stub()
           .callsArgWith(2)
-        return this.ProjectRootDocManager.setRootDocFromName(
+        await this.ProjectRootDocManager.promises.setRootDocFromName(
           this.project_id,
-          'other.tex',
-          done
+          'other.tex'
         )
       })
 
       it('should not set the root doc', function () {
-        return this.ProjectEntityUpdateHandler.setRootDoc.called.should.equal(
-          false
-        )
+        this.ProjectEntityUpdateHandler.setRootDoc.called.should.equal(false)
       })
     })
   })
@@ -545,73 +486,73 @@ describe('ProjectRootDocManager', function () {
       this.ProjectGetter.getProject = sinon
         .stub()
         .callsArgWith(2, null, this.project)
-      return (this.ProjectRootDocManager.setRootDocAutomatically = sinon
+      this.ProjectRootDocManager.setRootDocAutomatically = sinon
         .stub()
-        .callsArgWith(1, null))
+        .callsArgWith(1, null)
     })
 
     describe('when the root doc is set', function () {
       beforeEach(function () {
         this.project.rootDoc_id = this.docId2
-        return this.ProjectRootDocManager.ensureRootDocumentIsSet(
+        this.ProjectRootDocManager.ensureRootDocumentIsSet(
           this.project_id,
           this.callback
         )
       })
 
       it('should find the project fetching only the rootDoc_id field', function () {
-        return this.ProjectGetter.getProject
+        this.ProjectGetter.getProject
           .calledWith(this.project_id, { rootDoc_id: 1 })
           .should.equal(true)
       })
 
       it('should not try to update the project rootDoc_id', function () {
-        return this.ProjectRootDocManager.setRootDocAutomatically.called.should.equal(
+        this.ProjectRootDocManager.setRootDocAutomatically.called.should.equal(
           false
         )
       })
 
       it('should call the callback', function () {
-        return this.callback.called.should.equal(true)
+        this.callback.called.should.equal(true)
       })
     })
 
     describe('when the root doc is not set', function () {
       beforeEach(function () {
-        return this.ProjectRootDocManager.ensureRootDocumentIsSet(
+        this.ProjectRootDocManager.ensureRootDocumentIsSet(
           this.project_id,
           this.callback
         )
       })
 
       it('should find the project with only the rootDoc_id field', function () {
-        return this.ProjectGetter.getProject
+        this.ProjectGetter.getProject
           .calledWith(this.project_id, { rootDoc_id: 1 })
           .should.equal(true)
       })
 
       it('should update the project rootDoc_id', function () {
-        return this.ProjectRootDocManager.setRootDocAutomatically
+        this.ProjectRootDocManager.setRootDocAutomatically
           .calledWith(this.project_id)
           .should.equal(true)
       })
 
       it('should call the callback', function () {
-        return this.callback.called.should.equal(true)
+        this.callback.called.should.equal(true)
       })
     })
 
     describe('when the project does not exist', function () {
       beforeEach(function () {
         this.ProjectGetter.getProject = sinon.stub().callsArgWith(2, null, null)
-        return this.ProjectRootDocManager.ensureRootDocumentIsSet(
+        this.ProjectRootDocManager.ensureRootDocumentIsSet(
           this.project_id,
           this.callback
         )
       })
 
       it('should call the callback with an error', function () {
-        return this.callback
+        this.callback
           .calledWith(
             sinon.match
               .instanceOf(Error)
@@ -645,26 +586,26 @@ describe('ProjectRootDocManager', function () {
           this.ProjectEntityHandler.getDocPathFromProjectByDocId = sinon
             .stub()
             .callsArgWith(2, null, this.docPaths[this.docId2])
-          return this.ProjectRootDocManager.ensureRootDocumentIsValid(
+          this.ProjectRootDocManager.ensureRootDocumentIsValid(
             this.project_id,
             this.callback
           )
         })
 
         it('should find the project without doc lines', function () {
-          return this.ProjectGetter.getProjectWithoutDocLines
+          this.ProjectGetter.getProjectWithoutDocLines
             .calledWith(this.project_id)
             .should.equal(true)
         })
 
         it('should not try to update the project rootDoc_id', function () {
-          return this.ProjectRootDocManager.setRootDocAutomatically.called.should.equal(
+          this.ProjectRootDocManager.setRootDocAutomatically.called.should.equal(
             false
           )
         })
 
         it('should call the callback', function () {
-          return this.callback.called.should.equal(true)
+          this.callback.called.should.equal(true)
         })
       })
 
@@ -674,58 +615,58 @@ describe('ProjectRootDocManager', function () {
           this.ProjectEntityHandler.getDocPathFromProjectByDocId = sinon
             .stub()
             .callsArgWith(2, null, null)
-          return this.ProjectRootDocManager.ensureRootDocumentIsValid(
+          this.ProjectRootDocManager.ensureRootDocumentIsValid(
             this.project_id,
             this.callback
           )
         })
 
         it('should find the project without doc lines', function () {
-          return this.ProjectGetter.getProjectWithoutDocLines
+          this.ProjectGetter.getProjectWithoutDocLines
             .calledWith(this.project_id)
             .should.equal(true)
         })
 
         it('should unset the root doc', function () {
-          return this.ProjectEntityUpdateHandler.unsetRootDoc
+          this.ProjectEntityUpdateHandler.unsetRootDoc
             .calledWith(this.project_id)
             .should.equal(true)
         })
 
         it('should try to find a new rootDoc', function () {
-          return this.ProjectRootDocManager.setRootDocAutomatically.called.should.equal(
+          this.ProjectRootDocManager.setRootDocAutomatically.called.should.equal(
             true
           )
         })
 
         it('should call the callback', function () {
-          return this.callback.called.should.equal(true)
+          this.callback.called.should.equal(true)
         })
       })
     })
 
     describe('when the root doc is not set', function () {
       beforeEach(function () {
-        return this.ProjectRootDocManager.ensureRootDocumentIsValid(
+        this.ProjectRootDocManager.ensureRootDocumentIsValid(
           this.project_id,
           this.callback
         )
       })
 
       it('should find the project without doc lines', function () {
-        return this.ProjectGetter.getProjectWithoutDocLines
+        this.ProjectGetter.getProjectWithoutDocLines
           .calledWith(this.project_id)
           .should.equal(true)
       })
 
       it('should update the project rootDoc_id', function () {
-        return this.ProjectRootDocManager.setRootDocAutomatically
+        this.ProjectRootDocManager.setRootDocAutomatically
           .calledWith(this.project_id)
           .should.equal(true)
       })
 
       it('should call the callback', function () {
-        return this.callback.called.should.equal(true)
+        this.callback.called.should.equal(true)
       })
     })
 
@@ -734,14 +675,14 @@ describe('ProjectRootDocManager', function () {
         this.ProjectGetter.getProjectWithoutDocLines = sinon
           .stub()
           .callsArgWith(1, null, null)
-        return this.ProjectRootDocManager.ensureRootDocumentIsValid(
+        this.ProjectRootDocManager.ensureRootDocumentIsValid(
           this.project_id,
           this.callback
         )
       })
 
       it('should call the callback with an error', function () {
-        return this.callback
+        this.callback
           .calledWith(
             sinon.match
               .instanceOf(Error)

+ 4 - 9
services/web/test/unit/src/Publishers/PublishersGetterTests.js

@@ -42,15 +42,10 @@ describe('PublishersGetter', function () {
   })
 
   describe('getManagedPublishers', function () {
-    it('fetches v1 data before returning publisher list', function (done) {
-      this.PublishersGetter.getManagedPublishers(
-        this.userId,
-        (error, publishers) => {
-          expect(error).to.be.null
-          publishers.length.should.equal(1)
-          done()
-        }
-      )
+    it('fetches v1 data before returning publisher list', async function () {
+      const publishers =
+        await this.PublishersGetter.promises.getManagedPublishers(this.userId)
+      expect(publishers.length).to.equal(1)
     })
   })
 })

+ 26 - 31
services/web/test/unit/src/Security/LoginRateLimiterTests.js

@@ -25,23 +25,17 @@ describe('LoginRateLimiter', function () {
   })
 
   describe('processLoginRequest', function () {
-    it('should consume points', function (done) {
-      this.LoginRateLimiter.processLoginRequest(this.email, (err, allow) => {
-        if (err) {
-          return done(err)
-        }
-        expect(this.rateLimiter.consume).to.have.been.calledWith(this.email)
-        done()
-      })
+    it('should consume points', async function () {
+      await this.LoginRateLimiter.promises.processLoginRequest(this.email)
+      expect(this.rateLimiter.consume).to.have.been.calledWith(this.email)
     })
 
     describe('when login is allowed', function () {
-      it('should call pass allow=true', function (done) {
-        this.LoginRateLimiter.processLoginRequest(this.email, (err, allow) => {
-          expect(err).to.equal(null)
-          expect(allow).to.equal(true)
-          done()
-        })
+      it('should call pass allow=true', async function () {
+        const allow = await this.LoginRateLimiter.promises.processLoginRequest(
+          this.email
+        )
+        expect(allow).to.equal(true)
       })
     })
 
@@ -50,12 +44,11 @@ describe('LoginRateLimiter', function () {
         this.rateLimiter.consume.rejects({ remainingPoints: 0 })
       })
 
-      it('should call pass allow=false', function (done) {
-        this.LoginRateLimiter.processLoginRequest(this.email, (err, allow) => {
-          expect(err).to.equal(null)
-          expect(allow).to.equal(false)
-          done()
-        })
+      it('should call pass allow=false', async function () {
+        const allow = await this.LoginRateLimiter.promises.processLoginRequest(
+          this.email
+        )
+        expect(allow).to.equal(false)
       })
     })
 
@@ -64,22 +57,24 @@ describe('LoginRateLimiter', function () {
         this.rateLimiter.consume.rejects(new Error('woops'))
       })
 
-      it('should produce an error', function (done) {
-        this.LoginRateLimiter.processLoginRequest(this.email, (err, allow) => {
-          expect(err).to.not.equal(null)
-          expect(err).to.be.instanceof(Error)
-          done()
-        })
+      it('should produce an error', async function () {
+        let error
+
+        try {
+          await this.LoginRateLimiter.promises.processLoginRequest(this.email)
+        } catch (err) {
+          error = err
+        }
+
+        expect(error).to.exist
       })
     })
   })
 
   describe('recordSuccessfulLogin', function () {
-    it('should clear the rate limit', function (done) {
-      this.LoginRateLimiter.recordSuccessfulLogin(this.email, () => {
-        expect(this.rateLimiter.delete).to.have.been.calledWith(this.email)
-        done()
-      })
+    it('should clear the rate limit', async function () {
+      await this.LoginRateLimiter.promises.recordSuccessfulLogin(this.email)
+      expect(this.rateLimiter.delete).to.have.been.calledWith(this.email)
     })
   })
 })

+ 34 - 24
services/web/test/unit/src/Subscription/RecurlyWrapperTests.js

@@ -294,7 +294,7 @@ describe('RecurlyWrapper', function () {
   })
 
   describe('updateAccountEmailAddress, with invalid XML', function () {
-    beforeEach(async function (done) {
+    beforeEach(async function () {
       this.recurlyAccountId = 'account-id-123'
       this.newEmail = '\uD800@example.com'
       this.apiRequest = sinon
@@ -307,22 +307,24 @@ describe('RecurlyWrapper', function () {
             body: fixtures['accounts/104'],
           }
         })
-      done()
     })
 
     afterEach(function () {
       this.RecurlyWrapper.promises.apiRequest.restore()
     })
 
-    it('should produce an error', function (done) {
-      this.RecurlyWrapper.promises
-        .updateAccountEmailAddress(this.recurlyAccountId, this.newEmail)
-        .catch(error => {
-          expect(error).to.exist
-          expect(error.message.startsWith('Invalid character')).to.equal(true)
-          expect(this.apiRequest.called).to.equal(false)
-          done()
-        })
+    it('should produce an error', async function () {
+      try {
+        await this.RecurlyWrapper.promises.updateAccountEmailAddress(
+          this.recurlyAccountId,
+          this.newEmail
+        )
+        expect(false).to.equal(true) // Fail if we don't have an error
+      } catch (error) {
+        expect(error).to.have.property('message')
+        expect(error.message.startsWith('Invalid character')).to.be.true
+        expect(this.apiRequest.called).to.equal(false)
+      }
     })
   })
 
@@ -702,19 +704,27 @@ describe('RecurlyWrapper', function () {
         })
       })
 
-      it('should produce an error', function (done) {
-        this.call().catch(err => {
-          expect(err).to.be.instanceof(
-            SubscriptionErrors.RecurlyTransactionError
-          )
-          expect(err.info.public.message).to.be.equal(
-            'Your card must be authenticated with 3D Secure before continuing.'
-          )
-          expect(err.info.public.threeDSecureActionTokenId).to.be.equal(
-            'mock_three_d_secure_action_token'
-          )
-          done()
-        })
+      it('should produce an error', async function () {
+        const promise = this.call()
+        let error
+
+        try {
+          await promise
+        } catch (err) {
+          error = err
+        }
+
+        expect(error).to.be.instanceOf(
+          SubscriptionErrors.RecurlyTransactionError
+        )
+        expect(error).to.have.nested.property(
+          'info.public.message',
+          'Your card must be authenticated with 3D Secure before continuing.'
+        )
+        expect(error).to.have.nested.property(
+          'info.public.threeDSecureActionTokenId',
+          'mock_three_d_secure_action_token'
+        )
       })
     })
 

+ 250 - 258
services/web/test/unit/src/Subscription/TeamInvitesHandlerTests.js

@@ -131,174 +131,149 @@ describe('TeamInvitesHandler', function () {
   })
 
   describe('getInvite', function () {
-    it("returns the invite if there's one", function (done) {
-      this.TeamInvitesHandler.getInvite(
-        this.token,
-        (err, invite, subscription) => {
-          expect(err).to.eq(null)
-          expect(invite).to.deep.eq(this.teamInvite)
-          expect(subscription).to.deep.eq(this.subscription)
-          done()
-        }
-      )
+    it("returns the invite if there's one", async function () {
+      const { invite, subscription } =
+        await this.TeamInvitesHandler.promises.getInvite(this.token)
+
+      expect(invite).to.deep.eq(this.teamInvite)
+      expect(subscription).to.deep.eq(this.subscription)
     })
 
-    it("returns teamNotFound if there's none", function (done) {
+    it("returns teamNotFound if there's none", async function () {
       this.Subscription.findOne = sinon.stub().resolves(null)
 
-      this.TeamInvitesHandler.getInvite(
-        this.token,
-        (err, invite, subscription) => {
-          expect(err).to.be.instanceof(Errors.NotFoundError)
-          done()
-        }
-      )
+      let error
+      try {
+        await this.TeamInvitesHandler.promises.getInvite(this.token)
+      } catch (err) {
+        error = err
+      }
+
+      expect(error).to.be.instanceOf(Errors.NotFoundError)
     })
   })
 
   describe('createInvite', function () {
-    it('adds the team invite to the subscription', function (done) {
-      this.TeamInvitesHandler.createInvite(
+    it('adds the team invite to the subscription', async function () {
+      const invite = await this.TeamInvitesHandler.promises.createInvite(
         this.manager._id,
         this.subscription,
-        'John.Snow@example.com',
-        (err, invite) => {
-          expect(err).to.eq(null)
-          expect(invite.token).to.eq(this.newToken)
-          expect(invite.email).to.eq('john.snow@example.com')
-          expect(invite.inviterName).to.eq(
-            'Daenerys Targaryen (daenerys@example.com)'
-          )
-          expect(invite.invite).to.be.true
-          expect(this.subscription.teamInvites).to.deep.include(invite)
-          done()
-        }
+        'John.Snow@example.com'
+      )
+      expect(invite.token).to.eq(this.newToken)
+      expect(invite.email).to.eq('john.snow@example.com')
+      expect(invite.inviterName).to.eq(
+        'Daenerys Targaryen (daenerys@example.com)'
       )
+      expect(invite.invite).to.be.true
+      expect(this.subscription.teamInvites).to.deep.include(invite)
     })
 
-    it('sends an email', function (done) {
-      this.TeamInvitesHandler.createInvite(
+    it('sends an email', async function () {
+      await this.TeamInvitesHandler.promises.createInvite(
         this.manager._id,
         this.subscription,
-        'John.Snow@example.com',
-        (err, invite) => {
-          this.EmailHandler.promises.sendEmail
-            .calledWith(
-              'verifyEmailToJoinTeam',
-              sinon.match({
-                to: 'john.snow@example.com',
-                inviter: this.manager,
-                acceptInviteUrl: `http://example.com/subscription/invites/${this.newToken}/`,
-              })
-            )
-            .should.equal(true)
-          done(err)
-        }
+        'John.Snow@example.com'
       )
+
+      this.EmailHandler.promises.sendEmail
+        .calledWith(
+          'verifyEmailToJoinTeam',
+          sinon.match({
+            to: 'john.snow@example.com',
+            inviter: this.manager,
+            acceptInviteUrl: `http://example.com/subscription/invites/${this.newToken}/`,
+          })
+        )
+        .should.equal(true)
     })
 
-    it('refreshes the existing invite if the email has already been invited', function (done) {
+    it('refreshes the existing invite if the email has already been invited', async function () {
       const originalInvite = Object.assign({}, this.teamInvite)
 
-      this.TeamInvitesHandler.createInvite(
+      const invite = await this.TeamInvitesHandler.promises.createInvite(
         this.manager._id,
         this.subscription,
-        originalInvite.email,
-        (err, invite) => {
-          expect(err).to.eq(null)
-          expect(invite).to.exist
-
-          expect(this.subscription.teamInvites.length).to.eq(1)
-          expect(this.subscription.teamInvites).to.deep.include(invite)
+        originalInvite.email
+      )
+      expect(invite).to.exist
 
-          expect(invite.email).to.eq(originalInvite.email)
+      expect(this.subscription.teamInvites.length).to.eq(1)
+      expect(this.subscription.teamInvites).to.deep.include(invite)
 
-          this.subscription.save.calledOnce.should.eq(true)
+      expect(invite.email).to.eq(originalInvite.email)
 
-          done()
-        }
-      )
+      this.subscription.save.calledOnce.should.eq(true)
     })
 
-    it('removes any legacy invite from the subscription', function (done) {
-      this.TeamInvitesHandler.createInvite(
+    it('removes any legacy invite from the subscription', async function () {
+      await this.TeamInvitesHandler.promises.createInvite(
         this.manager._id,
         this.subscription,
-        'John.Snow@example.com',
-        (err, invite) => {
-          this.Subscription.updateOne
-            .calledWith(
-              { _id: new ObjectId('55153a8014829a865bbf700d') },
-              { $pull: { invited_emails: 'john.snow@example.com' } }
-            )
-            .should.eq(true)
-          done(err)
-        }
+        'John.Snow@example.com'
       )
+
+      this.Subscription.updateOne
+        .calledWith(
+          { _id: new ObjectId('55153a8014829a865bbf700d') },
+          { $pull: { invited_emails: 'john.snow@example.com' } }
+        )
+        .should.eq(true)
     })
 
-    it('add user to subscription if inviting self', function (done) {
-      this.TeamInvitesHandler.createInvite(
+    it('add user to subscription if inviting self', async function () {
+      const invite = await this.TeamInvitesHandler.promises.createInvite(
         this.manager._id,
         this.subscription,
-        this.manager.email,
-        (err, invite) => {
-          sinon.assert.calledWith(
-            this.SubscriptionUpdater.promises.addUserToGroup,
-            this.subscription._id,
-            this.manager._id
-          )
-          sinon.assert.notCalled(this.subscription.save)
-          expect(invite.token).to.not.exist
-          expect(invite.email).to.eq(this.manager.email)
-          expect(invite.first_name).to.eq(this.manager.first_name)
-          expect(invite.last_name).to.eq(this.manager.last_name)
-          expect(invite.invite).to.be.false
-          done(err)
-        }
+        this.manager.email
       )
+      sinon.assert.calledWith(
+        this.SubscriptionUpdater.promises.addUserToGroup,
+        this.subscription._id,
+        this.manager._id
+      )
+      sinon.assert.notCalled(this.subscription.save)
+      expect(invite.token).to.not.exist
+      expect(invite.email).to.eq(this.manager.email)
+      expect(invite.first_name).to.eq(this.manager.first_name)
+      expect(invite.last_name).to.eq(this.manager.last_name)
+      expect(invite.invite).to.be.false
     })
 
-    it('sends an SSO invite if SSO is enabled and inviting self', function (done) {
+    it('sends an SSO invite if SSO is enabled and inviting self', async function () {
       this.subscription.ssoConfig = new ObjectId('abc123abc123abc123abc123')
       this.SSOConfig.findById
         .withArgs(this.subscription.ssoConfig)
         .resolves({ enabled: true })
 
-      this.TeamInvitesHandler.createInvite(
+      await this.TeamInvitesHandler.promises.createInvite(
         this.manager._id,
         this.subscription,
-        this.manager.email,
-        (err, invite) => {
-          sinon.assert.calledWith(
-            this.Modules.promises.hooks.fire,
-            'sendGroupSSOReminder',
-            this.manager._id,
-            this.subscription._id
-          )
-          done(err)
-        }
+        this.manager.email
+      )
+      sinon.assert.calledWith(
+        this.Modules.promises.hooks.fire,
+        'sendGroupSSOReminder',
+        this.manager._id,
+        this.subscription._id
       )
     })
 
-    it('does not send an SSO invite if SSO is disabled and inviting self', function (done) {
+    it('does not send an SSO invite if SSO is disabled and inviting self', async function () {
       this.subscription.ssoConfig = new ObjectId('abc123abc123abc123abc123')
       this.SSOConfig.findById
         .withArgs(this.subscription.ssoConfig)
         .resolves({ enabled: false })
 
-      this.TeamInvitesHandler.createInvite(
+      await this.TeamInvitesHandler.promises.createInvite(
         this.manager._id,
         this.subscription,
-        this.manager.email,
-        (err, invite) => {
-          sinon.assert.notCalled(this.Modules.promises.hooks.fire)
-          done(err)
-        }
+        this.manager.email
       )
+      sinon.assert.notCalled(this.Modules.promises.hooks.fire)
     })
 
-    it('sends a notification if inviting registered user', function (done) {
+    it('sends a notification if inviting registered user', async function () {
       const id = new ObjectId('6a6b3a8014829a865bbf700d')
       const managedUsersEnabled = false
 
@@ -308,22 +283,19 @@ describe('TeamInvitesHandler', function () {
           _id: id,
         })
 
-      this.TeamInvitesHandler.createInvite(
+      const invite = await this.TeamInvitesHandler.promises.createInvite(
         this.manager._id,
         this.subscription,
-        'John.Snow@example.com',
-        (err, invite) => {
-          this.NotificationsBuilder.promises
-            .groupInvitation(
-              id.toString(),
-              this.subscription._id,
-              managedUsersEnabled
-            )
-            .create.calledWith(invite)
-            .should.eq(true)
-          done(err)
-        }
+        'John.Snow@example.com'
       )
+      this.NotificationsBuilder.promises
+        .groupInvitation(
+          id.toString(),
+          this.subscription._id,
+          managedUsersEnabled
+        )
+        .create.calledWith(invite)
+        .should.eq(true)
     })
   })
 
@@ -375,112 +347,115 @@ describe('TeamInvitesHandler', function () {
     })
 
     describe('with standard group', function () {
-      it('adds the user to the team', function (done) {
-        this.TeamInvitesHandler.acceptInvite('dddddddd', this.user.id, () => {
-          this.SubscriptionUpdater.promises.addUserToGroup
-            .calledWith(this.subscription._id, this.user.id)
-            .should.eq(true)
-          done()
-        })
+      it('adds the user to the team', async function () {
+        await this.TeamInvitesHandler.promises.acceptInvite(
+          'dddddddd',
+          this.user.id
+        )
+        this.SubscriptionUpdater.promises.addUserToGroup
+          .calledWith(this.subscription._id, this.user.id)
+          .should.eq(true)
       })
 
-      it('removes the invite from the subscription', function (done) {
-        this.TeamInvitesHandler.acceptInvite('dddddddd', this.user.id, () => {
-          this.Subscription.updateOne
-            .calledWith(
-              { _id: new ObjectId('55153a8014829a865bbf700d') },
-              { $pull: { teamInvites: { email: 'john.snow@example.com' } } }
-            )
-            .should.eq(true)
-          done()
-        })
+      it('removes the invite from the subscription', async function () {
+        await this.TeamInvitesHandler.promises.acceptInvite(
+          'dddddddd',
+          this.user.id
+        )
+        this.Subscription.updateOne
+          .calledWith(
+            { _id: new ObjectId('55153a8014829a865bbf700d') },
+            { $pull: { teamInvites: { email: 'john.snow@example.com' } } }
+          )
+          .should.eq(true)
       })
 
-      it('removes dashboard notification after they accepted group invitation', function (done) {
+      it('removes dashboard notification after they accepted group invitation', async function () {
         const managedUsersEnabled = false
 
-        this.TeamInvitesHandler.acceptInvite('dddddddd', this.user.id, () => {
-          sinon.assert.called(
-            this.NotificationsBuilder.promises.groupInvitation(
-              this.user.id,
-              this.subscription._id,
-              managedUsersEnabled
-            ).read
-          )
-          done()
-        })
+        await this.TeamInvitesHandler.promises.acceptInvite(
+          'dddddddd',
+          this.user.id
+        )
+        sinon.assert.called(
+          this.NotificationsBuilder.promises.groupInvitation(
+            this.user.id,
+            this.subscription._id,
+            managedUsersEnabled
+          ).read
+        )
       })
 
-      it('should not schedule an SSO invite reminder', function (done) {
-        this.TeamInvitesHandler.acceptInvite('dddddddd', this.user.id, () => {
-          sinon.assert.notCalled(this.Modules.promises.hooks.fire)
-          done()
-        })
+      it('should not schedule an SSO invite reminder', async function () {
+        await this.TeamInvitesHandler.promises.acceptInvite(
+          'dddddddd',
+          this.user.id
+        )
+        sinon.assert.notCalled(this.Modules.promises.hooks.fire)
       })
     })
 
     describe('with managed group', function () {
-      it('should enroll the group member', function (done) {
+      it('should enroll the group member', async function () {
         this.subscription.managedUsersEnabled = true
 
-        this.TeamInvitesHandler.acceptInvite('dddddddd', this.user.id, () => {
-          sinon.assert.calledWith(
-            this.Modules.promises.hooks.fire,
-            'enrollInManagedSubscription',
-            this.user.id,
-            this.subscription
-          )
-          done()
-        })
+        await this.TeamInvitesHandler.promises.acceptInvite(
+          'dddddddd',
+          this.user.id
+        )
+        sinon.assert.calledWith(
+          this.Modules.promises.hooks.fire,
+          'enrollInManagedSubscription',
+          this.user.id,
+          this.subscription
+        )
       })
     })
 
     describe('with group SSO enabled', function () {
-      it('should schedule an SSO invite reminder', function (done) {
+      it('should schedule an SSO invite reminder', async function () {
         this.subscription.ssoConfig = 'ssoconfig1'
         this.SSOConfig.findById
           .withArgs('ssoconfig1')
           .resolves({ enabled: true })
 
-        this.TeamInvitesHandler.acceptInvite('dddddddd', this.user.id, () => {
-          sinon.assert.calledWith(
-            this.Modules.promises.hooks.fire,
-            'scheduleGroupSSOReminder',
-            this.user.id,
-            this.subscription._id
-          )
-          done()
-        })
+        await this.TeamInvitesHandler.promises.acceptInvite(
+          'dddddddd',
+          this.user.id
+        )
+        sinon.assert.calledWith(
+          this.Modules.promises.hooks.fire,
+          'scheduleGroupSSOReminder',
+          this.user.id,
+          this.subscription._id
+        )
       })
     })
   })
 
   describe('revokeInvite', function () {
-    it('removes the team invite from the subscription', function (done) {
-      this.TeamInvitesHandler.revokeInvite(
+    it('removes the team invite from the subscription', async function () {
+      await this.TeamInvitesHandler.promises.revokeInvite(
         this.manager._id,
         this.subscription,
-        'jorah@example.com',
-        () => {
-          this.Subscription.updateOne
-            .calledWith(
-              { _id: new ObjectId('55153a8014829a865bbf700d') },
-              { $pull: { teamInvites: { email: 'jorah@example.com' } } }
-            )
-            .should.eq(true)
-
-          this.Subscription.updateOne
-            .calledWith(
-              { _id: new ObjectId('55153a8014829a865bbf700d') },
-              { $pull: { invited_emails: 'jorah@example.com' } }
-            )
-            .should.eq(true)
-          done()
-        }
+        'jorah@example.com'
       )
+      this.Subscription.updateOne
+        .calledWith(
+          { _id: new ObjectId('55153a8014829a865bbf700d') },
+          { $pull: { teamInvites: { email: 'jorah@example.com' } } }
+        )
+        .should.eq(true)
+
+      this.Subscription.updateOne
+        .calledWith(
+          { _id: new ObjectId('55153a8014829a865bbf700d') },
+          { $pull: { invited_emails: 'jorah@example.com' } }
+        )
+        .should.eq(true)
     })
 
-    it('removes dashboard notification for pending group invitation', function (done) {
+    it('removes dashboard notification for pending group invitation', async function () {
       const managedUsersEnabled = false
 
       const pendingUser = {
@@ -492,26 +467,23 @@ describe('TeamInvitesHandler', function () {
         .withArgs(pendingUser.email)
         .resolves(pendingUser)
 
-      this.TeamInvitesHandler.revokeInvite(
+      await this.TeamInvitesHandler.promises.revokeInvite(
         this.manager._id,
         this.subscription,
-        pendingUser.email,
-        () => {
-          sinon.assert.called(
-            this.NotificationsBuilder.promises.groupInvitation(
-              pendingUser.id,
-              this.subscription._id,
-              managedUsersEnabled
-            ).read
-          )
+        pendingUser.email
+      )
 
-          done()
-        }
+      sinon.assert.called(
+        this.NotificationsBuilder.promises.groupInvitation(
+          pendingUser.id,
+          this.subscription._id,
+          managedUsersEnabled
+        ).read
       )
     })
   })
 
-  describe('createTeamInvitesForLegacyInvitedEmail', function (done) {
+  describe('createTeamInvitesForLegacyInvitedEmail', function () {
     beforeEach(function () {
       this.subscription.invited_emails = [
         'eddard@example.com',
@@ -523,58 +495,71 @@ describe('TeamInvitesHandler', function () {
         .resolves([this.subscription])
     })
 
-    it('sends an invitation email to addresses in the legacy invited_emails field', function (done) {
-      this.TeamInvitesHandler.createTeamInvitesForLegacyInvitedEmail(
-        'eddard@example.com',
-        (err, invites) => {
-          expect(err).not.to.exist
-          expect(invites.length).to.eq(1)
-
-          const [invite] = invites
-          expect(invite.token).to.eq(this.newToken)
-          expect(invite.email).to.eq('eddard@example.com')
-          expect(invite.inviterName).to.eq(
-            'Daenerys Targaryen (daenerys@example.com)'
-          )
-          expect(invite.invite).to.be.true
-          expect(this.subscription.teamInvites).to.deep.include(invite)
+    it('sends an invitation email to addresses in the legacy invited_emails field', async function () {
+      const invites =
+        await this.TeamInvitesHandler.promises.createTeamInvitesForLegacyInvitedEmail(
+          'eddard@example.com'
+        )
 
-          done()
-        }
+      expect(invites.length).to.eq(1)
+
+      const [invite] = invites
+      expect(invite.token).to.eq(this.newToken)
+      expect(invite.email).to.eq('eddard@example.com')
+      expect(invite.inviterName).to.eq(
+        'Daenerys Targaryen (daenerys@example.com)'
       )
+      expect(invite.invite).to.be.true
+      expect(this.subscription.teamInvites).to.deep.include(invite)
     })
   })
 
   describe('validation', function () {
-    it("doesn't create an invite if the team limit has been reached", function (done) {
+    it("doesn't create an invite if the team limit has been reached", async function () {
       this.LimitationsManager.teamHasReachedMemberLimit = sinon
         .stub()
         .returns(true)
-      this.TeamInvitesHandler.createInvite(
-        this.manager._id,
-        this.subscription,
-        'John.Snow@example.com',
-        (err, invite) => {
-          expect(err).to.deep.equal({ limitReached: true })
-          done()
-        }
-      )
+      let error
+
+      try {
+        await this.TeamInvitesHandler.promises.createInvite(
+          this.manager._id,
+          this.subscription,
+          'John.Snow@example.com'
+        )
+      } catch (err) {
+        error = err
+      }
+
+      expect(error).to.exist
+
+      expect(error).to.deep.equal({
+        limitReached: true,
+      })
     })
 
-    it("doesn't create an invite if the subscription is not in a group plan", function (done) {
+    it("doesn't create an invite if the subscription is not in a group plan", async function () {
       this.subscription.groupPlan = false
-      this.TeamInvitesHandler.createInvite(
-        this.manager._id,
-        this.subscription,
-        'John.Snow@example.com',
-        (err, invite) => {
-          expect(err).to.deep.equal({ wrongPlan: true })
-          done()
-        }
-      )
+      let error
+
+      try {
+        await this.TeamInvitesHandler.promises.createInvite(
+          this.manager._id,
+          this.subscription,
+          'John.Snow@example.com'
+        )
+      } catch (err) {
+        error = err
+      }
+
+      expect(error).to.exist
+
+      expect(error).to.deep.equal({
+        wrongPlan: true,
+      })
     })
 
-    it("doesn't create an invite if the user is already part of the team", function (done) {
+    it("doesn't create an invite if the user is already part of the team", async function () {
       const member = {
         id: '1a2b',
         _id: '1a2b',
@@ -586,16 +571,23 @@ describe('TeamInvitesHandler', function () {
         .withArgs(member.email)
         .resolves(member)
 
-      this.TeamInvitesHandler.createInvite(
-        this.manager._id,
-        this.subscription,
-        'tyrion@example.com',
-        (err, invite) => {
-          expect(err).to.deep.equal({ alreadyInTeam: true })
-          expect(invite).not.to.exist
-          done()
-        }
-      )
+      let error
+
+      try {
+        await this.TeamInvitesHandler.promises.createInvite(
+          this.manager._id,
+          this.subscription,
+          'tyrion@example.com'
+        )
+      } catch (err) {
+        error = err
+      }
+
+      expect(error).to.exist
+
+      expect(error).to.eql({
+        alreadyInTeam: true,
+      })
     })
   })
 })

+ 88 - 127
services/web/test/unit/src/Tags/TagsHandlerTests.js

@@ -30,78 +30,72 @@ describe('TagsHandler', function () {
   })
 
   describe('finding users tags', function () {
-    it('should find all the documents with that user id', function (done) {
+    it('should find all the documents with that user id', async function () {
       const stubbedTags = [{ name: 'tag1' }, { name: 'tag2' }, { name: 'tag3' }]
       this.TagMock.expects('find')
         .once()
         .withArgs({ user_id: this.userId })
         .resolves(stubbedTags)
-      this.TagsHandler.getAllTags(this.userId, (err, result) => {
-        expect(err).to.not.exist
-        this.TagMock.verify()
-        expect(result).to.deep.equal(stubbedTags)
-        done()
-      })
+      const result = await this.TagsHandler.promises.getAllTags(this.userId)
+      this.TagMock.verify()
+      expect(result).to.deep.equal(stubbedTags)
     })
   })
 
   describe('createTag', function () {
     describe('when insert succeeds', function () {
-      it('should call insert in mongo', function (done) {
+      it('should call insert in mongo', async function () {
         this.TagMock.expects('create')
           .withArgs(this.tag)
           .once()
           .resolves(this.tag)
-        this.TagsHandler.createTag(
+        const resultTag = await this.TagsHandler.promises.createTag(
           this.tag.user_id,
           this.tag.name,
-          this.tag.color,
-          (err, resultTag) => {
-            expect(err).to.not.exist
-            this.TagMock.verify()
-            expect(resultTag.user_id).to.equal(this.tag.user_id)
-            expect(resultTag.name).to.equal(this.tag.name)
-            expect(resultTag.color).to.equal(this.tag.color)
-            done()
-          }
+          this.tag.color
         )
+        this.TagMock.verify()
+        expect(resultTag.user_id).to.equal(this.tag.user_id)
+        expect(resultTag.name).to.equal(this.tag.name)
+        expect(resultTag.color).to.equal(this.tag.color)
       })
     })
 
     describe('when truncate=true, and tag is too long', function () {
-      it('should truncate the tag name', function (done) {
+      it('should truncate the tag name', async function () {
         // Expect the tag to end up with this truncated name
         this.tag.name = 'a comically long tag that will be truncated intern'
         this.TagMock.expects('create')
           .withArgs(this.tag)
           .once()
           .resolves(this.tag)
-        this.TagsHandler.createTag(
+        const resultTag = await this.TagsHandler.promises.createTag(
           this.tag.user_id,
           // Pass this too-long name
           'a comically long tag that will be truncated internally and not throw an error',
           this.tag.color,
-          { truncate: true },
-          (err, resultTag) => {
-            expect(err).to.not.exist
-            expect(resultTag.name).to.equal(this.tag.name)
-            done()
-          }
+          { truncate: true }
         )
+        expect(resultTag.name).to.equal(this.tag.name)
       })
     })
 
     describe('when tag is too long', function () {
-      it('should throw an error', function (done) {
-        this.TagsHandler.createTag(
-          this.tag.user_id,
-          'this is a tag that is very very very very very very long',
-          undefined,
-          err => {
-            expect(err.message).to.equal('Exceeded max tag length')
-            done()
-          }
-        )
+      it('should throw an error', async function () {
+        let error
+
+        try {
+          await this.TagsHandler.promises.createTag(
+            this.tag.user_id,
+            'this is a tag that is very very very very very very long',
+            undefined
+          )
+        } catch (err) {
+          error = err
+        }
+
+        expect(error).to.exist
+        expect(error).to.have.property('message', 'Exceeded max tag length')
       })
     })
 
@@ -111,7 +105,7 @@ describe('TagsHandler', function () {
         this.duplicateKeyError.code = 11000
       })
 
-      it('should get tag with findOne and return that tag', function (done) {
+      it('should get tag with findOne and return that tag', async function () {
         this.TagMock.expects('create')
           .withArgs(this.tag)
           .once()
@@ -120,26 +114,22 @@ describe('TagsHandler', function () {
           .withArgs({ user_id: this.tag.user_id, name: this.tag.name })
           .once()
           .resolves(this.tag)
-        this.TagsHandler.createTag(
+        const resultTag = await this.TagsHandler.promises.createTag(
           this.tag.user_id,
           this.tag.name,
-          this.tag.color,
-          (err, resultTag) => {
-            expect(err).to.not.exist
-            this.TagMock.verify()
-            expect(resultTag.user_id).to.equal(this.tag.user_id)
-            expect(resultTag.name).to.equal(this.tag.name)
-            expect(resultTag.color).to.equal(this.tag.color)
-            done()
-          }
+          this.tag.color
         )
+        this.TagMock.verify()
+        expect(resultTag.user_id).to.equal(this.tag.user_id)
+        expect(resultTag.name).to.equal(this.tag.name)
+        expect(resultTag.color).to.equal(this.tag.color)
       })
     })
   })
 
   describe('addProjectToTag', function () {
     describe('with a valid tag_id', function () {
-      it('should call update in mongo', function (done) {
+      it('should call update in mongo', async function () {
         this.TagMock.expects('findOneAndUpdate')
           .once()
           .withArgs(
@@ -147,23 +137,19 @@ describe('TagsHandler', function () {
             { $addToSet: { project_ids: this.projectId } }
           )
           .resolves()
-        this.TagsHandler.addProjectToTag(
+        await this.TagsHandler.promises.addProjectToTag(
           this.userId,
           this.tagId,
-          this.projectId,
-          err => {
-            expect(err).to.not.exist
-            this.TagMock.verify()
-            done()
-          }
+          this.projectId
         )
+        this.TagMock.verify()
       })
     })
   })
 
   describe('addProjectsToTag', function () {
     describe('with a valid tag_id', function () {
-      it('should call update in mongo', function (done) {
+      it('should call update in mongo', async function () {
         this.TagMock.expects('findOneAndUpdate')
           .once()
           .withArgs(
@@ -171,22 +157,18 @@ describe('TagsHandler', function () {
             { $addToSet: { project_ids: { $each: this.projectIds } } }
           )
           .resolves()
-        this.TagsHandler.addProjectsToTag(
+        await this.TagsHandler.promises.addProjectsToTag(
           this.userId,
           this.tagId,
-          this.projectIds,
-          err => {
-            expect(err).to.not.exist
-            this.TagMock.verify()
-            done()
-          }
+          this.projectIds
         )
+        this.TagMock.verify()
       })
     })
   })
 
   describe('addProjectToTagName', function () {
-    it('should call update in mongo', function (done) {
+    it('should call update in mongo', async function () {
       this.TagMock.expects('updateOne')
         .once()
         .withArgs(
@@ -195,22 +177,18 @@ describe('TagsHandler', function () {
           { upsert: true }
         )
         .resolves()
-      this.TagsHandler.addProjectToTagName(
+      await this.TagsHandler.promises.addProjectToTagName(
         this.tag.userId,
         this.tag.name,
-        this.projectId,
-        err => {
-          expect(err).to.not.exist
-          this.TagMock.verify()
-          done()
-        }
+        this.projectId
       )
+      this.TagMock.verify()
     })
   })
 
   describe('removeProjectFromTag', function () {
     describe('with a valid tag_id', function () {
-      it('should call update in mongo', function (done) {
+      it('should call update in mongo', async function () {
         this.TagMock.expects('updateOne')
           .once()
           .withArgs(
@@ -223,23 +201,20 @@ describe('TagsHandler', function () {
             }
           )
           .resolves()
-        this.TagsHandler.removeProjectFromTag(
+        await this.TagsHandler.promises.removeProjectFromTag(
           this.userId,
           this.tagId,
-          this.projectId,
-          err => {
-            expect(err).to.not.exist
-            this.TagMock.verify()
-            done()
-          }
+          this.projectId
         )
+
+        this.TagMock.verify()
       })
     })
   })
 
   describe('removeProjectsFromTag', function () {
     describe('with a valid tag_id', function () {
-      it('should call update in mongo', function (done) {
+      it('should call update in mongo', async function () {
         this.TagMock.expects('updateOne')
           .once()
           .withArgs(
@@ -252,22 +227,18 @@ describe('TagsHandler', function () {
             }
           )
           .resolves()
-        this.TagsHandler.removeProjectsFromTag(
+        await this.TagsHandler.promises.removeProjectsFromTag(
           this.userId,
           this.tagId,
-          this.projectIds,
-          err => {
-            expect(err).to.not.exist
-            this.TagMock.verify()
-            done()
-          }
+          this.projectIds
         )
+        this.TagMock.verify()
       })
     })
   })
 
   describe('removeProjectFromAllTags', function () {
-    it('should pull the project id from the tag', function (done) {
+    it('should pull the project id from the tag', async function () {
       this.TagMock.expects('updateMany')
         .once()
         .withArgs(
@@ -279,20 +250,16 @@ describe('TagsHandler', function () {
           }
         )
         .resolves()
-      this.TagsHandler.removeProjectFromAllTags(
+      await this.TagsHandler.promises.removeProjectFromAllTags(
         this.userId,
-        this.projectId,
-        err => {
-          expect(err).to.not.exist
-          this.TagMock.verify()
-          done()
-        }
+        this.projectId
       )
+      this.TagMock.verify()
     })
   })
 
   describe('addProjectToTags', function () {
-    it('should add the project id to each tag', function (done) {
+    it('should add the project id to each tag', async function () {
       const tagIds = []
 
       this.TagMock.expects('updateMany')
@@ -307,38 +274,31 @@ describe('TagsHandler', function () {
           }
         )
         .resolves()
-      this.TagsHandler.addProjectToTags(
+      await this.TagsHandler.promises.addProjectToTags(
         this.userId,
         tagIds,
-        this.projectId,
-        (err, result) => {
-          expect(err).to.not.exist
-          this.TagMock.verify()
-          done()
-        }
+        this.projectId
       )
+      this.TagMock.verify()
     })
   })
 
   describe('deleteTag', function () {
     describe('with a valid tag_id', function () {
-      it('should call remove in mongo', function (done) {
+      it('should call remove in mongo', async function () {
         this.TagMock.expects('deleteOne')
           .once()
           .withArgs({ _id: this.tagId, user_id: this.userId })
           .resolves()
-        this.TagsHandler.deleteTag(this.userId, this.tagId, err => {
-          expect(err).to.not.exist
-          this.TagMock.verify()
-          done()
-        })
+        await this.TagsHandler.promises.deleteTag(this.userId, this.tagId)
+        this.TagMock.verify()
       })
     })
   })
 
   describe('renameTag', function () {
     describe('with a valid tag_id', function () {
-      it('should call remove in mongo', function (done) {
+      it('should call remove in mongo', async function () {
         this.newName = 'new name'
         this.TagMock.expects('updateOne')
           .once()
@@ -347,30 +307,31 @@ describe('TagsHandler', function () {
             { $set: { name: this.newName } }
           )
           .resolves()
-        this.TagsHandler.renameTag(
+        await this.TagsHandler.promises.renameTag(
           this.userId,
           this.tagId,
-          this.newName,
-          err => {
-            expect(err).to.not.exist
-            this.TagMock.verify()
-            done()
-          }
+          this.newName
         )
+        this.TagMock.verify()
       })
     })
 
     describe('when tag is too long', function () {
-      it('should throw an error', function (done) {
-        this.TagsHandler.renameTag(
-          this.userId,
-          this.tagId,
-          'this is a tag that is very very very very very very long',
-          err => {
-            expect(err.message).to.equal('Exceeded max tag length')
-            done()
-          }
-        )
+      it('should throw an error', async function () {
+        let error
+
+        try {
+          await this.TagsHandler.promises.renameTag(
+            this.userId,
+            this.tagId,
+            'this is a tag that is very very very very very very long'
+          )
+        } catch (err) {
+          error = err
+        }
+
+        expect(error).to.exist
+        expect(error).to.have.property('message', 'Exceeded max tag length')
       })
     })
   })

+ 171 - 290
services/web/test/unit/src/Uploads/FileTypeManagerTests.js

@@ -65,22 +65,25 @@ describe('FileTypeManager', function () {
     describe('when it is a directory', function () {
       beforeEach(function () {
         this.stats.isDirectory.returns(true)
-        this.FileTypeManager.isDirectory('/some/path', this.callback)
       })
 
-      it('should return true', function () {
-        this.callback.should.have.been.calledWith(null, true)
+      it('should return true', async function () {
+        const result =
+          await this.FileTypeManager.promises.isDirectory('/some/path')
+
+        expect(result).to.equal(true)
       })
     })
 
     describe('when it is not a directory', function () {
       beforeEach(function () {
         this.stats.isDirectory.returns(false)
-        this.FileTypeManager.isDirectory('/some/path', this.callback)
       })
 
-      it('should return false', function () {
-        this.callback.should.have.been.calledWith(null, false)
+      it('should return false', async function () {
+        const result =
+          await this.FileTypeManager.promises.isDirectory('/some/path')
+        expect(result).to.equal(false)
       })
     })
   })
@@ -113,223 +116,158 @@ describe('FileTypeManager', function () {
         '/GNUMakefile',
       ]
       TEXT_FILENAMES.forEach(filename => {
-        it(`should classify ${filename} as text`, function (done) {
-          this.FileTypeManager.getType(
+        it(`should classify ${filename} as text`, async function () {
+          const { binary } = await this.FileTypeManager.promises.getType(
             filename,
             'utf8.tex',
-            null,
-            (err, { binary }) => {
-              if (err) {
-                return done(err)
-              }
-              binary.should.equal(false)
-              done()
-            }
+            null
           )
+
+          binary.should.equal(false)
         })
       })
 
-      it('should not classify short text files as binary', function (done) {
+      it('should not classify short text files as binary', async function () {
         this.stats.size = 2 * 1024 * 1024 // 2MB
-        this.FileTypeManager.getType(
+        const { binary } = await this.FileTypeManager.promises.getType(
           '/file.tex',
           'text-short.tex',
-          null,
-          (err, { binary }) => {
-            if (err) {
-              return done(err)
-            }
-            binary.should.equal(false)
-            done()
-          }
+          null
         )
+
+        binary.should.equal(false)
       })
 
-      it('should not classify text files just under the size limit as binary', function (done) {
+      it('should not classify text files just under the size limit as binary', async function () {
         this.stats.size = 2 * 1024 * 1024 // 2MB
-        this.FileTypeManager.getType(
+        const { binary } = await this.FileTypeManager.promises.getType(
           '/file.tex',
           'text-smaller.tex',
-          null,
-          (err, { binary }) => {
-            if (err) {
-              return done(err)
-            }
-            binary.should.equal(false)
-            done()
-          }
+          null
         )
+
+        binary.should.equal(false)
       })
 
-      it('should classify text files at the size limit as binary', function (done) {
+      it('should classify text files at the size limit as binary', async function () {
         this.stats.size = 2 * 1024 * 1024 // 2MB
-        this.FileTypeManager.getType(
+        const { binary } = await this.FileTypeManager.promises.getType(
           '/file.tex',
           'text-exact.tex',
-          null,
-          (err, { binary }) => {
-            if (err) {
-              return done(err)
-            }
-            binary.should.equal(true)
-            done()
-          }
+          null
         )
+
+        binary.should.equal(true)
       })
 
-      it('should classify long text files as binary', function (done) {
+      it('should classify long text files as binary', async function () {
         this.stats.size = 2 * 1024 * 1024 // 2MB
-        this.FileTypeManager.getType(
+        const { binary } = await this.FileTypeManager.promises.getType(
           '/file.tex',
           'text-long.tex',
-          null,
-          (err, { binary }) => {
-            if (err) {
-              return done(err)
-            }
-            binary.should.equal(true)
-            done()
-          }
+          null
         )
+
+        binary.should.equal(true)
       })
 
-      it('should classify large text files as binary', function (done) {
+      it('should classify large text files as binary', async function () {
         this.stats.size = 8 * 1024 * 1024 // 8MB
-        this.FileTypeManager.getType(
+        const { binary } = await this.FileTypeManager.promises.getType(
           '/file.tex',
           'utf8.tex',
-          null,
-          (err, { binary }) => {
-            if (err) {
-              return done(err)
-            }
-            binary.should.equal(true)
-            done()
-          }
+          null
         )
+
+        binary.should.equal(true)
       })
 
-      it('should not try to determine the encoding of large files', function (done) {
+      it('should not try to determine the encoding of large files', async function () {
         this.stats.size = 8 * 1024 * 1024 // 8MB
-        this.FileTypeManager.getType('/file.tex', 'utf8.tex', null, err => {
-          if (err) {
-            return done(err)
-          }
-          sinon.assert.notCalled(this.isUtf8)
-          done()
-        })
+        await this.FileTypeManager.promises.getType(
+          '/file.tex',
+          'utf8.tex',
+          null
+        )
+
+        sinon.assert.notCalled(this.isUtf8)
       })
 
-      it('should detect the encoding of a utf8 file', function (done) {
-        this.FileTypeManager.getType(
+      it('should detect the encoding of a utf8 file', async function () {
+        const { encoding } = await this.FileTypeManager.promises.getType(
           '/file.tex',
           'utf8.tex',
-          null,
-          (err, { binary, encoding }) => {
-            if (err) {
-              return done(err)
-            }
-            sinon.assert.calledOnce(this.isUtf8)
-            this.isUtf8.returned(true).should.equal(true)
-            encoding.should.equal('utf-8')
-            done()
-          }
+          null
         )
+
+        sinon.assert.calledOnce(this.isUtf8)
+        this.isUtf8.returned(true).should.equal(true)
+        encoding.should.equal('utf-8')
       })
 
-      it("should return 'latin1' for non-unicode encodings", function (done) {
-        this.FileTypeManager.getType(
+      it("should return 'latin1' for non-unicode encodings", async function () {
+        const { encoding } = await this.FileTypeManager.promises.getType(
           '/file.tex',
           'latin1.tex',
-          null,
-          (err, { binary, encoding }) => {
-            if (err) {
-              return done(err)
-            }
-            sinon.assert.calledOnce(this.isUtf8)
-            this.isUtf8.returned(false).should.equal(true)
-            encoding.should.equal('latin1')
-            done()
-          }
+          null
         )
+
+        sinon.assert.calledOnce(this.isUtf8)
+        this.isUtf8.returned(false).should.equal(true)
+        encoding.should.equal('latin1')
       })
 
-      it('should classify utf16 with BOM as utf-16', function (done) {
-        this.FileTypeManager.getType(
+      it('should classify utf16 with BOM as utf-16', async function () {
+        const { encoding } = await this.FileTypeManager.promises.getType(
           '/file.tex',
           'utf16.tex',
-          null,
-          (err, { binary, encoding }) => {
-            if (err) {
-              return done(err)
-            }
-            sinon.assert.calledOnce(this.isUtf8)
-            this.isUtf8.returned(false).should.equal(true)
-            encoding.should.equal('utf-16le')
-            done()
-          }
+          null
         )
+
+        sinon.assert.calledOnce(this.isUtf8)
+        this.isUtf8.returned(false).should.equal(true)
+        encoding.should.equal('utf-16le')
       })
 
-      it('should classify latin1 files with a null char as binary', function (done) {
-        this.FileTypeManager.getType(
+      it('should classify latin1 files with a null char as binary', async function () {
+        const { binary } = await this.FileTypeManager.promises.getType(
           '/file.tex',
           'latin1-null.tex',
-          null,
-          (err, { binary }) => {
-            if (err) {
-              return done(err)
-            }
-            expect(binary).to.equal(true)
-            done()
-          }
+          null
         )
+        expect(binary).to.equal(true)
       })
 
-      it('should classify utf8 files with a null char as binary', function (done) {
-        this.FileTypeManager.getType(
+      it('should classify utf8 files with a null char as binary', async function () {
+        const { binary } = await this.FileTypeManager.promises.getType(
           '/file.tex',
           'utf8-null.tex',
-          null,
-          (err, { binary }) => {
-            if (err) {
-              return done(err)
-            }
-            expect(binary).to.equal(true)
-            done()
-          }
+          null
         )
+
+        expect(binary).to.equal(true)
       })
 
-      it('should classify utf8 files with non-BMP chars as binary', function (done) {
-        this.FileTypeManager.getType(
+      it('should classify utf8 files with non-BMP chars as binary', async function () {
+        const { binary } = await this.FileTypeManager.promises.getType(
           '/file.tex',
           'utf8-non-bmp.tex',
-          null,
-          (err, { binary }) => {
-            if (err) {
-              return done(err)
-            }
-            expect(binary).to.equal(true)
-            done()
-          }
+          null
         )
+
+        expect(binary).to.equal(true)
       })
 
-      it('should classify utf8 files with ascii control chars as utf-8', function (done) {
-        this.FileTypeManager.getType(
-          '/file.tex',
-          'utf8-control-chars.tex',
-          null,
-          (err, { binary, encoding }) => {
-            if (err) {
-              return done(err)
-            }
-            expect(binary).to.equal(false)
-            expect(encoding).to.equal('utf-8')
-            done()
-          }
-        )
+      it('should classify utf8 files with ascii control chars as utf-8', async function () {
+        const { binary, encoding } =
+          await this.FileTypeManager.promises.getType(
+            '/file.tex',
+            'utf8-control-chars.tex',
+            null
+          )
+
+        expect(binary).to.equal(false)
+        expect(encoding).to.equal('utf-8')
       })
     })
 
@@ -342,189 +280,132 @@ describe('FileTypeManager', function () {
         '/tex',
       ]
       BINARY_FILENAMES.forEach(filename => {
-        it(`should classify ${filename} as binary`, function (done) {
-          this.FileTypeManager.getType(
+        it(`should classify ${filename} as binary`, async function () {
+          const { binary } = await this.FileTypeManager.promises.getType(
             filename,
             'latin1.tex', // even if the content is not binary
-            null,
-            (err, { binary }) => {
-              if (err) {
-                return done(err)
-              }
-              binary.should.equal(true)
-              done()
-            }
+            null
           )
+
+          binary.should.equal(true)
         })
       })
 
-      it('should not try to get the character encoding', function (done) {
-        this.FileTypeManager.getType('/file.png', 'utf8.tex', null, err => {
-          if (err) {
-            return done(err)
-          }
-          sinon.assert.notCalled(this.isUtf8)
-          done()
-        })
+      it('should not try to get the character encoding', async function () {
+        await this.FileTypeManager.promises.getType(
+          '/file.png',
+          'utf8.tex',
+          null
+        )
+
+        sinon.assert.notCalled(this.isUtf8)
       })
 
-      it('should recognise new binary files as binary', function (done) {
-        this.FileTypeManager.getType(
+      it('should recognise new binary files as binary', async function () {
+        const { binary } = await this.FileTypeManager.promises.getType(
           '/file.py',
           'latin1.tex',
-          null,
-          (err, { binary }) => {
-            if (err) {
-              return done(err)
-            }
-            binary.should.equal(true)
-            done()
-          }
+          null
         )
+
+        binary.should.equal(true)
       })
 
-      it('should recognise existing binary files as binary', function (done) {
-        this.FileTypeManager.getType(
+      it('should recognise existing binary files as binary', async function () {
+        const { binary } = await this.FileTypeManager.promises.getType(
           '/file.py',
           'latin1.tex',
-          'file',
-          (err, { binary }) => {
-            if (err) {
-              return done(err)
-            }
-            binary.should.equal(true)
-            done()
-          }
+          'file'
         )
+
+        binary.should.equal(true)
       })
 
-      it('should preserve existing non-binary files as non-binary', function (done) {
-        this.FileTypeManager.getType(
+      it('should preserve existing non-binary files as non-binary', async function () {
+        const { binary } = await this.FileTypeManager.promises.getType(
           '/file.py',
           'latin1.tex',
-          'doc',
-          (err, { binary }) => {
-            if (err) {
-              return done(err)
-            }
-            binary.should.equal(false)
-            done()
-          }
+          'doc'
         )
+
+        binary.should.equal(false)
       })
     })
   })
 
   describe('shouldIgnore', function () {
-    it('should ignore tex auxiliary files', function (done) {
-      this.FileTypeManager.shouldIgnore('file.aux', (err, ignore) => {
-        if (err) {
-          return done(err)
-        }
-        ignore.should.equal(true)
-        done()
-      })
+    it('should ignore tex auxiliary files', async function () {
+      const ignore =
+        await this.FileTypeManager.promises.shouldIgnore('file.aux')
+      ignore.should.equal(true)
     })
 
-    it('should ignore dotfiles', function (done) {
-      this.FileTypeManager.shouldIgnore('path/.git', (err, ignore) => {
-        if (err) {
-          return done(err)
-        }
-        ignore.should.equal(true)
-        done()
-      })
+    it('should ignore dotfiles', async function () {
+      const ignore =
+        await this.FileTypeManager.promises.shouldIgnore('path/.git')
+
+      ignore.should.equal(true)
     })
 
-    it('should ignore .git directories and contained files', function (done) {
-      this.FileTypeManager.shouldIgnore('path/.git/info', (err, ignore) => {
-        if (err) {
-          return done(err)
-        }
-        ignore.should.equal(true)
-        done()
-      })
+    it('should ignore .git directories and contained files', async function () {
+      const ignore =
+        await this.FileTypeManager.promises.shouldIgnore('path/.git/info')
+
+      ignore.should.equal(true)
     })
 
-    it('should not ignore .latexmkrc dotfile', function (done) {
-      this.FileTypeManager.shouldIgnore('path/.latexmkrc', (err, ignore) => {
-        if (err) {
-          return done(err)
-        }
-        ignore.should.equal(false)
-        done()
-      })
+    it('should not ignore .latexmkrc dotfile', async function () {
+      const ignore =
+        await this.FileTypeManager.promises.shouldIgnore('path/.latexmkrc')
+
+      ignore.should.equal(false)
     })
 
-    it('should ignore __MACOSX', function (done) {
-      this.FileTypeManager.shouldIgnore('path/__MACOSX', (err, ignore) => {
-        if (err) {
-          return done(err)
-        }
-        ignore.should.equal(true)
-        done()
-      })
+    it('should ignore __MACOSX', async function () {
+      const ignore =
+        await this.FileTypeManager.promises.shouldIgnore('path/__MACOSX')
+
+      ignore.should.equal(true)
     })
 
-    it('should ignore synctex files', function (done) {
-      this.FileTypeManager.shouldIgnore('file.synctex', (err, ignore) => {
-        if (err) {
-          return done(err)
-        }
-        ignore.should.equal(true)
-        done()
-      })
+    it('should ignore synctex files', async function () {
+      const ignore =
+        await this.FileTypeManager.promises.shouldIgnore('file.synctex')
+
+      ignore.should.equal(true)
     })
 
-    it('should ignore synctex(busy) files', function (done) {
-      this.FileTypeManager.shouldIgnore('file.synctex(busy)', (err, ignore) => {
-        if (err) {
-          return done(err)
-        }
-        ignore.should.equal(true)
-        done()
-      })
+    it('should ignore synctex(busy) files', async function () {
+      const ignore =
+        await this.FileTypeManager.promises.shouldIgnore('file.synctex(busy)')
+
+      ignore.should.equal(true)
     })
 
-    it('should not ignore .tex files', function (done) {
-      this.FileTypeManager.shouldIgnore('file.tex', (err, ignore) => {
-        if (err) {
-          return done(err)
-        }
-        ignore.should.equal(false)
-        done()
-      })
+    it('should not ignore .tex files', async function () {
+      const ignore =
+        await this.FileTypeManager.promises.shouldIgnore('file.tex')
+
+      ignore.should.equal(false)
     })
 
-    it('should ignore the case of the extension', function (done) {
-      this.FileTypeManager.shouldIgnore('file.AUX', (err, ignore) => {
-        if (err) {
-          return done(err)
-        }
-        ignore.should.equal(true)
-        done()
-      })
+    it('should ignore the case of the extension', async function () {
+      const ignore =
+        await this.FileTypeManager.promises.shouldIgnore('file.AUX')
+
+      ignore.should.equal(true)
     })
 
-    it('should not ignore files with an ignored extension as full name', function (done) {
-      this.FileTypeManager.shouldIgnore('dvi', (err, ignore) => {
-        if (err) {
-          return done(err)
-        }
-        ignore.should.equal(false)
-        done()
-      })
+    it('should not ignore files with an ignored extension as full name', async function () {
+      const ignore = await this.FileTypeManager.promises.shouldIgnore('dvi')
+      ignore.should.equal(false)
     })
 
-    it('should not ignore directories with an ignored extension as full name', function (done) {
+    it('should not ignore directories with an ignored extension as full name', async function () {
       this.stats.isDirectory.returns(true)
-      this.FileTypeManager.shouldIgnore('dvi', (err, ignore) => {
-        if (err) {
-          return done(err)
-        }
-        ignore.should.equal(false)
-        done()
-      })
+      const ignore = await this.FileTypeManager.promises.shouldIgnore('dvi')
+
+      ignore.should.equal(false)
     })
   })
 })

+ 16 - 10
services/web/test/unit/src/User/ThirdPartyIdentityManagerTests.js

@@ -2,6 +2,9 @@ const sinon = require('sinon')
 const { expect } = require('chai')
 const SandboxedModule = require('sandboxed-module')
 const OError = require('@overleaf/o-error')
+const {
+  ThirdPartyUserNotFoundError,
+} = require('../../../../app/src/Features/Errors/Errors')
 const modulePath =
   '../../../../app/src/Features/User/ThirdPartyIdentityManager.js'
 
@@ -77,16 +80,19 @@ describe('ThirdPartyIdentityManager', function () {
         expect(user).to.deep.equal(this.user)
       })
     })
-    it('should return ThirdPartyUserNotFoundError when no user linked', function (done) {
-      this.ThirdPartyIdentityManager.getUser(
-        'google',
-        'an-id-not-linked',
-        (error, user) => {
-          expect(error).to.exist
-          expect(error.name).to.equal('ThirdPartyUserNotFoundError')
-          done()
-        }
-      )
+    it('should return ThirdPartyUserNotFoundError when no user linked', async function () {
+      let error
+
+      try {
+        await this.ThirdPartyIdentityManager.promises.getUser(
+          'google',
+          'an-id-not-linked'
+        )
+      } catch (err) {
+        error = err
+      }
+
+      expect(error).to.be.instanceOf(ThirdPartyUserNotFoundError)
     })
   })
   describe('link', function () {

Algunos archivos no se mostraron porque demasiados archivos cambiaron en este cambio