Просмотр исходного кода

Promisify tests

GitOrigin-RevId: 6f413f4c5ef8d034b4e94afacdf2d7b43c3a8830
Andrew Rumble 1 год назад
Родитель
Сommit
2ad9f36706
25 измененных файлов с 2873 добавлено и 3348 удалено
  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,
     upsertFile,
     upsertFileWithPath,
     upsertFileWithPath,
     appendToDocWithPath: appendToDoc,
     appendToDocWithPath: appendToDoc,
+    setMainBibliographyDoc,
   },
   },
 
 
   async _addDocAndSendToTpds(projectId, folderId, doc, userId) {
   async _addDocAndSendToTpds(projectId, folderId, doc, userId) {

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

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

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

@@ -59,47 +59,34 @@ describe('BrandVariationsHandler', function () {
   })
   })
 
 
   describe('getBrandVariationById', 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())
       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(
       this.V1Api.request.callsArgWith(
         1,
         1,
         null,
         null,
         { statusCode: 200 },
         { statusCode: 200 },
         this.mockedBrandVariationDetails
         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.mockedBrandVariationDetails.logo_url = '/journal-logo.png'
       this.V1Api.request.callsArgWith(
       this.V1Api.request.callsArgWith(
         1,
         1,
@@ -107,20 +94,16 @@ describe('BrandVariationsHandler', function () {
         { statusCode: 200 },
         { statusCode: 200 },
         this.mockedBrandVariationDetails
         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 =
       this.mockedBrandVariationDetails.submit_button_html =
         '<br class="break"/><strong style="color:#B39500">AGU Journal</strong><iframe>hello</iframe>'
         '<br class="break"/><strong style="color:#B39500">AGU Journal</strong><iframe>hello</iframe>'
       this.V1Api.request.callsArgWith(
       this.V1Api.request.callsArgWith(
@@ -129,14 +112,10 @@ describe('BrandVariationsHandler', function () {
         { statusCode: 200 },
         { statusCode: 200 },
         this.mockedBrandVariationDetails
         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: {
           thread1: {
             resolved: true,
             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
       this.ClsiFormatChecker._checkForConflictingPaths = sinon
         .stub()
         .stub()
         .callsArgWith(1, null)
         .callsArgWith(1, null)
       this.ClsiFormatChecker._checkDocsAreUnderSizeLimit = sinon
       this.ClsiFormatChecker._checkDocsAreUnderSizeLimit = sinon
         .stub()
         .stub()
         .callsArgWith(1)
         .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
       this.ClsiFormatChecker._checkForConflictingPaths = sinon
         .stub()
         .stub()
         .callsArgWith(1, null, [])
         .callsArgWith(1, null, [])
       this.ClsiFormatChecker._checkDocsAreUnderSizeLimit = sinon
       this.ClsiFormatChecker._checkDocsAreUnderSizeLimit = sinon
         .stub()
         .stub()
         .callsArgWith(1, null, {})
         .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
       this.ClsiFormatChecker._checkForConflictingPaths = sinon
         .stub()
         .stub()
         .callsArgWith(1, null, [{ path: 'somewhere/main.tex' }])
         .callsArgWith(1, null, [{ path: 'somewhere/main.tex' }])
       this.ClsiFormatChecker._checkDocsAreUnderSizeLimit = sinon
       this.ClsiFormatChecker._checkDocsAreUnderSizeLimit = sinon
         .stub()
         .stub()
         .callsArgWith(1, null, {})
         .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
       this.ClsiFormatChecker._checkForConflictingPaths = sinon
         .stub()
         .stub()
         .callsArgWith(1, null, [])
         .callsArgWith(1, null, [])
@@ -112,15 +103,13 @@ describe('ClsiFormatChecker', function () {
           resources: [{ 'a.tex': 'a.tex' }, { 'b.tex': 'b.tex' }],
           resources: [{ 'a.tex': 'a.tex' }, { 'b.tex': 'b.tex' }],
           totalSize: 1000000,
           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 () {
     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({
         this.resources.push({
           path: 'stuff/image',
           path: 'stuff/image',
           url: 'http://somwhere.com',
           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({
         this.resources.push({
           path: 'stuff',
           path: 'stuff',
           content: 'other 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({
         this.resources.push({
           path: 'stuf',
           path: 'stuf',
           content: 'other stuff',
           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 () {
     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({
         this.resources.push({
           path: 'massive.tex',
           path: 'massive.tex',
           content: 'hello world'.repeat(833333), // over 5mb limit
           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({
         this.resources.push({
           path: 'massive.tex',
           path: 'massive.tex',
           content: 'x'.repeat(2 * 1000 * 1000),
           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 () {
     describe('when the project has been recently compiled', function () {
-      it('should return', function (done) {
+      it('should return', async function () {
         this.CompileManager._checkIfAutoCompileLimitHasBeenHit = async (
         this.CompileManager._checkIfAutoCompileLimitHasBeenHit = async (
           isAutoCompile,
           isAutoCompile,
           compileGroup
           compileGroup
@@ -147,32 +147,27 @@ describe('CompileManager', function () {
         this.CompileManager._checkIfRecentlyCompiled = sinon
         this.CompileManager._checkIfRecentlyCompiled = sinon
           .stub()
           .stub()
           .resolves(true)
           .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 () {
     describe('should check the rate limit', function () {
-      it('should return', function (done) {
+      it('should return', async function () {
         this.CompileManager._checkIfAutoCompileLimitHasBeenHit = sinon
         this.CompileManager._checkIfAutoCompileLimitHasBeenHit = sinon
           .stub()
           .stub()
           .resolves(false)
           .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 () {
       beforeEach(function () {
         this.features.compileGroup = 'priority'
         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 SandboxedModule = require('sandboxed-module')
 const sinon = require('sinon')
 const sinon = require('sinon')
 const { expect } = require('chai')
 const { expect } = require('chai')
@@ -23,158 +11,143 @@ describe('CooldownManager', function () {
     this.projectId = 'abcdefg'
     this.projectId = 'abcdefg'
     this.rclient = { set: sinon.stub(), get: sinon.stub() }
     this.rclient = { set: sinon.stub(), get: sinon.stub() }
     this.RedisWrapper = { client: () => this.rclient }
     this.RedisWrapper = { client: () => this.rclient }
-    return (this.CooldownManager = SandboxedModule.require(modulePath, {
+    this.CooldownManager = SandboxedModule.require(modulePath, {
       requires: {
       requires: {
         '../../infrastructure/RedisWrapper': this.RedisWrapper,
         '../../infrastructure/RedisWrapper': this.RedisWrapper,
       },
       },
-    }))
+    })
   })
   })
 
 
   describe('_buildKey', function () {
   describe('_buildKey', function () {
     it('should build a properly formatted redis key', 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 () {
   describe('isProjectOnCooldown', function () {
-    beforeEach(function () {
-      return (this.call = cb => {
-        return this.CooldownManager.isProjectOnCooldown(this.projectId, cb)
-      })
-    })
-
     describe('when project is on cooldown', function () {
     describe('when project is on cooldown', function () {
       beforeEach(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 () {
     describe('when project is not on cooldown', function () {
       beforeEach(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 () {
     describe('when rclient.get produces an error', function () {
       beforeEach(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 () {
   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 () {
     describe('when rclient.set does not produce an error', function () {
       beforeEach(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 () {
     describe('when rclient.set produces an error', function () {
       beforeEach(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 sinon = require('sinon')
 const modulePath = '../../../../app/src/Features/Docstore/DocstoreManager'
 const modulePath = '../../../../app/src/Features/Docstore/DocstoreManager'
 const SandboxedModule = require('sandboxed-module')
 const SandboxedModule = require('sandboxed-module')
+const { expect } = require('chai')
 const Errors = require('../../../../app/src/Features/Errors/Errors')
 const Errors = require('../../../../app/src/Features/Errors/Errors')
 const tk = require('timekeeper')
 const tk = require('timekeeper')
 
 
@@ -26,7 +27,6 @@ describe('DocstoreManager', function () {
 
 
     this.project_id = 'project-id-123'
     this.project_id = 'project-id-123'
     this.doc_id = 'doc-id-123'
     this.doc_id = 'doc-id-123'
-    this.callback = sinon.stub()
   })
   })
 
 
   describe('deleteDoc', function () {
   describe('deleteDoc', function () {
@@ -39,16 +39,15 @@ describe('DocstoreManager', function () {
         tk.reset()
         tk.reset()
       })
       })
 
 
-      beforeEach(function () {
+      beforeEach(async function () {
         this.request.patch = sinon
         this.request.patch = sinon
           .stub()
           .stub()
           .callsArgWith(1, null, { statusCode: 204 }, '')
           .callsArgWith(1, null, { statusCode: 204 }, '')
-        this.DocstoreManager.deleteDoc(
+        await this.DocstoreManager.promises.deleteDoc(
           this.project_id,
           this.project_id,
           this.doc_id,
           this.doc_id,
           'wombat.tex',
           'wombat.tex',
-          new Date(),
-          this.callback
+          new Date()
         )
         )
       })
       })
 
 
@@ -61,10 +60,6 @@ describe('DocstoreManager', function () {
           })
           })
           .should.equal(true)
           .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 () {
     describe('with a failed response code', function () {
@@ -72,28 +67,27 @@ describe('DocstoreManager', function () {
         this.request.patch = sinon
         this.request.patch = sinon
           .stub()
           .stub()
           .callsArgWith(1, null, { statusCode: 500 }, '')
           .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
         this.request.patch = sinon
           .stub()
           .stub()
           .callsArgWith(1, null, { statusCode: 404 }, '')
           .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
       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
         this.request.post = sinon
           .stub()
           .stub()
           .callsArgWith(
           .callsArgWith(
@@ -147,13 +139,12 @@ describe('DocstoreManager', function () {
             { statusCode: 204 },
             { statusCode: 204 },
             { modified: this.modified, rev: this.rev }
             { modified: this.modified, rev: this.rev }
           )
           )
-        this.DocstoreManager.updateDoc(
+        this.updateDocResponse = await this.DocstoreManager.promises.updateDoc(
           this.project_id,
           this.project_id,
           this.doc_id,
           this.doc_id,
           this.lines,
           this.lines,
           this.version,
           this.version,
-          this.ranges,
-          this.callback
+          this.ranges
         )
         )
       })
       })
 
 
@@ -171,10 +162,12 @@ describe('DocstoreManager', function () {
           .should.equal(true)
           .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
         this.request.post = sinon
           .stub()
           .stub()
           .callsArgWith(1, null, { statusCode: 500 }, '')
           .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 () {
     describe('with a successful response code', function () {
-      beforeEach(function () {
+      beforeEach(async function () {
         this.request.get = sinon
         this.request.get = sinon
           .stub()
           .stub()
           .callsArgWith(1, null, { statusCode: 204 }, this.doc)
           .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 () {
       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
         this.request.get = sinon
           .stub()
           .stub()
           .callsArgWith(1, null, { statusCode: 500 }, '')
           .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 () {
     describe('with include_deleted=true', function () {
-      beforeEach(function () {
+      beforeEach(async function () {
         this.request.get = sinon
         this.request.get = sinon
           .stub()
           .stub()
           .callsArgWith(1, null, { statusCode: 204 }, this.doc)
           .callsArgWith(1, null, { statusCode: 204 }, this.doc)
-        this.DocstoreManager.getDoc(
+        this.getDocResponse = await this.DocstoreManager.promises.getDoc(
           this.project_id,
           this.project_id,
           this.doc_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 () {
     describe('with peek=true', function () {
-      beforeEach(function () {
+      beforeEach(async function () {
         this.request.get = sinon
         this.request.get = sinon
           .stub()
           .stub()
           .callsArgWith(1, null, { statusCode: 204 }, this.doc)
           .callsArgWith(1, null, { statusCode: 204 }, this.doc)
-        this.DocstoreManager.getDoc(
+        await this.DocstoreManager.promises.getDoc(
           this.project_id,
           this.project_id,
           this.doc_id,
           this.doc_id,
-          { peek: true },
-          this.callback
+          {
+            peek: true,
+          }
         )
         )
       })
       })
 
 
@@ -323,24 +327,30 @@ describe('DocstoreManager', function () {
         this.request.get = sinon
         this.request.get = sinon
           .stub()
           .stub()
           .callsArgWith(1, null, { statusCode: 404 }, '')
           .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('getAllDocs', function () {
     describe('with a successful response code', function () {
     describe('with a successful response code', function () {
-      beforeEach(function () {
+      let getAllDocsResult
+      beforeEach(async function () {
         this.request.get = sinon
         this.request.get = sinon
           .stub()
           .stub()
           .callsArgWith(
           .callsArgWith(
@@ -349,7 +359,9 @@ describe('DocstoreManager', function () {
             { statusCode: 204 },
             { statusCode: 204 },
             (this.docs = [{ _id: 'mock-doc-id' }])
             (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 () {
       it('should get all the project docs in the docstore api', function () {
@@ -362,8 +374,8 @@ describe('DocstoreManager', function () {
           .should.equal(true)
           .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
         this.request.get = sinon
           .stub()
           .stub()
           .callsArgWith(1, null, { statusCode: 500 }, '')
           .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('getAllDeletedDocs', function () {
     describe('with a successful response code', 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.docs = [{ _id: 'mock-doc-id', name: 'foo.tex' }]
         this.request.get = sinon
         this.request.get = sinon
           .stub()
           .stub()
           .callsArgWith(1, null, { statusCode: 200 }, this.docs)
           .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 () {
       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 () {
     describe('with an error', function () {
-      beforeEach(function (done) {
-        this.callback.callsFake(() => done())
+      beforeEach(async function () {
         this.request.get = sinon
         this.request.get = sinon
           .stub()
           .stub()
           .callsArgWith(1, new Error('connect failed'))
           .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 () {
     describe('with a failed response code', function () {
-      beforeEach(function (done) {
-        this.callback.callsFake(() => done())
+      beforeEach(function () {
         this.request.get = sinon
         this.request.get = sinon
           .stub()
           .stub()
           .callsArgWith(1, null, { statusCode: 500 })
           .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('getAllRanges', function () {
     describe('with a successful response code', function () {
     describe('with a successful response code', function () {
-      beforeEach(function () {
+      let getAllRangesResult
+      beforeEach(async function () {
         this.request.get = sinon
         this.request.get = sinon
           .stub()
           .stub()
           .callsArgWith(
           .callsArgWith(
@@ -469,7 +487,9 @@ describe('DocstoreManager', function () {
             { statusCode: 204 },
             { statusCode: 204 },
             (this.docs = [{ _id: 'mock-doc-id', ranges: 'mock-ranges' }])
             (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 () {
       it('should get all the project doc ranges in the docstore api', function () {
@@ -482,8 +502,8 @@ describe('DocstoreManager', function () {
           .should.equal(true)
           .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
         this.request.get = sinon
           .stub()
           .stub()
           .callsArgWith(1, null, { statusCode: 500 }, '')
           .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
         this.request.post = sinon
           .stub()
           .stub()
           .callsArgWith(1, null, { statusCode: 204 })
           .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
         this.request.post = sinon
           .stub()
           .stub()
           .callsArgWith(1, null, { statusCode: 500 })
           .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
         this.request.post = sinon
           .stub()
           .stub()
           .callsArgWith(1, null, { statusCode: 204 })
           .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
         this.request.post = sinon
           .stub()
           .stub()
           .callsArgWith(1, null, { statusCode: 500 })
           .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
         this.request.post = sinon
           .stub()
           .stub()
           .callsArgWith(1, null, { statusCode: 204 })
           .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
         this.request.post = sinon
           .stub()
           .stub()
           .callsArgWith(1, null, { statusCode: 500 })
           .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'
+        )
       })
       })
     })
     })
   })
   })

Разница между файлами не показана из-за своего большого размера
+ 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)
       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({
       this.fs.createReadStream.returns({
         pipe() {},
         pipe() {},
         on(type, cb) {
         on(type, cb) {
@@ -104,20 +104,17 @@ describe('FileStoreHandler', function () {
           }
           }
         },
         },
       })
       })
-      this.handler.uploadFileFromDisk(
+      await this.handler.promises.uploadFileFromDisk(
         this.projectId,
         this.projectId,
         this.fileArgs,
         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({
       this.fs.createReadStream.returns({
         pipe() {},
         pipe() {},
         on(type, cb) {
         on(type, cb) {
@@ -126,21 +123,18 @@ describe('FileStoreHandler', function () {
           }
           }
         },
         },
       })
       })
-      this.handler.uploadFileFromDisk(
+      await this.handler.promises.uploadFileFromDisk(
         this.projectId,
         this.projectId,
         this.fileArgs,
         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 () {
     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({
         this.fs.createReadStream.returns({
           pipe() {},
           pipe() {},
           on(type, cb) {
           on(type, cb) {
@@ -150,26 +144,23 @@ describe('FileStoreHandler', function () {
           },
           },
         })
         })
         this.Features.hasFeature.withArgs('project-history-blobs').returns(true)
         this.Features.hasFeature.withArgs('project-history-blobs').returns(true)
-        this.handler.uploadFileFromDisk(
+        await this.handler.promises.uploadFileFromDisk(
           this.projectId,
           this.projectId,
           this.fileArgs,
           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 () {
     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({
         this.fs.createReadStream.returns({
           pipe() {},
           pipe() {},
           on(type, cb) {
           on(type, cb) {
@@ -178,15 +169,12 @@ describe('FileStoreHandler', function () {
             }
             }
           },
           },
         })
         })
-        this.handler.uploadFileFromDisk(
+        await this.handler.promises.uploadFileFromDisk(
           this.projectId,
           this.projectId,
           this.fileArgs,
           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.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)
         const fileUrl = this.getFileUrl(this.projectId, this.fileId)
         this.fs.createReadStream.returns({
         this.fs.createReadStream.returns({
-          pipe() {},
-          on(type, cb) {
+          pipe: sinon.stub(),
+          on: sinon.stub((type, cb) => {
             if (type === 'open') {
             if (type === 'open') {
               cb()
               cb()
             }
             }
-          },
+          }),
         })
         })
-        this.handler.uploadFileFromDisk(
+        await this.handler.promises.uploadFileFromDisk(
           this.projectId,
           this.projectId,
           this.fileArgs,
           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)
         const fileUrl = this.getFileUrl(this.projectId, this.fileId)
         this.fs.createReadStream.returns({
         this.fs.createReadStream.returns({
-          pipe() {},
-          on(type, cb) {
+          pipe: sinon.stub(),
+          on: sinon.stub((type, cb) => {
             if (type === 'open') {
             if (type === 'open') {
               cb()
               cb()
             }
             }
-          },
+          }),
         })
         })
-        this.handler.uploadFileFromDisk(
+        const { url, fileRef } = await this.handler.promises.uploadFileFromDisk(
           this.projectId,
           this.projectId,
           this.fileArgs,
           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 () {
       describe('when upload to filestore fails', function () {
         beforeEach(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.callCount = 0
           this.fs.createReadStream.returns({
           this.fs.createReadStream.returns({
-            pipe() {},
-            on(type, cb) {
+            pipe: sinon.stub(),
+            on: sinon.stub((type, cb) => {
               if (type === 'open') {
               if (type === 'open') {
                 cb()
                 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 () {
       beforeEach(function () {
         this.Features.hasFeature.withArgs('filestore').returns(false)
         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.projectId,
           this.fileArgs,
           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.projectId,
           this.fileArgs,
           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)
         const fileUrl = this.getFileUrl(this.projectId, this.fileId)
-        this.handler.uploadFileFromDisk(
+        const { url, fileRef } = await this.handler.promises.uploadFileFromDisk(
           this.projectId,
           this.projectId,
           this.fileArgs,
           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 () {
     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, {
         this.fs.lstat = sinon.stub().callsArgWith(1, null, {
           isFile() {
           isFile() {
             return false
             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.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 () {
       beforeEach(function () {
         this.Features.hasFeature.withArgs('filestore').returns(true)
         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)
         const projectUrl = this.getProjectUrl(this.projectId)
         this.request.callsArgWith(1, null)
         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 () {
     describe('when filestore is disabled', function () {
       beforeEach(function () {
       beforeEach(function () {
         this.Features.hasFeature.withArgs('filestore').returns(false)
         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)
         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 () {
     describe('when filestore is enabled', function () {
@@ -490,53 +504,36 @@ describe('FileStoreHandler', function () {
         this.Features.hasFeature.withArgs('filestore').returns(true)
         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)
         const fileUrl = this.getFileUrl(this.projectId, this.fileId)
-        this.handler.getFileStream(
+        await this.handler.promises.getFileStream(
           this.projectId,
           this.projectId,
           this.fileId,
           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.projectId,
           this.fileId,
           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.projectId,
           this.fileId,
           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 () {
       describe('when range is specified in query', function () {
@@ -544,22 +541,17 @@ describe('FileStoreHandler', function () {
           this.query = { range: '0-10' }
           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.projectId,
             this.fileId,
             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 () {
         describe('when range is invalid', function () {
@@ -568,21 +560,15 @@ describe('FileStoreHandler', function () {
               this.query = { range: `${r}` }
               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.projectId,
                 this.fileId,
                 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 () {
   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 expectedFileSize = 32432
       const fileUrl =
       const fileUrl =
         this.getFileUrl(this.projectId, this.fileId) + '?from=getFileSize'
         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.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.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'
       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)
       const newFileUrl = this.getFileUrl(this.newProjectId, this.newFileId)
       this.request.callsArgWith(1, null, { statusCode: 200 })
       this.request.callsArgWith(1, null, { statusCode: 200 })
 
 
-      this.handler.copyFile(
+      await this.handler.promises.copyFile(
         this.projectId,
         this.projectId,
         this.fileId,
         this.fileId,
         this.newProjectId,
         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)
       const expectedUrl = this.getFileUrl(this.newProjectId, this.newFileId)
       this.request.callsArgWith(1, null, { statusCode: 200 })
       this.request.callsArgWith(1, null, { statusCode: 200 })
-      this.handler.copyFile(
+      const url = await this.handler.promises.copyFile(
         this.projectId,
         this.projectId,
         this.fileId,
         this.fileId,
         this.newProjectId,
         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.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 () {
   describe('getInstitutionAffiliations', function () {
-    it('get affiliations', function (done) {
+    it('get affiliations', async function () {
       this.institutionId = 123
       this.institutionId = 123
       const responseBody = ['123abc', '456def']
       const responseBody = ['123abc', '456def']
       this.request.yields(null, { statusCode: 200 }, responseBody)
       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.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 () {
   describe('getLicencesForAnalytics', function () {
     const lag = 'daily'
     const lag = 'daily'
     const queryDate = '2017-01-07:00:00.000Z'
     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 = {
       const v1Result = {
         lag: 'daily',
         lag: 'daily',
         date: queryDate,
         date: queryDate,
@@ -96,100 +91,91 @@ describe('InstitutionsAPI', function () {
         },
         },
       }
       }
       this.request.callsArgWith(1, null, { statusCode: 201 }, v1Result)
       this.request.callsArgWith(1, null, { statusCode: 201 }, v1Result)
-      this.InstitutionsAPI.getLicencesForAnalytics(
+      await this.InstitutionsAPI.promises.getLicencesForAnalytics(
         lag,
         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.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 () {
   describe('getUserAffiliations', function () {
-    it('get affiliations', function (done) {
+    it('get affiliations', async function () {
       const responseBody = [{ foo: 'bar' }]
       const responseBody = [{ foo: 'bar' }]
       this.request.callsArgWith(1, null, { statusCode: 201 }, responseBody)
       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' }
       const body = { errors: 'affiliation error message' }
       this.request.callsArgWith(1, null, { statusCode: 503 }, body)
       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.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 () {
   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.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.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 })
       this.fetchNothing.resolves({ status: 201 })
     })
     })
 
 
-    it('add affiliation', function (done) {
+    it('add affiliation', async function () {
       const affiliationOptions = {
       const affiliationOptions = {
         university: { id: 1 },
         university: { id: 1 },
         department: 'Math',
         department: 'Math',
@@ -206,95 +192,104 @@ describe('InstitutionsAPI', function () {
         confirmedAt: new Date(),
         confirmedAt: new Date(),
         entitlement: true,
         entitlement: true,
       }
       }
-      this.InstitutionsAPI.addAffiliation(
+      await this.InstitutionsAPI.promises.addAffiliation(
         this.stubbedUser._id,
         this.stubbedUser._id,
         this.newEmail,
         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 messageFromApi = 'affiliation error message'
       const body = JSON.stringify({ errors: messageFromApi })
       const body = JSON.stringify({ errors: messageFromApi })
       this.fetchNothing.throws({ response: { status: 422 }, body })
       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' }
       const body = { errors: 'affiliation error message' }
       this.fetchNothing.throws({ response: { status: 500 }, body })
       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.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 = {
       const affiliationOptions = {
         confirmedAt: new Date(),
         confirmedAt: new Date(),
       }
       }
 
 
-      this.InstitutionsAPI.addAffiliation(
+      await this.InstitutionsAPI.promises.addAffiliation(
         this.stubbedUser._id,
         this.stubbedUser._id,
         this.newEmail,
         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 } })
       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.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.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 () {
   describe('deleteAffiliations', function () {
-    it('delete affiliations', function (done) {
+    it('delete affiliations', async function () {
       this.request.callsArgWith(1, null, { statusCode: 200 })
       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' }
       const body = { errors: 'affiliation error message' }
       this.request.callsArgWith(1, null, { statusCode: 518 }, body)
       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 })
       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.stubbedUser._id,
         this.newEmail,
         this.newEmail,
         'Student',
         '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 () {
   describe('sendUsersWithReconfirmationsLapsedProcessed', function () {
     const users = ['abc123', 'def456']
     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.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.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 SandboxedModule = require('sandboxed-module')
-const assert = require('assert')
 const { expect } = require('chai')
 const { expect } = require('chai')
 const sinon = require('sinon')
 const sinon = require('sinon')
 const modulePath = require('path').join(
 const modulePath = require('path').join(
@@ -41,45 +28,39 @@ describe('InstitutionsFeatures', function () {
   })
   })
 
 
   describe('hasLicence', function () {
   describe('hasLicence', function () {
-    it('should handle error', function (done) {
+    it('should handle error', async function () {
       this.UserGetter.promises.getUserFullEmails.rejects(new Error('Nope'))
       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.UserGetter.promises.getUserFullEmails.resolves(
         this.emailDataWithoutLicense
         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 = [
       const emailData = [
         { emailHasInstitutionLicence: true },
         { emailHasInstitutionLicence: true },
         { emailHasInstitutionLicence: false },
         { emailHasInstitutionLicence: false },
       ]
       ]
       this.UserGetter.promises.getUserFullEmails.resolves(emailData)
       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)
         .returns(this.testFeatures)
     })
     })
 
 
-    it('should handle error', function (done) {
+    it('should handle error', async function () {
       this.UserGetter.promises.getUserFullEmails.rejects(new Error('Nope'))
       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.UserGetter.promises.getUserFullEmails.resolves(
         this.emailDataWithoutLicense
         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.UserGetter.promises.getUserFullEmails.resolves(
         this.emailDataWithLicense
         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 () {
   describe('getInstitutionsPlan', function () {
-    it('should handle error', function (done) {
+    it('should handle error', async function () {
       this.UserGetter.promises.getUserFullEmails.rejects(new Error('Nope'))
       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.UserGetter.promises.getUserFullEmails.resolves(
         this.emailDataWithoutLicense
         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.UserGetter.promises.getUserFullEmails.resolves(
         this.emailDataWithLicense
         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)
         .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 () {
     describe('NotificationsHandler error', function () {
       let anError
       let anError
@@ -45,19 +42,23 @@ describe('NotificationsBuilder', function () {
         anError = new Error('oops')
         anError = new Error('oops')
         this.handler.createNotification.yields(anError)
         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'
     const subscriptionId = '123123bcabca'
     beforeEach(function () {
     beforeEach(function () {
       this.invite = {
       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(
         .groupInvitation(
           userId,
           userId,
           subscriptionId,
           subscriptionId,
           this.invite.managedUsersEnabled
           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)
         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'
         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 () {
     describe('without portal and without SSO', function () {
@@ -143,27 +139,25 @@ describe('NotificationsBuilder', function () {
         this.request.callsArgWith(1, null, { statusCode: 200 }, this.body)
         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'
         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 SandboxedModule = require('sandboxed-module')
 const { assert } = require('chai')
 const { assert } = require('chai')
 const sinon = require('sinon')
 const sinon = require('sinon')
@@ -18,7 +5,6 @@ const modulePath = require('path').join(
   __dirname,
   __dirname,
   '../../../../app/src/Features/Notifications/NotificationsHandler.js'
   '../../../../app/src/Features/Notifications/NotificationsHandler.js'
 )
 )
-const _ = require('lodash')
 
 
 describe('NotificationsHandler', function () {
 describe('NotificationsHandler', function () {
   const userId = '123nd3ijdks'
   const userId = '123nd3ijdks'
@@ -27,18 +13,18 @@ describe('NotificationsHandler', function () {
 
 
   beforeEach(function () {
   beforeEach(function () {
     this.request = sinon.stub().callsArgWith(1)
     this.request = sinon.stub().callsArgWith(1)
-    return (this.handler = SandboxedModule.require(modulePath, {
+    this.handler = SandboxedModule.require(modulePath, {
       requires: {
       requires: {
         '@overleaf/settings': {
         '@overleaf/settings': {
           apis: { notifications: { url: notificationUrl } },
           apis: { notifications: { url: notificationUrl } },
         },
         },
         request: this.request,
         request: this.request,
       },
       },
-    }))
+    })
   })
   })
 
 
   describe('getUserNotifications', function () {
   describe('getUserNotifications', function () {
-    it('should get unread notifications', function (done) {
+    it('should get unread notifications', async function () {
       const stubbedNotifications = [{ _id: notificationId, user_id: userId }]
       const stubbedNotifications = [{ _id: notificationId, user_id: userId }]
       this.request.callsArgWith(
       this.request.callsArgWith(
         1,
         1,
@@ -46,51 +32,42 @@ describe('NotificationsHandler', function () {
         { statusCode: 200 },
         { statusCode: 200 },
         stubbedNotifications
         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)
       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 () {
   describe('markAsRead', function () {
     beforeEach(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.key = 'some key here'
       this.messageOpts = { value: 12344 }
       this.messageOpts = { value: 12344 }
       this.templateKey = 'renderThisHtml'
       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,
         userId,
         this.key,
         this.key,
         this.templateKey,
         this.templateKey,
         this.messageOpts,
         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 () {
     describe('when expiry date is supplied', function () {
@@ -130,50 +104,46 @@ describe('NotificationsHandler', function () {
         this.key = 'some key here'
         this.key = 'some key here'
         this.messageOpts = { value: 12344 }
         this.messageOpts = { value: 12344 }
         this.templateKey = 'renderThisHtml'
         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,
           userId,
           this.key,
           this.key,
           this.templateKey,
           this.templateKey,
           this.messageOpts,
           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 () {
   describe('markAsReadByKeyOnly', function () {
     beforeEach(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
     })
     })
   })
   })
 
 

Разница между файлами не показана из-за своего большого размера
+ 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 Errors = require('../../../../app/src/Features/Errors/Errors')
 
 
 const project = { _id: '1234566', rootFolder: [] }
 const project = { _id: '1234566', rootFolder: [] }
-class Project {}
 const rootDoc = { name: 'rootDoc', _id: 'das239djd' }
 const rootDoc = { name: 'rootDoc', _id: 'das239djd' }
 const doc1 = { name: 'otherDoc.txt', _id: 'dsad2ddd' }
 const doc1 = { name: 'otherDoc.txt', _id: 'dsad2ddd' }
 const doc2 = { name: 'docname.txt', _id: 'dsad2ddddd' }
 const doc2 = { name: 'docname.txt', _id: 'dsad2ddddd' }
@@ -40,9 +39,6 @@ project.rootDoc_id = rootDoc._id
 
 
 describe('ProjectLocator', function () {
 describe('ProjectLocator', function () {
   beforeEach(function () {
   beforeEach(function () {
-    Project.findById = (projectId, callback) => {
-      callback(null, project)
-    }
     this.ProjectGetter = {
     this.ProjectGetter = {
       getProject: sinon.stub().callsArgWith(2, null, project),
       getProject: sinon.stub().callsArgWith(2, null, project),
     }
     }
@@ -53,7 +49,6 @@ describe('ProjectLocator', function () {
     }
     }
     this.locator = SandboxedModule.require(modulePath, {
     this.locator = SandboxedModule.require(modulePath, {
       requires: {
       requires: {
-        '../../models/Project': { Project },
         '../../models/User': { User: this.User },
         '../../models/User': { User: this.User },
         './ProjectGetter': this.ProjectGetter,
         './ProjectGetter': this.ProjectGetter,
         './ProjectHelper': this.ProjectHelper,
         './ProjectHelper': this.ProjectHelper,
@@ -62,170 +57,152 @@ describe('ProjectLocator', function () {
   })
   })
 
 
   describe('finding a doc', 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 () {
   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,
           project_id: project._id,
-          element_id: secondSubFolder._id,
+          element_id: rootFolder._id,
           type: 'folder',
           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 () {
   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 () {
   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',
       _id: '1234566',
       rootFolder: [rootFolder2],
       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 () {
   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
       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'
       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 () {
   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}`
       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}`
       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}`
       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}`
       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}`
       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()}`
       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()}`
       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}`
       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 = '/'
       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`
       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'
       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 () {
     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}`
         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
       }) // 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`
         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
     }) // 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'
         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) }
         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'
         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 () {
     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}`
         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 { expect } = require('chai')
 const { ObjectId } = require('mongodb-legacy')
 const { ObjectId } = require('mongodb-legacy')
 const sinon = require('sinon')
 const sinon = require('sinon')
@@ -33,16 +20,13 @@ describe('ProjectRootDocManager', function () {
     this.sl_req_id = 'sl-req-id-123'
     this.sl_req_id = 'sl-req-id-123'
     this.callback = sinon.stub()
     this.callback = sinon.stub()
     this.globbyFiles = ['a.tex', 'b.tex', 'main.tex']
     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 = {
     this.fs = {
       readFile: sinon.stub().callsArgWith(2, new Error('file not found')),
       readFile: sinon.stub().callsArgWith(2, new Error('file not found')),
       stat: sinon.stub().callsArgWith(1, null, { size: 100 }),
       stat: sinon.stub().callsArgWith(1, null, { size: 100 }),
     }
     }
-    return (this.ProjectRootDocManager = SandboxedModule.require(modulePath, {
+    this.ProjectRootDocManager = SandboxedModule.require(modulePath, {
       requires: {
       requires: {
         './ProjectEntityHandler': (this.ProjectEntityHandler = {}),
         './ProjectEntityHandler': (this.ProjectEntityHandler = {}),
         './ProjectEntityUpdateHandler': (this.ProjectEntityUpdateHandler = {}),
         './ProjectEntityUpdateHandler': (this.ProjectEntityUpdateHandler = {}),
@@ -56,7 +40,7 @@ describe('ProjectRootDocManager', function () {
         globby: this.globby,
         globby: this.globby,
         fs: this.fs,
         fs: this.fs,
       },
       },
-    }))
+    })
   })
   })
 
 
   describe('setRootDocAutomatically', function () {
   describe('setRootDocAutomatically', function () {
@@ -67,7 +51,7 @@ describe('ProjectRootDocManager', function () {
         .returns(true)
         .returns(true)
     })
     })
     describe('when there is a suitable root doc', function () {
     describe('when there is a suitable root doc', function () {
-      beforeEach(function (done) {
+      beforeEach(async function () {
         this.docs = {
         this.docs = {
           '/chapter1.tex': {
           '/chapter1.tex': {
             _id: this.docId1,
             _id: this.docId1,
@@ -98,27 +82,26 @@ describe('ProjectRootDocManager', function () {
         this.ProjectEntityHandler.getAllDocs = sinon
         this.ProjectEntityHandler.getAllDocs = sinon
           .stub()
           .stub()
           .callsArgWith(1, null, this.docs)
           .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 () {
       it('should check the docs of the project', function () {
-        return this.ProjectEntityHandler.getAllDocs
+        this.ProjectEntityHandler.getAllDocs
           .calledWith(this.project_id)
           .calledWith(this.project_id)
           .should.equal(true)
           .should.equal(true)
       })
       })
 
 
       it('should set the root doc to the doc containing a documentclass', function () {
       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)
           .calledWith(this.project_id, this.docId2)
           .should.equal(true)
           .should.equal(true)
       })
       })
     })
     })
 
 
     describe('when the root doc is an Rtex file', function () {
     describe('when the root doc is an Rtex file', function () {
-      beforeEach(function (done) {
+      beforeEach(async function () {
         this.docs = {
         this.docs = {
           '/chapter1.tex': {
           '/chapter1.tex': {
             _id: this.docId1,
             _id: this.docId1,
@@ -132,21 +115,20 @@ describe('ProjectRootDocManager', function () {
         this.ProjectEntityHandler.getAllDocs = sinon
         this.ProjectEntityHandler.getAllDocs = sinon
           .stub()
           .stub()
           .callsArgWith(1, null, this.docs)
           .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 () {
       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)
           .calledWith(this.project_id, this.docId2)
           .should.equal(true)
           .should.equal(true)
       })
       })
     })
     })
 
 
     describe('when there is no suitable root doc', function () {
     describe('when there is no suitable root doc', function () {
-      beforeEach(function (done) {
+      beforeEach(async function () {
         this.docs = {
         this.docs = {
           '/chapter1.tex': {
           '/chapter1.tex': {
             _id: this.docId1,
             _id: this.docId1,
@@ -160,16 +142,13 @@ describe('ProjectRootDocManager', function () {
         this.ProjectEntityHandler.getAllDocs = sinon
         this.ProjectEntityHandler.getAllDocs = sinon
           .stub()
           .stub()
           .callsArgWith(1, null, this.docs)
           .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 () {
       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
       this.fs.readFile
         .withArgs('/foo/a/a.tex')
         .withArgs('/foo/a/a.tex')
         .callsArgWith(2, null, 'Potato? Potahto. Potootee!')
         .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 () {
     describe('when there is a file in a subfolder', function () {
       beforeEach(function () {
       beforeEach(function () {
         // have to splice globbyFiles weirdly because of the way the stubbed globby method handles references
         // have to splice globbyFiles weirdly because of the way the stubbed globby method handles references
-        return this.globbyFiles.splice(
+        this.globbyFiles.splice(
           0,
           0,
           this.globbyFiles.length,
           this.globbyFiles.length,
           'c.tex',
           '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 })
         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 () {
     describe('when main.tex contains a documentclass', function () {
       beforeEach(function () {
       beforeEach(function () {
-        return this.fs.readFile
+        this.fs.readFile
           .withArgs('/foo/main.tex')
           .withArgs('/foo/main.tex')
           .callsArgWith(2, null, this.documentclassContent)
           .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')
           .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(
         this.globbyFiles.splice(
           0,
           0,
           this.globbyFiles.length,
           this.globbyFiles.length,
@@ -293,18 +258,15 @@ describe('ProjectRootDocManager', function () {
           'nested/chapter1a.tex'
           '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(
         this.globbyFiles.splice(
           0,
           0,
           this.globbyFiles.length,
           this.globbyFiles.length,
@@ -314,227 +276,206 @@ describe('ProjectRootDocManager', function () {
           'nested/chapter1a.tex'
           '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 () {
     describe('when a.tex contains a documentclass', function () {
       beforeEach(function () {
       beforeEach(function () {
-        return this.fs.readFile
+        this.fs.readFile
           .withArgs('/foo/a.tex')
           .withArgs('/foo/a.tex')
           .callsArgWith(2, null, this.documentclassContent)
           .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 () {
     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 () {
     describe('when there is an error reading a file', function () {
       beforeEach(function () {
       beforeEach(function () {
-        return this.fs.readFile
+        this.fs.readFile
           .withArgs('/foo/a.tex')
           .withArgs('/foo/a.tex')
           .callsArgWith(2, new Error('something went wrong'))
           .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('setRootDocFromName', function () {
     describe('when there is a suitable root doc', function () {
     describe('when there is a suitable root doc', function () {
-      beforeEach(function (done) {
+      beforeEach(async function () {
         this.ProjectEntityHandler.getAllDocPathsFromProjectById = sinon
         this.ProjectEntityHandler.getAllDocPathsFromProjectById = sinon
           .stub()
           .stub()
           .callsArgWith(1, null, this.docPaths)
           .callsArgWith(1, null, this.docPaths)
         this.ProjectEntityUpdateHandler.setRootDoc = sinon
         this.ProjectEntityUpdateHandler.setRootDoc = sinon
           .stub()
           .stub()
           .callsArgWith(2)
           .callsArgWith(2)
-        return this.ProjectRootDocManager.setRootDocFromName(
+        await this.ProjectRootDocManager.promises.setRootDocFromName(
           this.project_id,
           this.project_id,
-          '/main.tex',
-          done
+          '/main.tex'
         )
         )
       })
       })
 
 
       it('should check the docs of the project', function () {
       it('should check the docs of the project', function () {
-        return this.ProjectEntityHandler.getAllDocPathsFromProjectById
+        this.ProjectEntityHandler.getAllDocPathsFromProjectById
           .calledWith(this.project_id)
           .calledWith(this.project_id)
           .should.equal(true)
           .should.equal(true)
       })
       })
 
 
       it('should set the root doc to main.tex', function () {
       it('should set the root doc to main.tex', function () {
-        return this.ProjectEntityUpdateHandler.setRootDoc
+        this.ProjectEntityUpdateHandler.setRootDoc
           .calledWith(this.project_id, this.docId2.toString())
           .calledWith(this.project_id, this.docId2.toString())
           .should.equal(true)
           .should.equal(true)
       })
       })
     })
     })
 
 
     describe('when there is a suitable root doc but the leading slash is missing', function () {
     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
         this.ProjectEntityHandler.getAllDocPathsFromProjectById = sinon
           .stub()
           .stub()
           .callsArgWith(1, null, this.docPaths)
           .callsArgWith(1, null, this.docPaths)
         this.ProjectEntityUpdateHandler.setRootDoc = sinon
         this.ProjectEntityUpdateHandler.setRootDoc = sinon
           .stub()
           .stub()
           .callsArgWith(2)
           .callsArgWith(2)
-        return this.ProjectRootDocManager.setRootDocFromName(
+        await this.ProjectRootDocManager.promises.setRootDocFromName(
           this.project_id,
           this.project_id,
-          'main.tex',
-          done
+          'main.tex'
         )
         )
       })
       })
 
 
       it('should check the docs of the project', function () {
       it('should check the docs of the project', function () {
-        return this.ProjectEntityHandler.getAllDocPathsFromProjectById
+        this.ProjectEntityHandler.getAllDocPathsFromProjectById
           .calledWith(this.project_id)
           .calledWith(this.project_id)
           .should.equal(true)
           .should.equal(true)
       })
       })
 
 
       it('should set the root doc to main.tex', function () {
       it('should set the root doc to main.tex', function () {
-        return this.ProjectEntityUpdateHandler.setRootDoc
+        this.ProjectEntityUpdateHandler.setRootDoc
           .calledWith(this.project_id, this.docId2.toString())
           .calledWith(this.project_id, this.docId2.toString())
           .should.equal(true)
           .should.equal(true)
       })
       })
     })
     })
 
 
     describe('when there is a suitable root doc with a basename match', function () {
     describe('when there is a suitable root doc with a basename match', function () {
-      beforeEach(function (done) {
+      beforeEach(async function () {
         this.ProjectEntityHandler.getAllDocPathsFromProjectById = sinon
         this.ProjectEntityHandler.getAllDocPathsFromProjectById = sinon
           .stub()
           .stub()
           .callsArgWith(1, null, this.docPaths)
           .callsArgWith(1, null, this.docPaths)
         this.ProjectEntityUpdateHandler.setRootDoc = sinon
         this.ProjectEntityUpdateHandler.setRootDoc = sinon
           .stub()
           .stub()
           .callsArgWith(2)
           .callsArgWith(2)
-        return this.ProjectRootDocManager.setRootDocFromName(
+        await this.ProjectRootDocManager.promises.setRootDocFromName(
           this.project_id,
           this.project_id,
-          'chapter1a.tex',
-          done
+          'chapter1a.tex'
         )
         )
       })
       })
 
 
       it('should check the docs of the project', function () {
       it('should check the docs of the project', function () {
-        return this.ProjectEntityHandler.getAllDocPathsFromProjectById
+        this.ProjectEntityHandler.getAllDocPathsFromProjectById
           .calledWith(this.project_id)
           .calledWith(this.project_id)
           .should.equal(true)
           .should.equal(true)
       })
       })
 
 
       it('should set the root doc using the basename', function () {
       it('should set the root doc using the basename', function () {
-        return this.ProjectEntityUpdateHandler.setRootDoc
+        this.ProjectEntityUpdateHandler.setRootDoc
           .calledWith(this.project_id, this.docId3.toString())
           .calledWith(this.project_id, this.docId3.toString())
           .should.equal(true)
           .should.equal(true)
       })
       })
     })
     })
 
 
     describe('when there is a suitable root doc but the filename is in quotes', function () {
     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
         this.ProjectEntityHandler.getAllDocPathsFromProjectById = sinon
           .stub()
           .stub()
           .callsArgWith(1, null, this.docPaths)
           .callsArgWith(1, null, this.docPaths)
         this.ProjectEntityUpdateHandler.setRootDoc = sinon
         this.ProjectEntityUpdateHandler.setRootDoc = sinon
           .stub()
           .stub()
           .callsArgWith(2)
           .callsArgWith(2)
-        return this.ProjectRootDocManager.setRootDocFromName(
+        await this.ProjectRootDocManager.promises.setRootDocFromName(
           this.project_id,
           this.project_id,
-          "'main.tex'",
-          done
+          "'main.tex'"
         )
         )
       })
       })
 
 
       it('should check the docs of the project', function () {
       it('should check the docs of the project', function () {
-        return this.ProjectEntityHandler.getAllDocPathsFromProjectById
+        this.ProjectEntityHandler.getAllDocPathsFromProjectById
           .calledWith(this.project_id)
           .calledWith(this.project_id)
           .should.equal(true)
           .should.equal(true)
       })
       })
 
 
       it('should set the root doc to main.tex', function () {
       it('should set the root doc to main.tex', function () {
-        return this.ProjectEntityUpdateHandler.setRootDoc
+        this.ProjectEntityUpdateHandler.setRootDoc
           .calledWith(this.project_id, this.docId2.toString())
           .calledWith(this.project_id, this.docId2.toString())
           .should.equal(true)
           .should.equal(true)
       })
       })
     })
     })
 
 
     describe('when there is no suitable root doc', function () {
     describe('when there is no suitable root doc', function () {
-      beforeEach(function (done) {
+      beforeEach(async function () {
         this.ProjectEntityHandler.getAllDocPathsFromProjectById = sinon
         this.ProjectEntityHandler.getAllDocPathsFromProjectById = sinon
           .stub()
           .stub()
           .callsArgWith(1, null, this.docPaths)
           .callsArgWith(1, null, this.docPaths)
         this.ProjectEntityUpdateHandler.setRootDoc = sinon
         this.ProjectEntityUpdateHandler.setRootDoc = sinon
           .stub()
           .stub()
           .callsArgWith(2)
           .callsArgWith(2)
-        return this.ProjectRootDocManager.setRootDocFromName(
+        await this.ProjectRootDocManager.promises.setRootDocFromName(
           this.project_id,
           this.project_id,
-          'other.tex',
-          done
+          'other.tex'
         )
         )
       })
       })
 
 
       it('should not set the root doc', function () {
       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
       this.ProjectGetter.getProject = sinon
         .stub()
         .stub()
         .callsArgWith(2, null, this.project)
         .callsArgWith(2, null, this.project)
-      return (this.ProjectRootDocManager.setRootDocAutomatically = sinon
+      this.ProjectRootDocManager.setRootDocAutomatically = sinon
         .stub()
         .stub()
-        .callsArgWith(1, null))
+        .callsArgWith(1, null)
     })
     })
 
 
     describe('when the root doc is set', function () {
     describe('when the root doc is set', function () {
       beforeEach(function () {
       beforeEach(function () {
         this.project.rootDoc_id = this.docId2
         this.project.rootDoc_id = this.docId2
-        return this.ProjectRootDocManager.ensureRootDocumentIsSet(
+        this.ProjectRootDocManager.ensureRootDocumentIsSet(
           this.project_id,
           this.project_id,
           this.callback
           this.callback
         )
         )
       })
       })
 
 
       it('should find the project fetching only the rootDoc_id field', function () {
       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 })
           .calledWith(this.project_id, { rootDoc_id: 1 })
           .should.equal(true)
           .should.equal(true)
       })
       })
 
 
       it('should not try to update the project rootDoc_id', function () {
       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
           false
         )
         )
       })
       })
 
 
       it('should call the callback', function () {
       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 () {
     describe('when the root doc is not set', function () {
       beforeEach(function () {
       beforeEach(function () {
-        return this.ProjectRootDocManager.ensureRootDocumentIsSet(
+        this.ProjectRootDocManager.ensureRootDocumentIsSet(
           this.project_id,
           this.project_id,
           this.callback
           this.callback
         )
         )
       })
       })
 
 
       it('should find the project with only the rootDoc_id field', function () {
       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 })
           .calledWith(this.project_id, { rootDoc_id: 1 })
           .should.equal(true)
           .should.equal(true)
       })
       })
 
 
       it('should update the project rootDoc_id', function () {
       it('should update the project rootDoc_id', function () {
-        return this.ProjectRootDocManager.setRootDocAutomatically
+        this.ProjectRootDocManager.setRootDocAutomatically
           .calledWith(this.project_id)
           .calledWith(this.project_id)
           .should.equal(true)
           .should.equal(true)
       })
       })
 
 
       it('should call the callback', function () {
       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 () {
     describe('when the project does not exist', function () {
       beforeEach(function () {
       beforeEach(function () {
         this.ProjectGetter.getProject = sinon.stub().callsArgWith(2, null, null)
         this.ProjectGetter.getProject = sinon.stub().callsArgWith(2, null, null)
-        return this.ProjectRootDocManager.ensureRootDocumentIsSet(
+        this.ProjectRootDocManager.ensureRootDocumentIsSet(
           this.project_id,
           this.project_id,
           this.callback
           this.callback
         )
         )
       })
       })
 
 
       it('should call the callback with an error', function () {
       it('should call the callback with an error', function () {
-        return this.callback
+        this.callback
           .calledWith(
           .calledWith(
             sinon.match
             sinon.match
               .instanceOf(Error)
               .instanceOf(Error)
@@ -645,26 +586,26 @@ describe('ProjectRootDocManager', function () {
           this.ProjectEntityHandler.getDocPathFromProjectByDocId = sinon
           this.ProjectEntityHandler.getDocPathFromProjectByDocId = sinon
             .stub()
             .stub()
             .callsArgWith(2, null, this.docPaths[this.docId2])
             .callsArgWith(2, null, this.docPaths[this.docId2])
-          return this.ProjectRootDocManager.ensureRootDocumentIsValid(
+          this.ProjectRootDocManager.ensureRootDocumentIsValid(
             this.project_id,
             this.project_id,
             this.callback
             this.callback
           )
           )
         })
         })
 
 
         it('should find the project without doc lines', function () {
         it('should find the project without doc lines', function () {
-          return this.ProjectGetter.getProjectWithoutDocLines
+          this.ProjectGetter.getProjectWithoutDocLines
             .calledWith(this.project_id)
             .calledWith(this.project_id)
             .should.equal(true)
             .should.equal(true)
         })
         })
 
 
         it('should not try to update the project rootDoc_id', function () {
         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
             false
           )
           )
         })
         })
 
 
         it('should call the callback', function () {
         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
           this.ProjectEntityHandler.getDocPathFromProjectByDocId = sinon
             .stub()
             .stub()
             .callsArgWith(2, null, null)
             .callsArgWith(2, null, null)
-          return this.ProjectRootDocManager.ensureRootDocumentIsValid(
+          this.ProjectRootDocManager.ensureRootDocumentIsValid(
             this.project_id,
             this.project_id,
             this.callback
             this.callback
           )
           )
         })
         })
 
 
         it('should find the project without doc lines', function () {
         it('should find the project without doc lines', function () {
-          return this.ProjectGetter.getProjectWithoutDocLines
+          this.ProjectGetter.getProjectWithoutDocLines
             .calledWith(this.project_id)
             .calledWith(this.project_id)
             .should.equal(true)
             .should.equal(true)
         })
         })
 
 
         it('should unset the root doc', function () {
         it('should unset the root doc', function () {
-          return this.ProjectEntityUpdateHandler.unsetRootDoc
+          this.ProjectEntityUpdateHandler.unsetRootDoc
             .calledWith(this.project_id)
             .calledWith(this.project_id)
             .should.equal(true)
             .should.equal(true)
         })
         })
 
 
         it('should try to find a new rootDoc', function () {
         it('should try to find a new rootDoc', function () {
-          return this.ProjectRootDocManager.setRootDocAutomatically.called.should.equal(
+          this.ProjectRootDocManager.setRootDocAutomatically.called.should.equal(
             true
             true
           )
           )
         })
         })
 
 
         it('should call the callback', function () {
         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 () {
     describe('when the root doc is not set', function () {
       beforeEach(function () {
       beforeEach(function () {
-        return this.ProjectRootDocManager.ensureRootDocumentIsValid(
+        this.ProjectRootDocManager.ensureRootDocumentIsValid(
           this.project_id,
           this.project_id,
           this.callback
           this.callback
         )
         )
       })
       })
 
 
       it('should find the project without doc lines', function () {
       it('should find the project without doc lines', function () {
-        return this.ProjectGetter.getProjectWithoutDocLines
+        this.ProjectGetter.getProjectWithoutDocLines
           .calledWith(this.project_id)
           .calledWith(this.project_id)
           .should.equal(true)
           .should.equal(true)
       })
       })
 
 
       it('should update the project rootDoc_id', function () {
       it('should update the project rootDoc_id', function () {
-        return this.ProjectRootDocManager.setRootDocAutomatically
+        this.ProjectRootDocManager.setRootDocAutomatically
           .calledWith(this.project_id)
           .calledWith(this.project_id)
           .should.equal(true)
           .should.equal(true)
       })
       })
 
 
       it('should call the callback', function () {
       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
         this.ProjectGetter.getProjectWithoutDocLines = sinon
           .stub()
           .stub()
           .callsArgWith(1, null, null)
           .callsArgWith(1, null, null)
-        return this.ProjectRootDocManager.ensureRootDocumentIsValid(
+        this.ProjectRootDocManager.ensureRootDocumentIsValid(
           this.project_id,
           this.project_id,
           this.callback
           this.callback
         )
         )
       })
       })
 
 
       it('should call the callback with an error', function () {
       it('should call the callback with an error', function () {
-        return this.callback
+        this.callback
           .calledWith(
           .calledWith(
             sinon.match
             sinon.match
               .instanceOf(Error)
               .instanceOf(Error)

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

@@ -42,15 +42,10 @@ describe('PublishersGetter', function () {
   })
   })
 
 
   describe('getManagedPublishers', 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 () {
   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 () {
     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 })
         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'))
         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 () {
   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 () {
   describe('updateAccountEmailAddress, with invalid XML', function () {
-    beforeEach(async function (done) {
+    beforeEach(async function () {
       this.recurlyAccountId = 'account-id-123'
       this.recurlyAccountId = 'account-id-123'
       this.newEmail = '\uD800@example.com'
       this.newEmail = '\uD800@example.com'
       this.apiRequest = sinon
       this.apiRequest = sinon
@@ -307,22 +307,24 @@ describe('RecurlyWrapper', function () {
             body: fixtures['accounts/104'],
             body: fixtures['accounts/104'],
           }
           }
         })
         })
-      done()
     })
     })
 
 
     afterEach(function () {
     afterEach(function () {
       this.RecurlyWrapper.promises.apiRequest.restore()
       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 () {
   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.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 () {
   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.manager._id,
         this.subscription,
         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.manager._id,
         this.subscription,
         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)
       const originalInvite = Object.assign({}, this.teamInvite)
 
 
-      this.TeamInvitesHandler.createInvite(
+      const invite = await this.TeamInvitesHandler.promises.createInvite(
         this.manager._id,
         this.manager._id,
         this.subscription,
         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.manager._id,
         this.subscription,
         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.manager._id,
         this.subscription,
         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.subscription.ssoConfig = new ObjectId('abc123abc123abc123abc123')
       this.SSOConfig.findById
       this.SSOConfig.findById
         .withArgs(this.subscription.ssoConfig)
         .withArgs(this.subscription.ssoConfig)
         .resolves({ enabled: true })
         .resolves({ enabled: true })
 
 
-      this.TeamInvitesHandler.createInvite(
+      await this.TeamInvitesHandler.promises.createInvite(
         this.manager._id,
         this.manager._id,
         this.subscription,
         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.subscription.ssoConfig = new ObjectId('abc123abc123abc123abc123')
       this.SSOConfig.findById
       this.SSOConfig.findById
         .withArgs(this.subscription.ssoConfig)
         .withArgs(this.subscription.ssoConfig)
         .resolves({ enabled: false })
         .resolves({ enabled: false })
 
 
-      this.TeamInvitesHandler.createInvite(
+      await this.TeamInvitesHandler.promises.createInvite(
         this.manager._id,
         this.manager._id,
         this.subscription,
         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 id = new ObjectId('6a6b3a8014829a865bbf700d')
       const managedUsersEnabled = false
       const managedUsersEnabled = false
 
 
@@ -308,22 +283,19 @@ describe('TeamInvitesHandler', function () {
           _id: id,
           _id: id,
         })
         })
 
 
-      this.TeamInvitesHandler.createInvite(
+      const invite = await this.TeamInvitesHandler.promises.createInvite(
         this.manager._id,
         this.manager._id,
         this.subscription,
         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 () {
     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
         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 () {
     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.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 () {
     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.subscription.ssoConfig = 'ssoconfig1'
         this.SSOConfig.findById
         this.SSOConfig.findById
           .withArgs('ssoconfig1')
           .withArgs('ssoconfig1')
           .resolves({ enabled: true })
           .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 () {
   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.manager._id,
         this.subscription,
         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 managedUsersEnabled = false
 
 
       const pendingUser = {
       const pendingUser = {
@@ -492,26 +467,23 @@ describe('TeamInvitesHandler', function () {
         .withArgs(pendingUser.email)
         .withArgs(pendingUser.email)
         .resolves(pendingUser)
         .resolves(pendingUser)
 
 
-      this.TeamInvitesHandler.revokeInvite(
+      await this.TeamInvitesHandler.promises.revokeInvite(
         this.manager._id,
         this.manager._id,
         this.subscription,
         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 () {
     beforeEach(function () {
       this.subscription.invited_emails = [
       this.subscription.invited_emails = [
         'eddard@example.com',
         'eddard@example.com',
@@ -523,58 +495,71 @@ describe('TeamInvitesHandler', function () {
         .resolves([this.subscription])
         .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 () {
   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
       this.LimitationsManager.teamHasReachedMemberLimit = sinon
         .stub()
         .stub()
         .returns(true)
         .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.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 = {
       const member = {
         id: '1a2b',
         id: '1a2b',
         _id: '1a2b',
         _id: '1a2b',
@@ -586,16 +571,23 @@ describe('TeamInvitesHandler', function () {
         .withArgs(member.email)
         .withArgs(member.email)
         .resolves(member)
         .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 () {
   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' }]
       const stubbedTags = [{ name: 'tag1' }, { name: 'tag2' }, { name: 'tag3' }]
       this.TagMock.expects('find')
       this.TagMock.expects('find')
         .once()
         .once()
         .withArgs({ user_id: this.userId })
         .withArgs({ user_id: this.userId })
         .resolves(stubbedTags)
         .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('createTag', function () {
     describe('when insert succeeds', 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')
         this.TagMock.expects('create')
           .withArgs(this.tag)
           .withArgs(this.tag)
           .once()
           .once()
           .resolves(this.tag)
           .resolves(this.tag)
-        this.TagsHandler.createTag(
+        const resultTag = await this.TagsHandler.promises.createTag(
           this.tag.user_id,
           this.tag.user_id,
           this.tag.name,
           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 () {
     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
         // Expect the tag to end up with this truncated name
         this.tag.name = 'a comically long tag that will be truncated intern'
         this.tag.name = 'a comically long tag that will be truncated intern'
         this.TagMock.expects('create')
         this.TagMock.expects('create')
           .withArgs(this.tag)
           .withArgs(this.tag)
           .once()
           .once()
           .resolves(this.tag)
           .resolves(this.tag)
-        this.TagsHandler.createTag(
+        const resultTag = await this.TagsHandler.promises.createTag(
           this.tag.user_id,
           this.tag.user_id,
           // Pass this too-long name
           // Pass this too-long name
           'a comically long tag that will be truncated internally and not throw an error',
           'a comically long tag that will be truncated internally and not throw an error',
           this.tag.color,
           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 () {
     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
         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')
         this.TagMock.expects('create')
           .withArgs(this.tag)
           .withArgs(this.tag)
           .once()
           .once()
@@ -120,26 +114,22 @@ describe('TagsHandler', function () {
           .withArgs({ user_id: this.tag.user_id, name: this.tag.name })
           .withArgs({ user_id: this.tag.user_id, name: this.tag.name })
           .once()
           .once()
           .resolves(this.tag)
           .resolves(this.tag)
-        this.TagsHandler.createTag(
+        const resultTag = await this.TagsHandler.promises.createTag(
           this.tag.user_id,
           this.tag.user_id,
           this.tag.name,
           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('addProjectToTag', function () {
     describe('with a valid tag_id', 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')
         this.TagMock.expects('findOneAndUpdate')
           .once()
           .once()
           .withArgs(
           .withArgs(
@@ -147,23 +137,19 @@ describe('TagsHandler', function () {
             { $addToSet: { project_ids: this.projectId } }
             { $addToSet: { project_ids: this.projectId } }
           )
           )
           .resolves()
           .resolves()
-        this.TagsHandler.addProjectToTag(
+        await this.TagsHandler.promises.addProjectToTag(
           this.userId,
           this.userId,
           this.tagId,
           this.tagId,
-          this.projectId,
-          err => {
-            expect(err).to.not.exist
-            this.TagMock.verify()
-            done()
-          }
+          this.projectId
         )
         )
+        this.TagMock.verify()
       })
       })
     })
     })
   })
   })
 
 
   describe('addProjectsToTag', function () {
   describe('addProjectsToTag', function () {
     describe('with a valid tag_id', 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')
         this.TagMock.expects('findOneAndUpdate')
           .once()
           .once()
           .withArgs(
           .withArgs(
@@ -171,22 +157,18 @@ describe('TagsHandler', function () {
             { $addToSet: { project_ids: { $each: this.projectIds } } }
             { $addToSet: { project_ids: { $each: this.projectIds } } }
           )
           )
           .resolves()
           .resolves()
-        this.TagsHandler.addProjectsToTag(
+        await this.TagsHandler.promises.addProjectsToTag(
           this.userId,
           this.userId,
           this.tagId,
           this.tagId,
-          this.projectIds,
-          err => {
-            expect(err).to.not.exist
-            this.TagMock.verify()
-            done()
-          }
+          this.projectIds
         )
         )
+        this.TagMock.verify()
       })
       })
     })
     })
   })
   })
 
 
   describe('addProjectToTagName', function () {
   describe('addProjectToTagName', function () {
-    it('should call update in mongo', function (done) {
+    it('should call update in mongo', async function () {
       this.TagMock.expects('updateOne')
       this.TagMock.expects('updateOne')
         .once()
         .once()
         .withArgs(
         .withArgs(
@@ -195,22 +177,18 @@ describe('TagsHandler', function () {
           { upsert: true }
           { upsert: true }
         )
         )
         .resolves()
         .resolves()
-      this.TagsHandler.addProjectToTagName(
+      await this.TagsHandler.promises.addProjectToTagName(
         this.tag.userId,
         this.tag.userId,
         this.tag.name,
         this.tag.name,
-        this.projectId,
-        err => {
-          expect(err).to.not.exist
-          this.TagMock.verify()
-          done()
-        }
+        this.projectId
       )
       )
+      this.TagMock.verify()
     })
     })
   })
   })
 
 
   describe('removeProjectFromTag', function () {
   describe('removeProjectFromTag', function () {
     describe('with a valid tag_id', 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')
         this.TagMock.expects('updateOne')
           .once()
           .once()
           .withArgs(
           .withArgs(
@@ -223,23 +201,20 @@ describe('TagsHandler', function () {
             }
             }
           )
           )
           .resolves()
           .resolves()
-        this.TagsHandler.removeProjectFromTag(
+        await this.TagsHandler.promises.removeProjectFromTag(
           this.userId,
           this.userId,
           this.tagId,
           this.tagId,
-          this.projectId,
-          err => {
-            expect(err).to.not.exist
-            this.TagMock.verify()
-            done()
-          }
+          this.projectId
         )
         )
+
+        this.TagMock.verify()
       })
       })
     })
     })
   })
   })
 
 
   describe('removeProjectsFromTag', function () {
   describe('removeProjectsFromTag', function () {
     describe('with a valid tag_id', 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')
         this.TagMock.expects('updateOne')
           .once()
           .once()
           .withArgs(
           .withArgs(
@@ -252,22 +227,18 @@ describe('TagsHandler', function () {
             }
             }
           )
           )
           .resolves()
           .resolves()
-        this.TagsHandler.removeProjectsFromTag(
+        await this.TagsHandler.promises.removeProjectsFromTag(
           this.userId,
           this.userId,
           this.tagId,
           this.tagId,
-          this.projectIds,
-          err => {
-            expect(err).to.not.exist
-            this.TagMock.verify()
-            done()
-          }
+          this.projectIds
         )
         )
+        this.TagMock.verify()
       })
       })
     })
     })
   })
   })
 
 
   describe('removeProjectFromAllTags', function () {
   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')
       this.TagMock.expects('updateMany')
         .once()
         .once()
         .withArgs(
         .withArgs(
@@ -279,20 +250,16 @@ describe('TagsHandler', function () {
           }
           }
         )
         )
         .resolves()
         .resolves()
-      this.TagsHandler.removeProjectFromAllTags(
+      await this.TagsHandler.promises.removeProjectFromAllTags(
         this.userId,
         this.userId,
-        this.projectId,
-        err => {
-          expect(err).to.not.exist
-          this.TagMock.verify()
-          done()
-        }
+        this.projectId
       )
       )
+      this.TagMock.verify()
     })
     })
   })
   })
 
 
   describe('addProjectToTags', function () {
   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 = []
       const tagIds = []
 
 
       this.TagMock.expects('updateMany')
       this.TagMock.expects('updateMany')
@@ -307,38 +274,31 @@ describe('TagsHandler', function () {
           }
           }
         )
         )
         .resolves()
         .resolves()
-      this.TagsHandler.addProjectToTags(
+      await this.TagsHandler.promises.addProjectToTags(
         this.userId,
         this.userId,
         tagIds,
         tagIds,
-        this.projectId,
-        (err, result) => {
-          expect(err).to.not.exist
-          this.TagMock.verify()
-          done()
-        }
+        this.projectId
       )
       )
+      this.TagMock.verify()
     })
     })
   })
   })
 
 
   describe('deleteTag', function () {
   describe('deleteTag', function () {
     describe('with a valid tag_id', 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')
         this.TagMock.expects('deleteOne')
           .once()
           .once()
           .withArgs({ _id: this.tagId, user_id: this.userId })
           .withArgs({ _id: this.tagId, user_id: this.userId })
           .resolves()
           .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('renameTag', function () {
     describe('with a valid tag_id', 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.newName = 'new name'
         this.TagMock.expects('updateOne')
         this.TagMock.expects('updateOne')
           .once()
           .once()
@@ -347,30 +307,31 @@ describe('TagsHandler', function () {
             { $set: { name: this.newName } }
             { $set: { name: this.newName } }
           )
           )
           .resolves()
           .resolves()
-        this.TagsHandler.renameTag(
+        await this.TagsHandler.promises.renameTag(
           this.userId,
           this.userId,
           this.tagId,
           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 () {
     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 () {
     describe('when it is a directory', function () {
       beforeEach(function () {
       beforeEach(function () {
         this.stats.isDirectory.returns(true)
         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 () {
     describe('when it is not a directory', function () {
       beforeEach(function () {
       beforeEach(function () {
         this.stats.isDirectory.returns(false)
         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',
         '/GNUMakefile',
       ]
       ]
       TEXT_FILENAMES.forEach(filename => {
       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,
             filename,
             'utf8.tex',
             '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.stats.size = 2 * 1024 * 1024 // 2MB
-        this.FileTypeManager.getType(
+        const { binary } = await this.FileTypeManager.promises.getType(
           '/file.tex',
           '/file.tex',
           'text-short.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.stats.size = 2 * 1024 * 1024 // 2MB
-        this.FileTypeManager.getType(
+        const { binary } = await this.FileTypeManager.promises.getType(
           '/file.tex',
           '/file.tex',
           'text-smaller.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.stats.size = 2 * 1024 * 1024 // 2MB
-        this.FileTypeManager.getType(
+        const { binary } = await this.FileTypeManager.promises.getType(
           '/file.tex',
           '/file.tex',
           'text-exact.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.stats.size = 2 * 1024 * 1024 // 2MB
-        this.FileTypeManager.getType(
+        const { binary } = await this.FileTypeManager.promises.getType(
           '/file.tex',
           '/file.tex',
           'text-long.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.stats.size = 8 * 1024 * 1024 // 8MB
-        this.FileTypeManager.getType(
+        const { binary } = await this.FileTypeManager.promises.getType(
           '/file.tex',
           '/file.tex',
           'utf8.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.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',
           '/file.tex',
           'utf8.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',
           '/file.tex',
           'latin1.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',
           '/file.tex',
           'utf16.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',
           '/file.tex',
           'latin1-null.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',
           '/file.tex',
           'utf8-null.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',
           '/file.tex',
           'utf8-non-bmp.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',
         '/tex',
       ]
       ]
       BINARY_FILENAMES.forEach(filename => {
       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,
             filename,
             'latin1.tex', // even if the content is not binary
             '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',
           '/file.py',
           'latin1.tex',
           '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',
           '/file.py',
           'latin1.tex',
           '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',
           '/file.py',
           'latin1.tex',
           'latin1.tex',
-          'doc',
-          (err, { binary }) => {
-            if (err) {
-              return done(err)
-            }
-            binary.should.equal(false)
-            done()
-          }
+          'doc'
         )
         )
+
+        binary.should.equal(false)
       })
       })
     })
     })
   })
   })
 
 
   describe('shouldIgnore', function () {
   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.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 { expect } = require('chai')
 const SandboxedModule = require('sandboxed-module')
 const SandboxedModule = require('sandboxed-module')
 const OError = require('@overleaf/o-error')
 const OError = require('@overleaf/o-error')
+const {
+  ThirdPartyUserNotFoundError,
+} = require('../../../../app/src/Features/Errors/Errors')
 const modulePath =
 const modulePath =
   '../../../../app/src/Features/User/ThirdPartyIdentityManager.js'
   '../../../../app/src/Features/User/ThirdPartyIdentityManager.js'
 
 
@@ -77,16 +80,19 @@ describe('ThirdPartyIdentityManager', function () {
         expect(user).to.deep.equal(this.user)
         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 () {
   describe('link', function () {

Некоторые файлы не были показаны из-за большого количества измененных файлов