Jelajahi Sumber

Merge pull request #15719 from overleaf/em-promisify-mongo-manager

Promisify MongoManager

GitOrigin-RevId: e9e71a1865734ae202270d5b4fdaa6ba51fadab6
Eric Mc Sween 2 tahun lalu
induk
melakukan
29d41497ef

+ 121 - 169
services/docstore/app/js/MongoManager.js

@@ -1,28 +1,25 @@
 const { db, ObjectId } = require('./mongodb')
 const { db, ObjectId } = require('./mongodb')
-const logger = require('@overleaf/logger')
-const metrics = require('@overleaf/metrics')
 const Settings = require('@overleaf/settings')
 const Settings = require('@overleaf/settings')
-const OError = require('@overleaf/o-error')
 const Errors = require('./Errors')
 const Errors = require('./Errors')
-const { promisify } = require('util')
+const { callbackify } = require('util')
 
 
 const ARCHIVING_LOCK_DURATION_MS = Settings.archivingLockDurationMs
 const ARCHIVING_LOCK_DURATION_MS = Settings.archivingLockDurationMs
 
 
-function findDoc(projectId, docId, filter, callback) {
-  db.docs.findOne(
+async function findDoc(projectId, docId, filter) {
+  const doc = await db.docs.findOne(
     {
     {
       _id: new ObjectId(docId.toString()),
       _id: new ObjectId(docId.toString()),
       project_id: new ObjectId(projectId.toString()),
       project_id: new ObjectId(projectId.toString()),
     },
     },
     {
     {
       projection: filter,
       projection: filter,
-    },
-    callback
+    }
   )
   )
+  return doc
 }
 }
 
 
-function getProjectsDeletedDocs(projectId, filter, callback) {
-  db.docs
+async function getProjectsDeletedDocs(projectId, filter) {
+  const docs = await db.docs
     .find(
     .find(
       {
       {
         project_id: new ObjectId(projectId.toString()),
         project_id: new ObjectId(projectId.toString()),
@@ -34,10 +31,11 @@ function getProjectsDeletedDocs(projectId, filter, callback) {
         limit: Settings.max_deleted_docs,
         limit: Settings.max_deleted_docs,
       }
       }
     )
     )
-    .toArray(callback)
+    .toArray()
+  return docs
 }
 }
 
 
-function getProjectsDocs(projectId, options, filter, callback) {
+async function getProjectsDocs(projectId, options, filter) {
   const query = { project_id: new ObjectId(projectId.toString()) }
   const query = { project_id: new ObjectId(projectId.toString()) }
   if (!options.include_deleted) {
   if (!options.include_deleted) {
     query.deleted = { $ne: true }
     query.deleted = { $ne: true }
@@ -48,21 +46,23 @@ function getProjectsDocs(projectId, options, filter, callback) {
   if (options.limit) {
   if (options.limit) {
     queryOptions.limit = options.limit
     queryOptions.limit = options.limit
   }
   }
-  db.docs.find(query, queryOptions).toArray(callback)
+  const docs = await db.docs.find(query, queryOptions).toArray()
+  return docs
 }
 }
 
 
-function getArchivedProjectDocs(projectId, maxResults, callback) {
+async function getArchivedProjectDocs(projectId, maxResults) {
   const query = {
   const query = {
     project_id: new ObjectId(projectId.toString()),
     project_id: new ObjectId(projectId.toString()),
     inS3: true,
     inS3: true,
   }
   }
-  db.docs
+  const docs = await db.docs
     .find(query, { projection: { _id: 1 }, limit: maxResults })
     .find(query, { projection: { _id: 1 }, limit: maxResults })
-    .toArray(callback)
+    .toArray()
+  return docs
 }
 }
 
 
-function getNonArchivedProjectDocIds(projectId, callback) {
-  db.docs
+async function getNonArchivedProjectDocIds(projectId) {
+  const docs = await db.docs
     .find(
     .find(
       {
       {
         project_id: new ObjectId(projectId),
         project_id: new ObjectId(projectId),
@@ -71,27 +71,23 @@ function getNonArchivedProjectDocIds(projectId, callback) {
       { projection: { _id: 1 } }
       { projection: { _id: 1 } }
     )
     )
     .map(doc => doc._id)
     .map(doc => doc._id)
-    .toArray(callback)
+    .toArray()
+  return docs
 }
 }
 
 
-function getNonDeletedArchivedProjectDocs(projectId, maxResults, callback) {
+async function getNonDeletedArchivedProjectDocs(projectId, maxResults) {
   const query = {
   const query = {
     project_id: new ObjectId(projectId.toString()),
     project_id: new ObjectId(projectId.toString()),
     deleted: { $ne: true },
     deleted: { $ne: true },
     inS3: true,
     inS3: true,
   }
   }
-  db.docs
+  const docs = await db.docs
     .find(query, { projection: { _id: 1 }, limit: maxResults })
     .find(query, { projection: { _id: 1 }, limit: maxResults })
-    .toArray(callback)
+    .toArray()
+  return docs
 }
 }
 
 
-function upsertIntoDocCollection(
-  projectId,
-  docId,
-  previousRev,
-  updates,
-  callback
-) {
+async function upsertIntoDocCollection(projectId, docId, previousRev, updates) {
   if (previousRev) {
   if (previousRev) {
     const update = {
     const update = {
       $set: updates,
       $set: updates,
@@ -100,51 +96,43 @@ function upsertIntoDocCollection(
     if (updates.lines || updates.ranges) {
     if (updates.lines || updates.ranges) {
       update.$inc = { rev: 1 }
       update.$inc = { rev: 1 }
     }
     }
-    db.docs.updateOne(
+    const result = await db.docs.updateOne(
       {
       {
         _id: new ObjectId(docId),
         _id: new ObjectId(docId),
         project_id: new ObjectId(projectId),
         project_id: new ObjectId(projectId),
         rev: previousRev,
         rev: previousRev,
       },
       },
-      update,
-      (err, result) => {
-        if (err) return callback(err)
-        if (result.matchedCount !== 1) {
-          return callback(new Errors.DocRevValueError())
-        }
-        callback()
-      }
+      update
     )
     )
+    if (result.matchedCount !== 1) {
+      throw new Errors.DocRevValueError()
+    }
   } else {
   } else {
-    db.docs.insertOne(
-      {
+    try {
+      await db.docs.insertOne({
         _id: new ObjectId(docId),
         _id: new ObjectId(docId),
         project_id: new ObjectId(projectId),
         project_id: new ObjectId(projectId),
         rev: 1,
         rev: 1,
         ...updates,
         ...updates,
-      },
-      err => {
-        if (err) {
-          if (err.code === 11000) {
-            // duplicate doc _id
-            return callback(new Errors.DocRevValueError())
-          }
-          return callback(err)
-        }
-        callback()
+      })
+    } catch (err) {
+      if (err.code === 11000) {
+        // duplicate doc _id
+        throw new Errors.DocRevValueError()
+      } else {
+        throw err
       }
       }
-    )
+    }
   }
   }
 }
 }
 
 
-function patchDoc(projectId, docId, meta, callback) {
-  db.docs.updateOne(
+async function patchDoc(projectId, docId, meta) {
+  await db.docs.updateOne(
     {
     {
       _id: new ObjectId(docId),
       _id: new ObjectId(docId),
       project_id: new ObjectId(projectId),
       project_id: new ObjectId(projectId),
     },
     },
-    { $set: meta },
-    callback
+    { $set: meta }
   )
   )
 }
 }
 
 
@@ -154,9 +142,9 @@ function patchDoc(projectId, docId, meta, callback) {
  * This will return null if the doc is not found, if it's already archived or
  * This will return null if the doc is not found, if it's already archived or
  * if the lock can't be acquired.
  * if the lock can't be acquired.
  */
  */
-function getDocForArchiving(projectId, docId, callback) {
+async function getDocForArchiving(projectId, docId) {
   const archivingUntil = new Date(Date.now() + ARCHIVING_LOCK_DURATION_MS)
   const archivingUntil = new Date(Date.now() + ARCHIVING_LOCK_DURATION_MS)
-  db.docs.findOneAndUpdate(
+  const result = await db.docs.findOneAndUpdate(
     {
     {
       _id: new ObjectId(docId),
       _id: new ObjectId(docId),
       project_id: new ObjectId(projectId),
       project_id: new ObjectId(projectId),
@@ -167,27 +155,21 @@ function getDocForArchiving(projectId, docId, callback) {
     {
     {
       projection: { lines: 1, ranges: 1, rev: 1 },
       projection: { lines: 1, ranges: 1, rev: 1 },
       includeResultMetadata: true,
       includeResultMetadata: true,
-    },
-    (err, result) => {
-      if (err) {
-        return callback(err)
-      }
-      callback(null, result.value)
     }
     }
   )
   )
+  return result.value
 }
 }
 
 
 /**
 /**
  * Clear the doc contents from Mongo and release the archiving lock
  * Clear the doc contents from Mongo and release the archiving lock
  */
  */
-function markDocAsArchived(projectId, docId, rev, callback) {
-  db.docs.updateOne(
+async function markDocAsArchived(projectId, docId, rev) {
+  await db.docs.updateOne(
     { _id: new ObjectId(docId), rev },
     { _id: new ObjectId(docId), rev },
     {
     {
       $set: { inS3: true },
       $set: { inS3: true },
       $unset: { lines: 1, ranges: 1, archivingUntil: 1 },
       $unset: { lines: 1, ranges: 1, archivingUntil: 1 },
-    },
-    callback
+    }
   )
   )
 }
 }
 
 
@@ -196,7 +178,7 @@ function markDocAsArchived(projectId, docId, rev, callback) {
  *
  *
  * This checks that the archived doc's rev matches.
  * This checks that the archived doc's rev matches.
  */
  */
-function restoreArchivedDoc(projectId, docId, archivedDoc, callback) {
+async function restoreArchivedDoc(projectId, docId, archivedDoc) {
   const query = {
   const query = {
     _id: new ObjectId(docId),
     _id: new ObjectId(docId),
     project_id: new ObjectId(projectId),
     project_id: new ObjectId(projectId),
@@ -211,60 +193,34 @@ function restoreArchivedDoc(projectId, docId, archivedDoc, callback) {
       inS3: true,
       inS3: true,
     },
     },
   }
   }
-  db.docs.updateOne(query, update, (err, result) => {
-    if (err) {
-      OError.tag(err, 'failed to unarchive doc', {
-        docId,
-        rev: archivedDoc.rev,
-      })
-      return callback(err)
-    }
-    if (result.matchedCount === 0) {
-      return callback(
-        new Errors.DocRevValueError('failed to unarchive doc', {
-          docId,
-          rev: archivedDoc.rev,
-        })
-      )
-    }
-    callback()
-  })
+  const result = await db.docs.updateOne(query, update)
+
+  if (result.matchedCount === 0) {
+    throw new Errors.DocRevValueError('failed to unarchive doc', {
+      docId,
+      rev: archivedDoc.rev,
+    })
+  }
 }
 }
 
 
-function getDocVersion(docId, callback) {
-  db.docOps.findOne(
-    {
-      doc_id: new ObjectId(docId),
-    },
+async function getDocVersion(docId) {
+  const doc = await db.docOps.findOne(
+    { doc_id: new ObjectId(docId) },
     {
     {
       projection: {
       projection: {
         version: 1,
         version: 1,
       },
       },
-    },
-    function (error, doc) {
-      if (error) {
-        return callback(error)
-      }
-      callback(null, (doc && doc.version) || 0)
     }
     }
   )
   )
+  return (doc && doc.version) || 0
 }
 }
 
 
-function getDocRev(docId, callback) {
-  db.docs.findOne(
-    {
-      _id: new ObjectId(docId.toString()),
-    },
-    {
-      projection: { rev: 1 },
-    },
-    function (err, doc) {
-      if (err) {
-        return callback(err)
-      }
-      callback(null, doc && doc.rev)
-    }
+async function getDocRev(docId) {
+  const doc = await db.docs.findOne(
+    { _id: new ObjectId(docId.toString()) },
+    { projection: { rev: 1 } }
   )
   )
+  return doc && doc.rev
 }
 }
 
 
 /**
 /**
@@ -273,68 +229,64 @@ function getDocRev(docId, callback) {
  * Check that the rev of an existing doc is unchanged. If the rev has
  * Check that the rev of an existing doc is unchanged. If the rev has
  * changed, return a DocModifiedError.
  * changed, return a DocModifiedError.
  */
  */
-function checkRevUnchanged(doc, callback) {
-  getDocRev(doc._id, function (err, currentRev) {
-    if (err) return callback(err)
-    if (isNaN(currentRev) || isNaN(doc.rev)) {
-      return callback(
-        new Errors.DocRevValueError('doc rev is NaN', {
-          doc_id: doc._id,
-          rev: doc.rev,
-          currentRev,
-        })
-      )
-    }
-    if (doc.rev !== currentRev) {
-      return callback(
-        new Errors.DocModifiedError('doc rev has changed', {
-          doc_id: doc._id,
-          rev: doc.rev,
-          currentRev,
-        })
-      )
-    }
-    callback()
-  })
+async function checkRevUnchanged(doc) {
+  const currentRev = await getDocRev(doc._id)
+  if (isNaN(currentRev) || isNaN(doc.rev)) {
+    throw new Errors.DocRevValueError('doc rev is NaN', {
+      doc_id: doc._id,
+      rev: doc.rev,
+      currentRev,
+    })
+  }
+  if (doc.rev !== currentRev) {
+    throw new Errors.DocModifiedError('doc rev has changed', {
+      doc_id: doc._id,
+      rev: doc.rev,
+      currentRev,
+    })
+  }
 }
 }
 
 
-function destroyProject(projectId, callback) {
-  db.docs
+async function destroyProject(projectId) {
+  const records = await db.docs
     .find({ project_id: new ObjectId(projectId) }, { projection: { _id: 1 } })
     .find({ project_id: new ObjectId(projectId) }, { projection: { _id: 1 } })
-    .toArray((err, records) => {
-      const docIds = records.map(r => r._id)
-      if (err) {
-        return callback(err)
-      }
-      db.docOps.deleteMany({ doc_id: { $in: docIds } }, err => {
-        if (err) {
-          return callback(err)
-        }
-        db.docs.deleteMany({ project_id: new ObjectId(projectId) }, callback)
-      })
-    })
+    .toArray()
+  const docIds = records.map(r => r._id)
+  await db.docOps.deleteMany({ doc_id: { $in: docIds } })
+  await db.docs.deleteMany({ project_id: new ObjectId(projectId) })
 }
 }
 
 
 module.exports = {
 module.exports = {
-  findDoc,
-  getProjectsDeletedDocs,
-  getProjectsDocs,
-  getArchivedProjectDocs,
-  getNonArchivedProjectDocIds,
-  getNonDeletedArchivedProjectDocs,
-  upsertIntoDocCollection,
-  restoreArchivedDoc,
-  patchDoc,
-  getDocForArchiving,
-  markDocAsArchived,
-  getDocVersion,
-  checkRevUnchanged,
-  destroyProject,
-}
-
-const methods = Object.getOwnPropertyNames(module.exports)
-module.exports.promises = {}
-for (const method of methods) {
-  metrics.timeAsyncMethod(module.exports, method, 'mongo.MongoManager', logger)
-  module.exports.promises[method] = promisify(module.exports[method])
+  findDoc: callbackify(findDoc),
+  getProjectsDeletedDocs: callbackify(getProjectsDeletedDocs),
+  getProjectsDocs: callbackify(getProjectsDocs),
+  getArchivedProjectDocs: callbackify(getArchivedProjectDocs),
+  getNonArchivedProjectDocIds: callbackify(getNonArchivedProjectDocIds),
+  getNonDeletedArchivedProjectDocs: callbackify(
+    getNonDeletedArchivedProjectDocs
+  ),
+  upsertIntoDocCollection: callbackify(upsertIntoDocCollection),
+  restoreArchivedDoc: callbackify(restoreArchivedDoc),
+  patchDoc: callbackify(patchDoc),
+  getDocForArchiving: callbackify(getDocForArchiving),
+  markDocAsArchived: callbackify(markDocAsArchived),
+  getDocVersion: callbackify(getDocVersion),
+  checkRevUnchanged: callbackify(checkRevUnchanged),
+  destroyProject: callbackify(destroyProject),
+  promises: {
+    findDoc,
+    getProjectsDeletedDocs,
+    getProjectsDocs,
+    getArchivedProjectDocs,
+    getNonArchivedProjectDocIds,
+    getNonDeletedArchivedProjectDocs,
+    upsertIntoDocCollection,
+    restoreArchivedDoc,
+    patchDoc,
+    getDocForArchiving,
+    markDocAsArchived,
+    getDocVersion,
+    checkRevUnchanged,
+    destroyProject,
+  },
 }
 }

+ 132 - 170
services/docstore/test/unit/js/MongoManagerTests.js

@@ -12,8 +12,8 @@ describe('MongoManager', function () {
   beforeEach(function () {
   beforeEach(function () {
     this.db = {
     this.db = {
       docs: {
       docs: {
-        updateOne: sinon.stub().yields(null, { matchedCount: 1 }),
-        insertOne: sinon.stub().yields(null),
+        updateOne: sinon.stub().resolves({ matchedCount: 1 }),
+        insertOne: sinon.stub().resolves(),
       },
       },
       docOps: {},
       docOps: {},
     }
     }
@@ -34,21 +34,19 @@ describe('MongoManager', function () {
     this.projectId = new ObjectId().toString()
     this.projectId = new ObjectId().toString()
     this.docId = new ObjectId().toString()
     this.docId = new ObjectId().toString()
     this.rev = 42
     this.rev = 42
-    this.callback = sinon.stub()
     this.stubbedErr = new Error('hello world')
     this.stubbedErr = new Error('hello world')
     this.lines = ['Three French hens', 'Two turtle doves']
     this.lines = ['Three French hens', 'Two turtle doves']
   })
   })
 
 
   describe('findDoc', function () {
   describe('findDoc', function () {
-    beforeEach(function () {
+    beforeEach(async function () {
       this.doc = { name: 'mock-doc' }
       this.doc = { name: 'mock-doc' }
-      this.db.docs.findOne = sinon.stub().callsArgWith(2, null, this.doc)
+      this.db.docs.findOne = sinon.stub().resolves(this.doc)
       this.filter = { lines: true }
       this.filter = { lines: true }
-      this.MongoManager.findDoc(
+      this.result = await this.MongoManager.promises.findDoc(
         this.projectId,
         this.projectId,
         this.docId,
         this.docId,
-        this.filter,
-        this.callback
+        this.filter
       )
       )
     })
     })
 
 
@@ -66,20 +64,18 @@ describe('MongoManager', function () {
         .should.equal(true)
         .should.equal(true)
     })
     })
 
 
-    it('should call the callback with the doc', function () {
-      this.callback.calledWith(null, this.doc).should.equal(true)
+    it('should return the doc', function () {
+      expect(this.doc).to.deep.equal(this.doc)
     })
     })
   })
   })
 
 
   describe('patchDoc', function () {
   describe('patchDoc', function () {
-    beforeEach(function (done) {
+    beforeEach(async function () {
       this.meta = { name: 'foo.tex' }
       this.meta = { name: 'foo.tex' }
-      this.callback.callsFake(done)
-      this.MongoManager.patchDoc(
+      await this.MongoManager.promises.patchDoc(
         this.projectId,
         this.projectId,
         this.docId,
         this.docId,
-        this.meta,
-        this.callback
+        this.meta
       )
       )
     })
     })
 
 
@@ -91,8 +87,7 @@ describe('MongoManager', function () {
         },
         },
         {
         {
           $set: this.meta,
           $set: this.meta,
-        },
-        this.callback
+        }
       )
       )
     })
     })
   })
   })
@@ -105,19 +100,16 @@ describe('MongoManager', function () {
       this.doc3 = { name: 'mock-doc3' }
       this.doc3 = { name: 'mock-doc3' }
       this.doc4 = { name: 'mock-doc4' }
       this.doc4 = { name: 'mock-doc4' }
       this.db.docs.find = sinon.stub().returns({
       this.db.docs.find = sinon.stub().returns({
-        toArray: sinon
-          .stub()
-          .callsArgWith(0, null, [this.doc, this.doc3, this.doc4]),
+        toArray: sinon.stub().resolves([this.doc, this.doc3, this.doc4]),
       })
       })
     })
     })
 
 
     describe('with included_deleted = false', function () {
     describe('with included_deleted = false', function () {
-      beforeEach(function () {
-        this.MongoManager.getProjectsDocs(
+      beforeEach(async function () {
+        this.result = await this.MongoManager.promises.getProjectsDocs(
           this.projectId,
           this.projectId,
           { include_deleted: false },
           { include_deleted: false },
-          this.filter,
-          this.callback
+          this.filter
         )
         )
       })
       })
 
 
@@ -135,20 +127,17 @@ describe('MongoManager', function () {
           .should.equal(true)
           .should.equal(true)
       })
       })
 
 
-      it('should call the callback with the docs', function () {
-        this.callback
-          .calledWith(null, [this.doc, this.doc3, this.doc4])
-          .should.equal(true)
+      it('should call return the docs', function () {
+        expect(this.result).to.deep.equal([this.doc, this.doc3, this.doc4])
       })
       })
     })
     })
 
 
     describe('with included_deleted = true', function () {
     describe('with included_deleted = true', function () {
-      beforeEach(function () {
-        this.MongoManager.getProjectsDocs(
+      beforeEach(async function () {
+        this.result = await this.MongoManager.promises.getProjectsDocs(
           this.projectId,
           this.projectId,
           { include_deleted: true },
           { include_deleted: true },
-          this.filter,
-          this.callback
+          this.filter
         )
         )
       })
       })
 
 
@@ -165,28 +154,24 @@ describe('MongoManager', function () {
           .should.equal(true)
           .should.equal(true)
       })
       })
 
 
-      it('should call the callback with the docs', function () {
-        this.callback
-          .calledWith(null, [this.doc, this.doc3, this.doc4])
-          .should.equal(true)
+      it('should return the docs', function () {
+        expect(this.result).to.deep.equal([this.doc, this.doc3, this.doc4])
       })
       })
     })
     })
   })
   })
 
 
   describe('getProjectsDeletedDocs', function () {
   describe('getProjectsDeletedDocs', function () {
-    beforeEach(function (done) {
+    beforeEach(async function () {
       this.filter = { name: true }
       this.filter = { name: true }
       this.doc1 = { _id: '1', name: 'mock-doc1.tex' }
       this.doc1 = { _id: '1', name: 'mock-doc1.tex' }
       this.doc2 = { _id: '2', name: 'mock-doc2.tex' }
       this.doc2 = { _id: '2', name: 'mock-doc2.tex' }
       this.doc3 = { _id: '3', name: 'mock-doc3.tex' }
       this.doc3 = { _id: '3', name: 'mock-doc3.tex' }
       this.db.docs.find = sinon.stub().returns({
       this.db.docs.find = sinon.stub().returns({
-        toArray: sinon.stub().yields(null, [this.doc1, this.doc2, this.doc3]),
+        toArray: sinon.stub().resolves([this.doc1, this.doc2, this.doc3]),
       })
       })
-      this.callback.callsFake(done)
-      this.MongoManager.getProjectsDeletedDocs(
+      this.result = await this.MongoManager.promises.getProjectsDeletedDocs(
         this.projectId,
         this.projectId,
-        this.filter,
-        this.callback
+        this.filter
       )
       )
     })
     })
 
 
@@ -209,10 +194,8 @@ describe('MongoManager', function () {
         .should.equal(true)
         .should.equal(true)
     })
     })
 
 
-    it('should call the callback with the docs', function () {
-      this.callback
-        .calledWith(null, [this.doc1, this.doc2, this.doc3])
-        .should.equal(true)
+    it('should return the docs', function () {
+      expect(this.result).to.deep.equal([this.doc1, this.doc2, this.doc3])
     })
     })
   })
   })
 
 
@@ -221,108 +204,97 @@ describe('MongoManager', function () {
       this.oldRev = 77
       this.oldRev = 77
     })
     })
 
 
-    it('should upsert the document', function (done) {
-      this.MongoManager.upsertIntoDocCollection(
+    it('should upsert the document', async function () {
+      await this.MongoManager.promises.upsertIntoDocCollection(
         this.projectId,
         this.projectId,
         this.docId,
         this.docId,
         this.oldRev,
         this.oldRev,
-        { lines: this.lines },
-        err => {
-          assert.equal(err, null)
-          const args = this.db.docs.updateOne.args[0]
-          assert.deepEqual(args[0], {
-            _id: new ObjectId(this.docId),
-            project_id: new ObjectId(this.projectId),
-            rev: this.oldRev,
-          })
-          assert.equal(args[1].$set.lines, this.lines)
-          assert.equal(args[1].$inc.rev, 1)
-          done()
-        }
+        { lines: this.lines }
       )
       )
-    })
 
 
-    it('should handle update error', function (done) {
-      this.db.docs.updateOne.yields(this.stubbedErr)
-      this.MongoManager.upsertIntoDocCollection(
-        this.projectId,
-        this.docId,
-        this.rev,
-        { lines: this.lines },
-        err => {
-          err.should.equal(this.stubbedErr)
-          done()
-        }
-      )
+      const args = this.db.docs.updateOne.args[0]
+      assert.deepEqual(args[0], {
+        _id: new ObjectId(this.docId),
+        project_id: new ObjectId(this.projectId),
+        rev: this.oldRev,
+      })
+      assert.equal(args[1].$set.lines, this.lines)
+      assert.equal(args[1].$inc.rev, 1)
     })
     })
 
 
-    it('should insert without a previous rev', function (done) {
-      this.MongoManager.upsertIntoDocCollection(
-        this.projectId,
-        this.docId,
-        null,
-        { lines: this.lines, ranges: this.ranges },
-        err => {
-          expect(this.db.docs.insertOne).to.have.been.calledWith({
-            _id: new ObjectId(this.docId),
-            project_id: new ObjectId(this.projectId),
-            rev: 1,
+    it('should handle update error', async function () {
+      this.db.docs.updateOne.rejects(this.stubbedErr)
+      await expect(
+        this.MongoManager.promises.upsertIntoDocCollection(
+          this.projectId,
+          this.docId,
+          this.rev,
+          {
             lines: this.lines,
             lines: this.lines,
-            ranges: this.ranges,
-          })
-          expect(err).to.not.exist
-          done()
-        }
-      )
+          }
+        )
+      ).to.be.rejectedWith(this.stubbedErr)
     })
     })
 
 
-    it('should handle generic insert error', function (done) {
-      this.db.docs.insertOne.yields(this.stubbedErr)
-      this.MongoManager.upsertIntoDocCollection(
+    it('should insert without a previous rev', async function () {
+      await this.MongoManager.promises.upsertIntoDocCollection(
         this.projectId,
         this.projectId,
         this.docId,
         this.docId,
         null,
         null,
-        { lines: this.lines, ranges: this.ranges },
-        err => {
-          expect(err).to.equal(this.stubbedErr)
-          done()
-        }
+        { lines: this.lines, ranges: this.ranges }
       )
       )
+
+      expect(this.db.docs.insertOne).to.have.been.calledWith({
+        _id: new ObjectId(this.docId),
+        project_id: new ObjectId(this.projectId),
+        rev: 1,
+        lines: this.lines,
+        ranges: this.ranges,
+      })
     })
     })
 
 
-    it('should handle duplicate insert error', function (done) {
-      this.db.docs.insertOne.yields({ code: 11000 })
-      this.MongoManager.upsertIntoDocCollection(
-        this.projectId,
-        this.docId,
-        null,
-        { lines: this.lines, ranges: this.ranges },
-        err => {
-          expect(err).to.be.instanceof(Errors.DocRevValueError)
-          done()
-        }
-      )
+    it('should handle generic insert error', async function () {
+      this.db.docs.insertOne.rejects(this.stubbedErr)
+      await expect(
+        this.MongoManager.promises.upsertIntoDocCollection(
+          this.projectId,
+          this.docId,
+          null,
+          { lines: this.lines, ranges: this.ranges }
+        )
+      ).to.be.rejectedWith(this.stubbedErr)
+    })
+
+    it('should handle duplicate insert error', async function () {
+      this.db.docs.insertOne.rejects({ code: 11000 })
+      await expect(
+        this.MongoManager.promises.upsertIntoDocCollection(
+          this.projectId,
+          this.docId,
+          null,
+          { lines: this.lines, ranges: this.ranges }
+        )
+      ).to.be.rejectedWith(Errors.DocRevValueError)
     })
     })
   })
   })
 
 
   describe('destroyProject', function () {
   describe('destroyProject', function () {
-    beforeEach(function (done) {
+    beforeEach(async function () {
       this.projectId = new ObjectId()
       this.projectId = new ObjectId()
       this.docIds = [new ObjectId(), new ObjectId()]
       this.docIds = [new ObjectId(), new ObjectId()]
-      this.db.docs.deleteMany = sinon.stub().yields()
-      this.db.docOps.deleteMany = sinon.stub().yields()
+      this.db.docs.deleteMany = sinon.stub().resolves()
+      this.db.docOps.deleteMany = sinon.stub().resolves()
       this.db.docs.find = sinon
       this.db.docs.find = sinon
         .stub()
         .stub()
         .withArgs({ project_id: this.projectId })
         .withArgs({ project_id: this.projectId })
         .returns({
         .returns({
-          toArray: sinon.stub().yields(
-            null,
+          toArray: sinon.stub().resolves(
             this.docIds.map(id => ({
             this.docIds.map(id => ({
               _id: id,
               _id: id,
             }))
             }))
           ),
           ),
         })
         })
-      this.MongoManager.destroyProject(this.projectId, done)
+      await this.MongoManager.promises.destroyProject(this.projectId)
     })
     })
 
 
     it('should destroy all docs', function () {
     it('should destroy all docs', function () {
@@ -340,10 +312,10 @@ describe('MongoManager', function () {
 
 
   describe('getDocVersion', function () {
   describe('getDocVersion', function () {
     describe('when the doc exists', function () {
     describe('when the doc exists', function () {
-      beforeEach(function () {
+      beforeEach(async function () {
         this.doc = { version: (this.version = 42) }
         this.doc = { version: (this.version = 42) }
-        this.db.docOps.findOne = sinon.stub().callsArgWith(2, null, this.doc)
-        this.MongoManager.getDocVersion(this.docId, this.callback)
+        this.db.docOps.findOne = sinon.stub().resolves(this.doc)
+        this.result = await this.MongoManager.promises.getDocVersion(this.docId)
       })
       })
 
 
       it('should look for the doc in the database', function () {
       it('should look for the doc in the database', function () {
@@ -357,19 +329,19 @@ describe('MongoManager', function () {
           .should.equal(true)
           .should.equal(true)
       })
       })
 
 
-      it('should call the callback with the version', function () {
-        this.callback.calledWith(null, this.version).should.equal(true)
+      it('should return the version', function () {
+        expect(this.result).to.equal(this.version)
       })
       })
     })
     })
 
 
     describe("when the doc doesn't exist", function () {
     describe("when the doc doesn't exist", function () {
-      beforeEach(function () {
-        this.db.docOps.findOne = sinon.stub().callsArgWith(2, null, null)
-        this.MongoManager.getDocVersion(this.docId, this.callback)
+      beforeEach(async function () {
+        this.db.docOps.findOne = sinon.stub().resolves(null)
+        this.result = await this.MongoManager.promises.getDocVersion(this.docId)
       })
       })
 
 
-      it('should call the callback with 0', function () {
-        this.callback.calledWith(null, 0).should.equal(true)
+      it('should return 0', function () {
+        expect(this.result).to.equal(0)
       })
       })
     })
     })
   })
   })
@@ -379,37 +351,31 @@ describe('MongoManager', function () {
       this.doc = { _id: new ObjectId(), name: 'mock-doc', rev: 1 }
       this.doc = { _id: new ObjectId(), name: 'mock-doc', rev: 1 }
     })
     })
 
 
-    it('should call the callback when the rev has not changed', function (done) {
-      this.db.docs.findOne = sinon.stub().callsArgWith(2, null, { rev: 1 })
-      this.MongoManager.checkRevUnchanged(this.doc, err => {
-        assert.isUndefined(err)
-        done()
-      })
+    it('should not error when the rev has not changed', async function () {
+      this.db.docs.findOne = sinon.stub().resolves({ rev: 1 })
+      await this.MongoManager.promises.checkRevUnchanged(this.doc)
     })
     })
 
 
-    it('should return an error when the rev has changed', function (done) {
-      this.db.docs.findOne = sinon.stub().callsArgWith(2, null, { rev: 2 })
-      this.MongoManager.checkRevUnchanged(this.doc, err => {
-        err.should.be.instanceof(Errors.DocModifiedError)
-        done()
-      })
+    it('should return an error when the rev has changed', async function () {
+      this.db.docs.findOne = sinon.stub().resolves({ rev: 2 })
+      await expect(
+        this.MongoManager.promises.checkRevUnchanged(this.doc)
+      ).to.be.rejectedWith(Errors.DocModifiedError)
     })
     })
 
 
-    it('should return a value error if incoming rev is NaN', function (done) {
-      this.db.docs.findOne = sinon.stub().callsArgWith(2, null, { rev: 2 })
+    it('should return a value error if incoming rev is NaN', async function () {
+      this.db.docs.findOne = sinon.stub().resolves({ rev: 2 })
       this.doc = { _id: new ObjectId(), name: 'mock-doc', rev: NaN }
       this.doc = { _id: new ObjectId(), name: 'mock-doc', rev: NaN }
-      this.MongoManager.checkRevUnchanged(this.doc, err => {
-        err.should.be.instanceof(Errors.DocRevValueError)
-        done()
-      })
+      await expect(
+        this.MongoManager.promises.checkRevUnchanged(this.doc)
+      ).to.be.rejectedWith(Errors.DocRevValueError)
     })
     })
 
 
-    it('should return a value error if checked doc rev is NaN', function (done) {
-      this.db.docs.findOne = sinon.stub().callsArgWith(2, null, { rev: NaN })
-      this.MongoManager.checkRevUnchanged(this.doc, err => {
-        err.should.be.instanceof(Errors.DocRevValueError)
-        done()
-      })
+    it('should return a value error if checked doc rev is NaN', async function () {
+      this.db.docs.findOne = sinon.stub().resolves({ rev: NaN })
+      await expect(
+        this.MongoManager.promises.checkRevUnchanged(this.doc)
+      ).to.be.rejectedWith(Errors.DocRevValueError)
     })
     })
   })
   })
 
 
@@ -423,12 +389,11 @@ describe('MongoManager', function () {
     })
     })
 
 
     describe('complete doc', function () {
     describe('complete doc', function () {
-      beforeEach(function (done) {
-        this.MongoManager.restoreArchivedDoc(
+      beforeEach(async function () {
+        await this.MongoManager.promises.restoreArchivedDoc(
           this.projectId,
           this.projectId,
           this.docId,
           this.docId,
-          this.archivedDoc,
-          done
+          this.archivedDoc
         )
         )
       })
       })
 
 
@@ -453,13 +418,12 @@ describe('MongoManager', function () {
     })
     })
 
 
     describe('without ranges', function () {
     describe('without ranges', function () {
-      beforeEach(function (done) {
+      beforeEach(async function () {
         delete this.archivedDoc.ranges
         delete this.archivedDoc.ranges
-        this.MongoManager.restoreArchivedDoc(
+        await this.MongoManager.promises.restoreArchivedDoc(
           this.projectId,
           this.projectId,
           this.docId,
           this.docId,
-          this.archivedDoc,
-          done
+          this.archivedDoc
         )
         )
       })
       })
 
 
@@ -484,17 +448,15 @@ describe('MongoManager', function () {
     })
     })
 
 
     describe("when the update doesn't succeed", function () {
     describe("when the update doesn't succeed", function () {
-      it('throws a DocRevValueError', function (done) {
-        this.db.docs.updateOne.yields(null, { matchedCount: 0 })
-        this.MongoManager.restoreArchivedDoc(
-          this.projectId,
-          this.docId,
-          this.archivedDoc,
-          err => {
-            expect(err).to.be.instanceof(Errors.DocRevValueError)
-            done()
-          }
-        )
+      it('throws a DocRevValueError', async function () {
+        this.db.docs.updateOne.resolves({ matchedCount: 0 })
+        await expect(
+          this.MongoManager.promises.restoreArchivedDoc(
+            this.projectId,
+            this.docId,
+            this.archivedDoc
+          )
+        ).to.be.rejectedWith(Errors.DocRevValueError)
       })
       })
     })
     })
   })
   })