Sfoglia il codice sorgente

Promisify Metadata feature (#19361)

GitOrigin-RevId: 962aa9dbbc41a49c2c3120af9a1254a4db85387b
Alf Eaton 2 anni fa
parent
commit
7e136131c0

+ 51 - 56
services/web/app/src/Features/Metadata/MetaController.js

@@ -1,66 +1,61 @@
-/* eslint-disable
-    max-len,
-    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
- * DS207: Consider shorter variations of null checks
- * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
- */
-let MetaController
 const OError = require('@overleaf/o-error')
 const EditorRealTimeController = require('../Editor/EditorRealTimeController')
 const MetaHandler = require('./MetaHandler')
 const logger = require('@overleaf/logger')
+const { expressify } = require('@overleaf/promise-utils')
 
-module.exports = MetaController = {
-  getMetadata(req, res, next) {
-    const { project_id: projectId } = req.params
-    logger.debug({ projectId }, 'getting all labels for project')
-    return MetaHandler.getAllMetaForProject(
-      projectId,
-      function (err, projectMeta) {
-        if (err != null) {
-          OError.tag(
-            err,
-            '[MetaController] error getting all labels from project',
-            {
-              project_id: projectId,
-            }
-          )
-          return next(err)
-        }
-        return res.json({ projectId, projectMeta })
+async function getMetadata(req, res) {
+  const { project_id: projectId } = req.params
+
+  logger.debug({ projectId }, 'getting all labels for project')
+
+  let projectMeta
+  try {
+    projectMeta = await MetaHandler.promises.getAllMetaForProject(projectId)
+  } catch (error) {
+    throw OError.tag(
+      error,
+      '[MetaController] error getting all labels from project',
+      {
+        project_id: projectId,
       }
     )
-  },
+  }
 
-  broadcastMetadataForDoc(req, res, next) {
-    const { project_id: projectId } = req.params
-    const { doc_id: docId } = req.params
-    const { broadcast } = req.body
-    logger.debug({ projectId, docId, broadcast }, 'getting labels for doc')
-    return MetaHandler.getMetaForDoc(projectId, docId, function (err, docMeta) {
-      if (err != null) {
-        OError.tag(err, '[MetaController] error getting labels from doc', {
-          project_id: projectId,
-          doc_id: docId,
-        })
-        return next(err)
-      }
-      // default to broadcasting, unless explicitly disabled (for backwards compatibility)
-      if (broadcast !== false) {
-        EditorRealTimeController.emitToRoom(projectId, 'broadcastDocMeta', {
-          docId,
-          meta: docMeta,
-        })
-        return res.sendStatus(200)
-      } else {
-        return res.json({ docId, meta: docMeta })
-      }
+  res.json({ projectId, projectMeta })
+}
+
+async function broadcastMetadataForDoc(req, res) {
+  const { project_id: projectId } = req.params
+  const { doc_id: docId } = req.params
+  const { broadcast } = req.body
+
+  logger.debug({ projectId, docId, broadcast }, 'getting labels for doc')
+
+  let docMeta
+  try {
+    docMeta = await MetaHandler.promises.getMetaForDoc(projectId, docId)
+  } catch (error) {
+    throw OError.tag(error, '[MetaController] error getting labels from doc', {
+      project_id: projectId,
+      doc_id: docId,
     })
-  },
+  }
+
+  // default to broadcasting, unless explicitly disabled (for backwards compatibility)
+  if (broadcast === false) {
+    return res.json({ docId, meta: docMeta })
+  }
+
+  EditorRealTimeController.emitToRoom(projectId, 'broadcastDocMeta', {
+    docId,
+    meta: docMeta,
+  })
+
+  res.sendStatus(200) // 204?
+}
+
+module.exports = {
+  getMetadata: expressify(getMetadata),
+  broadcastMetadataForDoc: expressify(broadcastMetadataForDoc),
 }

+ 109 - 124
services/web/app/src/Features/Metadata/MetaHandler.js

@@ -1,142 +1,127 @@
-/* eslint-disable
-    n/handle-callback-err,
-    max-len,
-    no-cond-assign,
-*/
-// TODO: This file was created by bulk-decaffeinate.
-// Fix any style issues and re-enable lint.
-/*
- * decaffeinate suggestions:
- * DS101: Remove unnecessary use of Array.from
- * DS102: Remove unnecessary code created because of implicit returns
- * DS207: Consider shorter variations of null checks
- * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
- */
-let MetaHandler
 const ProjectEntityHandler = require('../Project/ProjectEntityHandler')
 const DocumentUpdaterHandler = require('../DocumentUpdater/DocumentUpdaterHandler')
 const packageMapping = require('./packageMapping')
+const { callbackify } = require('@overleaf/promise-utils')
 
-module.exports = MetaHandler = {
-  labelRegex() {
-    return /\\label{(.{0,80}?)}/g
-  },
+/** @typedef {{
+ *   labels: string[]
+ *   packages: Record<string, Record<string, any>>,
+ *   packageNames: string[],
+ * }} DocMeta
+ */
 
-  usepackageRegex() {
-    return /^\\usepackage(?:\[.{0,80}?])?{(.{0,80}?)}/g
-  },
+/**
+ * @param {string[]} lines
+ * @return {Promise<DocMeta>}
+ */
+async function extractMetaFromDoc(lines) {
+  /** @type {DocMeta} */
+  const docMeta = {
+    labels: [],
+    packages: {},
+    packageNames: [],
+  }
 
-  ReqPackageRegex() {
-    return /^\\RequirePackage(?:\[.{0,80}?])?{(.{0,80}?)}/g
-  },
+  const labelRe = /\\label{(.{0,80}?)}/g
+  const packageRe = /^\\usepackage(?:\[.{0,80}?])?{(.{0,80}?)}/g
+  const reqPackageRe = /^\\RequirePackage(?:\[.{0,80}?])?{(.{0,80}?)}/g
 
-  getAllMetaForProject(projectId, callback) {
-    if (callback == null) {
-      callback = function () {}
+  for (const rawLine of lines) {
+    const line = getNonCommentedContent(rawLine)
+
+    for (const pkg of lineMatches(labelRe, line)) {
+      docMeta.labels.push(pkg)
     }
-    return DocumentUpdaterHandler.flushProjectToMongo(
-      projectId,
-      function (err) {
-        if (err != null) {
-          return callback(err)
-        }
-        return ProjectEntityHandler.getAllDocs(projectId, function (err, docs) {
-          if (err != null) {
-            return callback(err)
-          }
-          const projectMeta = MetaHandler.extractMetaFromProjectDocs(docs)
-          return callback(null, projectMeta)
-        })
-      }
-    )
-  },
 
-  getMetaForDoc(projectId, docId, callback) {
-    if (callback == null) {
-      callback = function () {}
+    for (const pkg of lineMatches(packageRe, line, ',')) {
+      docMeta.packageNames.push(pkg)
     }
-    return DocumentUpdaterHandler.flushDocToMongo(
-      projectId,
-      docId,
-      function (err) {
-        if (err != null) {
-          return callback(err)
-        }
-        return ProjectEntityHandler.getDoc(
-          projectId,
-          docId,
-          function (err, lines) {
-            if (err != null) {
-              return callback(err)
-            }
-            const docMeta = MetaHandler.extractMetaFromDoc(lines)
-            return callback(null, docMeta)
-          }
-        )
-      }
-    )
-  },
 
-  extractMetaFromDoc(lines) {
-    let pkg
-    const docMeta = { labels: [], packages: {} }
-    const packages = []
-    const labelRe = MetaHandler.labelRegex()
-    const packageRe = MetaHandler.usepackageRegex()
-    const reqPackageRe = MetaHandler.ReqPackageRegex()
-    for (const rawLine of Array.from(lines)) {
-      const line = MetaHandler._getNonCommentedContent(rawLine)
-      let labelMatch
-      let clean, messy, packageMatch
-      while ((labelMatch = labelRe.exec(line))) {
-        let label
-        if ((label = labelMatch[1])) {
-          docMeta.labels.push(label)
-        }
-      }
-      while ((packageMatch = packageRe.exec(line))) {
-        if ((messy = packageMatch[1])) {
-          for (pkg of Array.from(messy.split(','))) {
-            if ((clean = pkg.trim())) {
-              packages.push(clean)
-            }
-          }
-        }
-      }
-      while ((packageMatch = reqPackageRe.exec(line))) {
-        if ((messy = packageMatch[1])) {
-          for (pkg of Array.from(messy.split(','))) {
-            if ((clean = pkg.trim())) {
-              packages.push(clean)
-            }
-          }
-        }
-      }
+    for (const pkg of lineMatches(reqPackageRe, line, ',')) {
+      docMeta.packageNames.push(pkg)
     }
-    for (pkg of Array.from(packages)) {
-      if (packageMapping[pkg] != null) {
-        docMeta.packages[pkg] = packageMapping[pkg]
-      }
+  }
+
+  for (const packageName of docMeta.packageNames) {
+    if (packageMapping[packageName]) {
+      docMeta.packages[packageName] = packageMapping[packageName]
     }
-    docMeta.packageNames = packages
-    return docMeta
-  },
+  }
 
-  extractMetaFromProjectDocs(projectDocs) {
-    const projectMeta = {}
-    for (const _path in projectDocs) {
-      const doc = projectDocs[_path]
-      projectMeta[doc._id] = MetaHandler.extractMetaFromDoc(doc.lines)
+  return docMeta
+}
+
+/**
+ *
+ * @param {RegExp} matchRe
+ * @param {string} line
+ * @param {string} [separator]
+ * @return {Generator<string>}
+ */
+function* lineMatches(matchRe, line, separator) {
+  let match
+  while ((match = matchRe.exec(line))) {
+    const matched = match[1].trim()
+
+    if (matched) {
+      if (separator) {
+        const items = matched
+          .split(',')
+          .map(item => item.trim())
+          .filter(Boolean)
+
+        for (const item of items) {
+          yield item
+        }
+      } else {
+        yield matched
+      }
     }
-    return projectMeta
-  },
+  }
+}
+
+/**
+ * @param {Record<{ lines: string[] }, any>} projectDocs
+ * @return {Promise<{}>}
+ */
+async function extractMetaFromProjectDocs(projectDocs) {
+  const projectMeta = {}
+  for (const doc of Object.values(projectDocs)) {
+    projectMeta[doc._id] = await extractMetaFromDoc(doc.lines)
+  }
+  return projectMeta
+}
+
+/**
+ * Trims comment content from line
+ * @param {string} rawLine
+ * @returns {string}
+ */
+function getNonCommentedContent(rawLine) {
+  return rawLine.replace(/(^|[^\\])%.*/, '$1')
+}
+
+async function getAllMetaForProject(projectId) {
+  await DocumentUpdaterHandler.promises.flushProjectToMongo(projectId)
+
+  const docs = await ProjectEntityHandler.promises.getAllDocs(projectId)
+
+  return await extractMetaFromProjectDocs(docs)
+}
+
+async function getMetaForDoc(projectId, docId) {
+  await DocumentUpdaterHandler.promises.flushDocToMongo(projectId, docId)
+
+  const { lines } = await ProjectEntityHandler.promises.getDoc(projectId, docId)
+
+  return await extractMetaFromDoc(lines)
+}
 
-  /**
-   * Trims comment content from line
-   * @param {string} rawLine
-   * @returns {string}
-   */
-  _getNonCommentedContent(rawLine) {
-    return rawLine.replace(/(^|[^\\])%.*/, '$1')
+module.exports = {
+  promises: {
+    getAllMetaForProject,
+    getMetaForDoc,
   },
+  getAllMetaForProject: callbackify(getAllMetaForProject),
+  getMetaForDoc: callbackify(getMetaForDoc),
 }

+ 0 - 5
services/web/app/src/Features/Metadata/packageMapping.js

@@ -1,8 +1,3 @@
-/* eslint-disable
-    max-len,
-*/
-// TODO: This file was created by bulk-decaffeinate.
-// Fix any style issues and re-enable lint.
 module.exports = {
   inputenc: [
     {

+ 114 - 213
services/web/test/unit/src/Metadata/MetaControllerTests.js

@@ -1,263 +1,164 @@
-/* eslint-disable
-    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 { expect } = require('chai')
 const sinon = require('sinon')
 const modulePath = '../../../../app/src/Features/Metadata/MetaController'
 const SandboxedModule = require('sandboxed-module')
+const MockResponse = require('../helpers/MockResponse')
 
 describe('MetaController', function () {
   beforeEach(function () {
-    this.projectId = 'somekindofid'
     this.EditorRealTimeController = {
       emitToRoom: sinon.stub(),
     }
+
     this.MetaHandler = {
-      getAllMetaForProject: sinon.stub(),
-      getMetaForDoc: sinon.stub(),
+      promises: {
+        getAllMetaForProject: sinon.stub(),
+        getMetaForDoc: sinon.stub(),
+      },
     }
-    return (this.MetadataController = SandboxedModule.require(modulePath, {
+
+    this.MetadataController = SandboxedModule.require(modulePath, {
       requires: {
         '../Editor/EditorRealTimeController': this.EditorRealTimeController,
         './MetaHandler': this.MetaHandler,
       },
-    }))
+    })
   })
 
   describe('getMetadata', function () {
-    beforeEach(function () {
-      this.fakeLabels = { somedoc: ['a_label'] }
-      this.MetaHandler.getAllMetaForProject = sinon
+    it('should respond with json', async function () {
+      const projectMeta = {
+        'doc-id': {
+          labels: ['foo'],
+          packages: { a: { commands: [] } },
+          packageNames: ['a'],
+        },
+      }
+
+      this.MetaHandler.promises.getAllMetaForProject = sinon
         .stub()
-        .callsArgWith(1, null, this.fakeLabels)
-      this.req = { params: { project_id: this.projectId } }
-      this.res = { json: sinon.stub() }
-      return (this.next = sinon.stub())
-    })
+        .resolves(projectMeta)
 
-    it('should call MetaHandler.getAllMetaForProject', function () {
-      this.MetadataController.getMetadata(this.req, this.res, this.next)
-      this.MetaHandler.getAllMetaForProject.callCount.should.equal(1)
-      return this.MetaHandler.getAllMetaForProject
-        .calledWith(this.projectId)
-        .should.equal(true)
-    })
+      const req = { params: { project_id: 'project-id' } }
+      const res = new MockResponse()
+      const next = sinon.stub()
 
-    it('should call not call next with an error', function () {
-      this.MetadataController.getMetadata(this.req, this.res, this.next)
-      return this.next.callCount.should.equal(0)
-    })
+      await this.MetadataController.getMetadata(req, res, next)
 
-    it('should send a json response', function () {
-      this.MetadataController.getMetadata(this.req, this.res, this.next)
-      this.res.json.callCount.should.equal(1)
-      return expect(this.res.json.lastCall.args[0]).to.have.all.keys([
-        'projectId',
-        'projectMeta',
-      ])
+      this.MetaHandler.promises.getAllMetaForProject.should.have.been.calledWith(
+        'project-id'
+      )
+      res.json.should.have.been.calledOnceWith({
+        projectId: 'project-id',
+        projectMeta,
+      })
+      next.should.not.have.been.called
     })
 
-    describe('when MetaHandler.getAllMetaForProject produces an error', function () {
-      beforeEach(function () {
-        this.MetaHandler.getAllMetaForProject = sinon
-          .stub()
-          .callsArgWith(1, new Error('woops'))
-        this.req = { params: { project_id: this.projectId } }
-        this.res = { json: sinon.stub() }
-        return (this.next = sinon.stub())
-      })
+    it('should handle an error', async function () {
+      this.MetaHandler.promises.getAllMetaForProject = sinon
+        .stub()
+        .throws(new Error('woops'))
 
-      it('should call MetaHandler.getAllMetaForProject', function () {
-        this.MetadataController.getMetadata(this.req, this.res, this.next)
-        this.MetaHandler.getAllMetaForProject.callCount.should.equal(1)
-        return this.MetaHandler.getAllMetaForProject
-          .calledWith(this.projectId)
-          .should.equal(true)
-      })
+      const req = { params: { project_id: 'project-id' } }
+      const res = new MockResponse()
+      const next = sinon.stub()
 
-      it('should call next with an error', function () {
-        this.MetadataController.getMetadata(this.req, this.res, this.next)
-        this.next.callCount.should.equal(1)
-        return expect(this.next.lastCall.args[0]).to.be.instanceof(Error)
-      })
+      await this.MetadataController.getMetadata(req, res, next)
 
-      it('should not send a json response', function () {
-        this.MetadataController.getMetadata(this.req, this.res, this.next)
-        return this.res.json.callCount.should.equal(0)
-      })
+      this.MetaHandler.promises.getAllMetaForProject.should.have.been.calledWith(
+        'project-id'
+      )
+      res.json.should.not.have.been.called
+      next.should.have.been.calledWithMatch(error => error instanceof Error)
     })
   })
 
   describe('broadcastMetadataForDoc', function () {
-    beforeEach(function () {
-      this.MetaHandler.getMetaForDoc = sinon
-        .stub()
-        .callsArgWith(2, null, this.fakeLabels)
-      this.EditorRealTimeController.emitToRoom = sinon.stub()
-      this.docId = 'somedoc'
-      this.res = { sendStatus: sinon.stub(), json: sinon.stub() }
-      return (this.next = sinon.stub())
-    })
-
-    describe('with broadcast:true', function () {
-      beforeEach(function () {
-        this.req = {
-          params: { project_id: this.projectId, doc_id: this.docId },
-          body: { broadcast: true },
-        }
-      })
-
-      it('should call MetaHandler.getMetaForDoc', function () {
-        this.MetadataController.broadcastMetadataForDoc(
-          this.req,
-          this.res,
-          this.next
-        )
-        this.MetaHandler.getMetaForDoc.callCount.should.equal(1)
-        return this.MetaHandler.getMetaForDoc
-          .calledWith(this.projectId)
-          .should.equal(true)
+    it('should broadcast on broadcast:true ', async function () {
+      this.MetaHandler.promises.getMetaForDoc = sinon.stub().resolves({
+        labels: ['foo'],
+        packages: { a: { commands: [] } },
+        packageNames: ['a'],
       })
 
-      it('should call not call next with an error', function () {
-        this.MetadataController.broadcastMetadataForDoc(
-          this.req,
-          this.res,
-          this.next
-        )
-        return this.next.callCount.should.equal(0)
-      })
-
-      it('should send a success response', function () {
-        this.MetadataController.broadcastMetadataForDoc(
-          this.req,
-          this.res,
-          this.next
-        )
-        this.res.sendStatus.callCount.should.equal(1)
-        return this.res.sendStatus.calledWith(200).should.equal(true)
-      })
+      this.EditorRealTimeController.emitToRoom = sinon.stub()
 
-      it('should emit a message to room', function () {
-        this.MetadataController.broadcastMetadataForDoc(
-          this.req,
-          this.res,
-          this.next
-        )
-        this.EditorRealTimeController.emitToRoom.callCount.should.equal(1)
-        const { lastCall } = this.EditorRealTimeController.emitToRoom
-        expect(lastCall.args[0]).to.equal(this.projectId)
-        expect(lastCall.args[1]).to.equal('broadcastDocMeta')
-        return expect(lastCall.args[2]).to.have.all.keys(['docId', 'meta'])
-      })
+      const req = {
+        params: { project_id: 'project-id', doc_id: 'doc-id' },
+        body: { broadcast: true },
+      }
+      const res = new MockResponse()
+      const next = sinon.stub()
+
+      await this.MetadataController.broadcastMetadataForDoc(req, res, next)
+
+      this.MetaHandler.promises.getMetaForDoc.should.have.been.calledWith(
+        'project-id'
+      )
+      res.json.should.not.have.been.called
+      res.sendStatus.should.have.been.calledOnceWith(200)
+      next.should.not.have.been.called
+
+      this.EditorRealTimeController.emitToRoom.should.have.been.calledOnce
+      const { lastCall } = this.EditorRealTimeController.emitToRoom
+      expect(lastCall.args[0]).to.equal('project-id')
+      expect(lastCall.args[1]).to.equal('broadcastDocMeta')
+      expect(lastCall.args[2]).to.have.all.keys(['docId', 'meta'])
     })
 
-    describe('with broadcast:false', function () {
-      beforeEach(function () {
-        this.req = {
-          params: { project_id: this.projectId, doc_id: this.docId },
-          body: { broadcast: false },
-        }
-      })
+    it('should return json on broadcast:false ', async function () {
+      const docMeta = {
+        labels: ['foo'],
+        packages: { a: [] },
+        packageNames: ['a'],
+      }
 
-      it('should call MetaHandler.getMetaForDoc', function () {
-        this.MetadataController.broadcastMetadataForDoc(
-          this.req,
-          this.res,
-          this.next
-        )
-        this.MetaHandler.getMetaForDoc.callCount.should.equal(1)
-        return this.MetaHandler.getMetaForDoc
-          .calledWith(this.projectId)
-          .should.equal(true)
-      })
+      this.MetaHandler.promises.getMetaForDoc = sinon.stub().resolves(docMeta)
 
-      it('should call not call next with an error', function () {
-        this.MetadataController.broadcastMetadataForDoc(
-          this.req,
-          this.res,
-          this.next
-        )
-        return this.next.callCount.should.equal(0)
-      })
+      this.EditorRealTimeController.emitToRoom = sinon.stub()
 
-      it('should send the metadata in the response', function () {
-        this.MetadataController.broadcastMetadataForDoc(
-          this.req,
-          this.res,
-          this.next
-        )
-        this.res.json.callCount.should.equal(1)
-        return this.res.json
-          .calledWith({ docId: this.docId, meta: this.fakeLabels })
-          .should.equal(true)
-      })
+      const req = {
+        params: { project_id: 'project-id', doc_id: 'doc-id' },
+        body: { broadcast: false },
+      }
+      const res = new MockResponse()
+      const next = sinon.stub()
+
+      await this.MetadataController.broadcastMetadataForDoc(req, res, next)
 
-      it('should not emit a message to room', function () {
-        this.MetadataController.broadcastMetadataForDoc(
-          this.req,
-          this.res,
-          this.next
-        )
-        return this.EditorRealTimeController.emitToRoom.callCount.should.equal(
-          0
-        )
+      this.MetaHandler.promises.getMetaForDoc.should.have.been.calledWith(
+        'project-id'
+      )
+      this.EditorRealTimeController.emitToRoom.should.not.have.been.called
+      res.json.should.have.been.calledOnceWith({
+        docId: 'doc-id',
+        meta: docMeta,
       })
+      next.should.not.have.been.called
     })
 
-    describe('when MetaHandler.getMetaForDoc produces an error', function () {
-      beforeEach(function () {
-        this.MetaHandler.getMetaForDoc = sinon
-          .stub()
-          .callsArgWith(2, new Error('woops'))
-        this.EditorRealTimeController.emitToRoom = sinon.stub()
-        this.docId = 'somedoc'
-        this.req = {
-          params: { project_id: this.projectId, doc_id: this.docId },
-          body: { broadcast: true },
-        }
-        this.res = { json: sinon.stub() }
-        return (this.next = sinon.stub())
-      })
+    it('should handle an error', async function () {
+      this.MetaHandler.promises.getMetaForDoc = sinon
+        .stub()
+        .throws(new Error('woops'))
 
-      it('should call MetaHandler.getMetaForDoc', function () {
-        this.MetadataController.broadcastMetadataForDoc(
-          this.req,
-          this.res,
-          this.next
-        )
-        this.MetaHandler.getMetaForDoc.callCount.should.equal(1)
-        return this.MetaHandler.getMetaForDoc
-          .calledWith(this.projectId)
-          .should.equal(true)
-      })
+      this.EditorRealTimeController.emitToRoom = sinon.stub()
 
-      it('should call next with an error', function () {
-        this.MetadataController.broadcastMetadataForDoc(
-          this.req,
-          this.res,
-          this.next
-        )
-        this.next.callCount.should.equal(1)
-        return expect(this.next.lastCall.args[0]).to.be.instanceof(Error)
-      })
+      const req = {
+        params: { project_id: 'project-id', doc_id: 'doc-id' },
+        body: { broadcast: true },
+      }
+      const res = new MockResponse()
+      const next = sinon.stub()
 
-      it('should not send a json response', function () {
-        this.MetadataController.broadcastMetadataForDoc(
-          this.req,
-          this.res,
-          this.next
-        )
-        return this.res.json.callCount.should.equal(0)
-      })
+      await this.MetadataController.broadcastMetadataForDoc(req, res, next)
+
+      this.MetaHandler.promises.getMetaForDoc.should.have.been.calledWith(
+        'project-id'
+      )
+      res.json.should.not.have.been.called
+      next.should.have.been.calledWithMatch(error => error instanceof Error)
     })
   })
 })

+ 83 - 213
services/web/test/unit/src/Metadata/MetaHandlerTests.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 { expect } = require('chai')
 const sinon = require('sinon')
 const modulePath = '../../../../app/src/Features/Metadata/MetaHandler'
@@ -19,13 +7,40 @@ describe('MetaHandler', function () {
   beforeEach(function () {
     this.projectId = 'someprojectid'
     this.docId = 'somedocid'
+
+    this.lines = [
+      '\\usepackage{ foo, bar }',
+      '\\usepackage{baz}',
+      'one',
+      '\\label{aaa}',
+      'two',
+      'commented label % \\label{bbb}', // bbb should not be in the returned labels
+      '\\label{ccc}%bar', // ccc should be in the returned labels
+      '\\label{ddd} % bar', // ddd should be in the returned labels
+      '\\label{ e,f,g }', // e,f,g should be in the returned labels
+    ]
+
+    this.docs = {
+      [this.docId]: {
+        _id: this.docId,
+        lines: this.lines,
+      },
+    }
+
     this.ProjectEntityHandler = {
-      getAllDocs: sinon.stub(),
-      getDoc: sinon.stub(),
+      promises: {
+        getAllDocs: sinon.stub().resolves(this.docs),
+        getDoc: sinon.stub().resolves(this.docs[this.docId]),
+      },
     }
+
     this.DocumentUpdaterHandler = {
-      flushDocToMongo: sinon.stub(),
+      promises: {
+        flushDocToMongo: sinon.stub().resolves(),
+        flushProjectToMongo: sinon.stub().resolves(),
+      },
     }
+
     this.packageMapping = {
       foo: [
         {
@@ -51,58 +66,47 @@ describe('MetaHandler', function () {
       ],
     }
 
-    return (this.MetaHandler = SandboxedModule.require(modulePath, {
+    this.MetaHandler = SandboxedModule.require(modulePath, {
       requires: {
         '../Project/ProjectEntityHandler': this.ProjectEntityHandler,
         '../DocumentUpdater/DocumentUpdaterHandler':
           this.DocumentUpdaterHandler,
         './packageMapping': this.packageMapping,
       },
-    }))
-  })
-
-  describe('extractMetaFromDoc', function () {
-    beforeEach(function () {
-      return (this.lines = [
-        '\\usepackage{foo}',
-        '\\usepackage{amsmath, booktabs}',
-        'one',
-        'two',
-        'three \\label{aaa}',
-        'four five',
-        '\\label{bbb}',
-        'six seven',
-      ])
     })
+  })
 
-    it('should extract all the labels and packages', function () {
-      const docMeta = this.MetaHandler.extractMetaFromDoc(this.lines)
-      return expect(docMeta).to.deep.equal({
-        labels: ['aaa', 'bbb'],
+  describe('getMetaForDoc', function () {
+    it('should extract all the labels and packages', async function () {
+      const result = await this.MetaHandler.promises.getMetaForDoc(
+        this.projectId,
+        this.docId
+      )
+
+      expect(result).to.deep.equal({
+        labels: ['aaa', 'ccc', 'ddd', 'e,f,g'],
         packages: {
-          foo: [
-            {
-              caption: '\\bar',
-              snippet: '\\bar',
-              meta: 'foo-cmd',
-              score: 12,
-            },
-            {
-              caption: '\\bat[]{}',
-              snippet: '\\bar[$1]{$2}',
-              meta: 'foo-cmd',
-              score: 10,
-            },
-          ],
+          foo: this.packageMapping.foo,
+          baz: this.packageMapping.baz,
         },
-        packageNames: ['foo', 'amsmath', 'booktabs'],
+        packageNames: ['foo', 'bar', 'baz'],
       })
+
+      this.DocumentUpdaterHandler.promises.flushDocToMongo.should.be.calledWith(
+        this.projectId,
+        this.docId
+      )
+
+      this.ProjectEntityHandler.promises.getDoc.should.be.calledWith(
+        this.projectId,
+        this.docId
+      )
     })
   })
 
-  describe('extractMetaFromProjectDocs', function () {
-    beforeEach(function () {
-      return (this.docs = {
+  describe('getAllMetaForProject', function () {
+    it('should extract all metadata', async function () {
+      this.ProjectEntityHandler.promises.getAllDocs = sinon.stub().resolves({
         doc_one: {
           _id: 'id_one',
           lines: ['one', '\\label{aaa} two', 'three'],
@@ -134,14 +138,27 @@ describe('MetaHandler', function () {
           ],
         },
       })
-    })
 
-    it('should extract all metadata', function () {
-      const projectMeta = this.MetaHandler.extractMetaFromProjectDocs(this.docs)
-      return expect(projectMeta).to.deep.equal({
-        id_one: { labels: ['aaa'], packages: {}, packageNames: [] },
-        id_two: { labels: [], packages: {}, packageNames: [] },
-        id_three: { labels: ['bbb', 'ccc'], packages: {}, packageNames: [] },
+      const result = await this.MetaHandler.promises.getAllMetaForProject(
+        this.projectId
+      )
+
+      expect(result).to.deep.equal({
+        id_one: {
+          labels: ['aaa'],
+          packages: {},
+          packageNames: [],
+        },
+        id_two: {
+          labels: [],
+          packages: {},
+          packageNames: [],
+        },
+        id_three: {
+          labels: ['bbb', 'ccc'],
+          packages: {},
+          packageNames: [],
+        },
         id_four: {
           labels: [],
           packages: {
@@ -185,161 +202,14 @@ describe('MetaHandler', function () {
           packageNames: ['foo', 'baz', 'hello'],
         },
       })
-    })
-  })
-
-  describe('getMetaForDoc', function () {
-    beforeEach(function () {
-      this.fakeLines = [
-        '\\usepackage{abc}',
-        'one',
-        '\\label{aaa}',
-        'two',
-        // bbb should not be in the returned labels
-        'commented label % \\label{bbb}',
-        '\\label{ccc}%bar',
-        '\\label{ddd} % bar',
-      ]
-      this.fakeMeta = {
-        labels: ['aaa', 'ccc', 'ddd'],
-        packages: { abc: [] },
-        packageNames: ['abc'],
-      }
-      this.DocumentUpdaterHandler.flushDocToMongo = sinon
-        .stub()
-        .callsArgWith(2, null)
-      this.ProjectEntityHandler.getDoc = sinon
-        .stub()
-        .callsArgWith(2, null, this.fakeLines)
-      this.MetaHandler.extractMetaFromDoc = sinon.stub().returns(this.fakeMeta)
-      return (this.call = callback => {
-        return this.MetaHandler.getMetaForDoc(
-          this.projectId,
-          this.docId,
-          callback
-        )
-      })
-    })
 
-    it('should not produce an error', function (done) {
-      return this.call((err, docMeta) => {
-        expect(err).to.equal(null)
-        return done()
-      })
-    })
+      this.DocumentUpdaterHandler.promises.flushProjectToMongo.should.be.calledWith(
+        this.projectId
+      )
 
-    it('should produce docMeta', function (done) {
-      return this.call((err, docMeta) => {
-        expect(docMeta).to.equal(this.fakeMeta)
-        return done()
-      })
-    })
-
-    it('should call flushDocToMongo', function (done) {
-      return this.call((err, docMeta) => {
-        this.DocumentUpdaterHandler.flushDocToMongo.callCount.should.equal(1)
-        this.DocumentUpdaterHandler.flushDocToMongo
-          .calledWith(this.projectId, this.docId)
-          .should.equal(true)
-        return done()
-      })
-    })
-
-    it('should call getDoc', function (done) {
-      return this.call((err, docMeta) => {
-        this.ProjectEntityHandler.getDoc.callCount.should.equal(1)
-        this.ProjectEntityHandler.getDoc
-          .calledWith(this.projectId, this.docId)
-          .should.equal(true)
-        return done()
-      })
-    })
-
-    it('should call extractMetaFromDoc', function (done) {
-      return this.call((err, docMeta) => {
-        this.MetaHandler.extractMetaFromDoc.callCount.should.equal(1)
-        this.MetaHandler.extractMetaFromDoc
-          .calledWith(this.fakeLines)
-          .should.equal(true)
-        return done()
-      })
-    })
-  })
-
-  describe('getAllMetaForProject', function () {
-    beforeEach(function () {
-      this.fakeDocs = {
-        doc_one: {
-          lines: ['\\usepackage[some-options,more=foo]{foo}', '\\label{aaa}'],
-        },
-      }
-
-      this.fakeMeta = {
-        labels: ['aaa'],
-        packages: {
-          foo: [
-            {
-              caption: '\\bar',
-              snippet: '\\bar',
-              meta: 'foo-cmd',
-              score: 12,
-            },
-            {
-              caption: '\\bat[]{}',
-              snippet: '\\bar[$1]{$2}',
-              meta: 'foo-cmd',
-              score: 10,
-            },
-          ],
-        },
-        packageNames: ['foo'],
-      }
-      this.DocumentUpdaterHandler.flushProjectToMongo = sinon
-        .stub()
-        .callsArgWith(1, null)
-      this.ProjectEntityHandler.getAllDocs = sinon
-        .stub()
-        .callsArgWith(1, null, this.fakeDocs)
-      this.MetaHandler.extractMetaFromProjectDocs = sinon
-        .stub()
-        .returns(this.fakeMeta)
-      return (this.call = callback => {
-        return this.MetaHandler.getAllMetaForProject(this.projectId, callback)
-      })
-    })
-
-    it('should not produce an error', function (done) {
-      return this.call((err, projectMeta) => {
-        expect(err).to.equal(null)
-        return done()
-      })
-    })
-
-    it('should produce projectMeta', function (done) {
-      return this.call((err, projectMeta) => {
-        expect(projectMeta).to.equal(this.fakeMeta)
-        return done()
-      })
-    })
-
-    it('should call getAllDocs', function (done) {
-      return this.call((err, projectMeta) => {
-        this.ProjectEntityHandler.getAllDocs.callCount.should.equal(1)
-        this.ProjectEntityHandler.getAllDocs
-          .calledWith(this.projectId)
-          .should.equal(true)
-        return done()
-      })
-    })
-
-    it('should call extractMetaFromDoc', function (done) {
-      return this.call((err, docMeta) => {
-        this.MetaHandler.extractMetaFromProjectDocs.callCount.should.equal(1)
-        this.MetaHandler.extractMetaFromProjectDocs
-          .calledWith(this.fakeDocs)
-          .should.equal(true)
-        return done()
-      })
+      this.ProjectEntityHandler.promises.getAllDocs.should.be.calledWith(
+        this.projectId
+      )
     })
   })
 })