Browse Source

Merge pull request #28752 from overleaf/msm-clsi-acceptance-async-await

[clsi] async/await migration in acceptance tests

GitOrigin-RevId: d614fabb6d568dc5c955603fb923fb40b871a703
Miguel Serrano 10 months ago
parent
commit
e8bc186ca0

+ 87 - 98
services/clsi/test/acceptance/js/AllowedImageNamesTests.js

@@ -3,7 +3,7 @@ const ClsiApp = require('./helpers/ClsiApp')
 const { expect } = require('chai')
 
 describe('AllowedImageNames', function () {
-  beforeEach(function (done) {
+  beforeEach(async function () {
     this.project_id = Client.randomId()
     this.request = {
       options: {
@@ -21,22 +21,21 @@ Hello world
         },
       ],
     }
-    ClsiApp.ensureRunning(done)
+    await ClsiApp.ensureRunning()
   })
 
   describe('with a valid name', function () {
-    beforeEach(function (done) {
+    beforeEach(async function () {
       this.request.options.imageName = process.env.TEXLIVE_IMAGE
 
-      Client.compile(this.project_id, this.request, (error, res, body) => {
+      try {
+        this.body = await Client.compile(this.project_id, this.request)
+      } catch (error) {
         this.error = error
-        this.res = res
-        this.body = body
-        done(error)
-      })
+      }
     })
     it('should return success', function () {
-      expect(this.res.statusCode).to.equal(200)
+      expect(this.error).not.to.exist
     })
 
     it('should return a PDF', function () {
@@ -49,17 +48,16 @@ Hello world
   })
 
   describe('with an invalid name', function () {
-    beforeEach(function (done) {
+    beforeEach(async function () {
       this.request.options.imageName = 'something/evil:1337'
-      Client.compile(this.project_id, this.request, (error, res, body) => {
+      try {
+        this.body = await Client.compile(this.project_id, this.request)
+      } catch (error) {
         this.error = error
-        this.res = res
-        this.body = body
-        done(error)
-      })
+      }
     })
     it('should return non success', function () {
-      expect(this.res.statusCode).to.not.equal(200)
+      expect(this.error.response.status).to.equal(500)
     })
 
     it('should not return a PDF', function () {
@@ -72,118 +70,109 @@ Hello world
   })
 
   describe('syncToCode', function () {
-    beforeEach(function (done) {
-      Client.compile(this.project_id, this.request, done)
+    beforeEach(async function () {
+      await Client.compile(this.project_id, this.request)
     })
-    it('should error out with an invalid imageName', function (done) {
-      Client.syncFromCodeWithImage(
-        this.project_id,
-        'main.tex',
-        3,
-        5,
-        'something/evil:1337',
-        (error, body) => {
-          expect(String(error)).to.include('statusCode=400')
-          expect(body).to.equal('invalid image')
-          done()
-        }
-      )
+    it('should error out with an invalid imageName', async function () {
+      const rejects = () =>
+        expect(
+          Client.syncFromCodeWithImage(
+            this.project_id,
+            'main.tex',
+            3,
+            5,
+            'something/evil:1337'
+          )
+        ).to.eventually.be.rejected
+
+      await rejects().and.have.property('body', 'invalid image')
+      await rejects().and.have.property('info').to.contain({ status: 400 })
     })
 
-    it('should produce a mapping a valid imageName', function (done) {
-      Client.syncFromCodeWithImage(
+    it('should produce a mapping a valid imageName', async function () {
+      const result = await Client.syncFromCodeWithImage(
         this.project_id,
         'main.tex',
         3,
         5,
-        process.env.TEXLIVE_IMAGE,
-        (error, result) => {
-          expect(error).to.not.exist
-          expect(result).to.deep.equal({
-            pdf: [
-              {
-                page: 1,
-                h: 133.768356,
-                v: 134.764618,
-                height: 6.918498,
-                width: 343.71106,
-              },
-            ],
-            downloadedFromCache: false,
-          })
-          done()
-        }
+        process.env.TEXLIVE_IMAGE
       )
+      expect(result).to.deep.equal({
+        pdf: [
+          {
+            page: 1,
+            h: 133.768356,
+            v: 134.764618,
+            height: 6.918498,
+            width: 343.71106,
+          },
+        ],
+        downloadedFromCache: false,
+      })
     })
   })
 
   describe('syncToPdf', function () {
-    beforeEach(function (done) {
-      Client.compile(this.project_id, this.request, done)
+    beforeEach(async function () {
+      await Client.compile(this.project_id, this.request)
     })
-    it('should error out with an invalid imageName', function (done) {
-      Client.syncFromPdfWithImage(
-        this.project_id,
-        'main.tex',
-        100,
-        200,
-        'something/evil:1337',
-        (error, body) => {
-          expect(String(error)).to.include('statusCode=400')
-          expect(body).to.equal('invalid image')
-          done()
-        }
-      )
+    it('should error out with an invalid imageName', async function () {
+      const rejects = () =>
+        expect(
+          Client.syncFromPdfWithImage(
+            this.project_id,
+            'main.tex',
+            100,
+            200,
+            'something/evil:1337'
+          )
+        ).to.eventually.be.rejected
+
+      await rejects().and.have.property('body', 'invalid image')
+      await rejects().and.have.property('info').to.contain({ status: 400 })
     })
 
-    it('should produce a mapping a valid imageName', function (done) {
-      Client.syncFromPdfWithImage(
+    it('should produce a mapping a valid imageName', async function () {
+      const result = await Client.syncFromPdfWithImage(
         this.project_id,
         1,
         100,
         200,
-        process.env.TEXLIVE_IMAGE,
-        (error, result) => {
-          expect(error).to.not.exist
-          expect(result).to.deep.equal({
-            code: [{ file: 'main.tex', line: 3, column: -1 }],
-            downloadedFromCache: false,
-          })
-          done()
-        }
+        process.env.TEXLIVE_IMAGE
       )
+      expect(result).to.deep.equal({
+        code: [{ file: 'main.tex', line: 3, column: -1 }],
+        downloadedFromCache: false,
+      })
     })
   })
 
   describe('wordcount', function () {
-    beforeEach(function (done) {
-      Client.compile(this.project_id, this.request, done)
+    beforeEach(async function () {
+      await Client.compile(this.project_id, this.request)
     })
-    it('should error out with an invalid imageName', function (done) {
-      Client.wordcountWithImage(
-        this.project_id,
-        'main.tex',
-        'something/evil:1337',
-        (error, body) => {
-          expect(String(error)).to.include('statusCode=400')
-          expect(body).to.equal('invalid image')
-          done()
-        }
-      )
+    it('should error out with an invalid imageName', async function () {
+      const rejects = () =>
+        expect(
+          Client.wordcountWithImage(
+            this.project_id,
+            'main.tex',
+            'something/evil:1337'
+          )
+        ).to.eventually.be.rejected
+
+      await rejects().and.have.property('body', 'invalid image')
+      await rejects().and.have.property('info').to.contain({ status: 400 })
     })
 
-    it('should produce a texcout a valid imageName', function (done) {
-      Client.wordcountWithImage(
+    it('should produce a texcout a valid imageName', async function () {
+      const result = await Client.wordcountWithImage(
         this.project_id,
         'main.tex',
-        process.env.TEXLIVE_IMAGE,
-        (error, result) => {
-          expect(error).to.not.exist
-          expect(result).to.exist
-          expect(result.texcount).to.exist
-          done()
-        }
+        process.env.TEXLIVE_IMAGE
       )
+      expect(result).to.exist
+      expect(result.texcount).to.exist
     })
   })
 })

+ 10 - 40
services/clsi/test/acceptance/js/BrokenLatexFileTests.js

@@ -1,20 +1,9 @@
-/* eslint-disable
-    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 Client = require('./helpers/Client')
-const request = require('request')
 const ClsiApp = require('./helpers/ClsiApp')
 const { expect } = require('chai')
 
 describe('Broken LaTeX file', function () {
-  before(function (done) {
+  before(async function () {
     this.broken_request = {
       resources: [
         {
@@ -41,26 +30,17 @@ Hello world
         },
       ],
     }
-    return ClsiApp.ensureRunning(done)
+    await ClsiApp.ensureRunning()
   })
 
   describe('on first run', function () {
-    before(function (done) {
+    before(async function () {
       this.project_id = Client.randomId()
-      return Client.compile(
-        this.project_id,
-        this.broken_request,
-        (error, res, body) => {
-          this.error = error
-          this.res = res
-          this.body = body
-          return done()
-        }
-      )
+      this.body = await Client.compile(this.project_id, this.broken_request)
     })
 
     it('should return a failure status', function () {
-      return this.body.compile.status.should.equal('failure')
+      this.body.compile.status.should.equal('failure')
     })
 
     it('should return isInitialCompile flag', function () {
@@ -82,25 +62,15 @@ Hello world
     })
   })
 
-  return describe('on second run', function () {
-    before(function (done) {
+  describe('on second run', function () {
+    before(async function () {
       this.project_id = Client.randomId()
-      return Client.compile(this.project_id, this.correct_request, () => {
-        return Client.compile(
-          this.project_id,
-          this.broken_request,
-          (error, res, body) => {
-            this.error = error
-            this.res = res
-            this.body = body
-            return done()
-          }
-        )
-      })
+      await Client.compile(this.project_id, this.correct_request)
+      this.body = await Client.compile(this.project_id, this.broken_request)
     })
 
     it('should return a failure status', function () {
-      return this.body.compile.status.should.equal('failure')
+      this.body.compile.status.should.equal('failure')
     })
 
     it('should not return isInitialCompile flag', function () {

+ 11 - 40
services/clsi/test/acceptance/js/DeleteOldFilesTest.js

@@ -1,19 +1,8 @@
-/* eslint-disable
-    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 Client = require('./helpers/Client')
-const request = require('request')
 const ClsiApp = require('./helpers/ClsiApp')
 
 describe('Deleting Old Files', function () {
-  before(function (done) {
+  before(async function () {
     this.request = {
       resources: [
         {
@@ -27,45 +16,27 @@ Hello world
         },
       ],
     }
-    return ClsiApp.ensureRunning(done)
+    await ClsiApp.ensureRunning()
   })
 
-  return describe('on first run', function () {
-    before(function (done) {
+  describe('on first run', function () {
+    before(async function () {
       this.project_id = Client.randomId()
-      return Client.compile(
-        this.project_id,
-        this.request,
-        (error, res, body) => {
-          this.error = error
-          this.res = res
-          this.body = body
-          return done()
-        }
-      )
+      this.body = await Client.compile(this.project_id, this.request)
     })
 
     it('should return a success status', function () {
-      return this.body.compile.status.should.equal('success')
+      this.body.compile.status.should.equal('success')
     })
 
-    return describe('after file has been deleted', function () {
-      before(function (done) {
+    describe('after file has been deleted', function () {
+      before(async function () {
         this.request.resources = []
-        return Client.compile(
-          this.project_id,
-          this.request,
-          (error, res, body) => {
-            this.error = error
-            this.res = res
-            this.body = body
-            return done()
-          }
-        )
+        this.body = await Client.compile(this.project_id, this.request)
       })
 
-      return it('should return a failure status', function () {
-        return this.body.compile.status.should.equal('failure')
+      it('should return a failure status', function () {
+        this.body.compile.status.should.equal('failure')
       })
     })
   })

+ 122 - 216
services/clsi/test/acceptance/js/ExampleDocumentTests.js

@@ -1,24 +1,11 @@
-/* eslint-disable
-    no-return-assign,
-    no-unused-vars,
-*/
-// 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
- * DS103: Rewrite code to no longer use __guard__
- * DS207: Consider shorter variations of null checks
- * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
- */
 const Client = require('./helpers/Client')
 const fetch = require('node-fetch')
-const { pipeline } = require('node:stream')
+const Stream = require('node:stream')
 const fs = require('node:fs')
+const fsPromises = require('node:fs/promises')
 const ChildProcess = require('node:child_process')
+const { promisify } = require('node:util')
 const ClsiApp = require('./helpers/ClsiApp')
-const logger = require('@overleaf/logger')
 const Path = require('node:path')
 const fixturePath = path => {
   if (path.slice(0, 3) === 'tmp') {
@@ -27,6 +14,7 @@ const fixturePath = path => {
   return Path.join(__dirname, '../fixtures/', path)
 }
 const process = require('node:process')
+const pipeline = promisify(Stream.pipeline)
 console.log(
   process.pid,
   process.ppid,
@@ -37,249 +25,167 @@ console.log(
 
 const MOCHA_LATEX_TIMEOUT = 60 * 1000
 
-const convertToPng = function (pdfPath, pngPath, callback) {
-  if (callback == null) {
-    callback = function () {}
-  }
-  const command = `convert ${fixturePath(pdfPath)} ${fixturePath(pngPath)}`
-  console.log('COMMAND')
-  console.log(command)
-  const convert = ChildProcess.exec(command)
-  const stdout = ''
-  convert.stdout.on('data', chunk => console.log('STDOUT', chunk.toString()))
-  convert.stderr.on('data', chunk => console.log('STDERR', chunk.toString()))
-  return convert.on('exit', () => callback())
-}
-
-const compare = function (originalPath, generatedPath, callback) {
-  if (callback == null) {
-    callback = function () {}
-  }
-  const diffFile = `${fixturePath(generatedPath)}-diff.png`
-  const proc = ChildProcess.exec(
-    `compare -metric mae ${fixturePath(originalPath)} ${fixturePath(
-      generatedPath
-    )} ${diffFile}`
-  )
-  let stderr = ''
-  proc.stderr.on('data', chunk => (stderr += chunk))
-  return proc.on('exit', () => {
-    if (stderr.trim() === '0 (0)') {
-      // remove output diff if test matches expected image
-      fs.unlink(diffFile, err => {
-        if (err) {
-          throw err
-        }
-      })
-      return callback(null, true)
-    } else {
-      console.log('compare result', stderr)
-      return callback(null, false)
-    }
+const convertToPng = function (pdfPath, pngPath) {
+  return new Promise((resolve, reject) => {
+    const command = `convert ${fixturePath(pdfPath)} ${fixturePath(pngPath)}`
+    console.log('COMMAND')
+    console.log(command)
+    const convert = ChildProcess.exec(command)
+    convert.stdout.on('data', chunk => console.log('STDOUT', chunk.toString()))
+    convert.stderr.on('data', chunk => console.log('STDERR', chunk.toString()))
+    convert.on('exit', () => resolve())
+    convert.on('error', error => reject(error))
   })
 }
 
-const checkPdfInfo = function (pdfPath, callback) {
-  if (callback == null) {
-    callback = function () {}
-  }
-  const proc = ChildProcess.exec(`pdfinfo ${fixturePath(pdfPath)}`)
-  let stdout = ''
-  proc.stdout.on('data', chunk => (stdout += chunk))
-  proc.stderr.on('data', chunk => console.log('STDERR', chunk.toString()))
-  return proc.on('exit', () => {
-    if (stdout.match(/Optimized:\s+yes/)) {
-      return callback(null, true)
-    } else {
-      return callback(null, false)
-    }
+const compare = function (originalPath, generatedPath) {
+  return new Promise((resolve, reject) => {
+    const diffFile = `${fixturePath(generatedPath)}-diff.png`
+    const proc = ChildProcess.exec(
+      `compare -metric mae ${fixturePath(originalPath)} ${fixturePath(
+        generatedPath
+      )} ${diffFile}`
+    )
+    let stderr = ''
+    proc.stderr.on('data', chunk => (stderr += chunk))
+    proc.on('exit', () => {
+      if (stderr.trim() === '0 (0)') {
+        // remove output diff if test matches expected image
+        fs.unlink(diffFile, err => {
+          if (err) {
+            reject(err)
+          }
+        })
+        resolve(true)
+      } else {
+        console.log('compare result', stderr)
+        resolve(false)
+      }
+    })
   })
 }
 
-const compareMultiplePages = function (projectId, callback) {
-  if (callback == null) {
-    callback = function () {}
-  }
-  function compareNext(pageNo, callback) {
-    const path = `tmp/${projectId}-source-${pageNo}.png`
-    return fs.stat(fixturePath(path), (error, stat) => {
-      if (error != null) {
-        return callback()
+const checkPdfInfo = function (pdfPath) {
+  return new Promise((resolve, reject) => {
+    const proc = ChildProcess.exec(`pdfinfo ${fixturePath(pdfPath)}`)
+    let stdout = ''
+    proc.stdout.on('data', chunk => (stdout += chunk))
+    proc.stderr.on('data', chunk => console.log('STDERR', chunk.toString()))
+    proc.on('exit', () => {
+      if (stdout.match(/Optimized:\s+yes/)) {
+        resolve(true)
       } else {
-        return compare(
-          `tmp/${projectId}-source-${pageNo}.png`,
-          `tmp/${projectId}-generated-${pageNo}.png`,
-          (error, same) => {
-            if (error != null) {
-              throw error
-            }
-            same.should.equal(true)
-            return compareNext(pageNo + 1, callback)
-          }
-        )
+        resolve(false)
       }
     })
-  }
-  return compareNext(0, callback)
+    proc.on('error', error => reject(error))
+  })
 }
 
-const comparePdf = function (projectId, exampleDir, callback) {
-  if (callback == null) {
-    callback = function () {}
+const compareMultiplePages = async function (projectId) {
+  async function compareNext(pageNo) {
+    const path = `tmp/${projectId}-source-${pageNo}.png`
+    try {
+      await fsPromises.stat(fixturePath(path))
+    } catch (error) {
+      return
+    }
+
+    const same = await compare(
+      `tmp/${projectId}-source-${pageNo}.png`,
+      `tmp/${projectId}-generated-${pageNo}.png`
+    )
+    same.should.equal(true)
+    await compareNext(pageNo + 1)
   }
+  await compareNext(0)
+}
+
+const comparePdf = async function (projectId, exampleDir) {
   console.log('CONVERT')
   console.log(`tmp/${projectId}.pdf`, `tmp/${projectId}-generated.png`)
-  return convertToPng(
-    `tmp/${projectId}.pdf`,
-    `tmp/${projectId}-generated.png`,
-    error => {
-      if (error != null) {
-        throw error
-      }
-      return convertToPng(
-        `examples/${exampleDir}/output.pdf`,
-        `tmp/${projectId}-source.png`,
-        error => {
-          if (error != null) {
-            throw error
-          }
-          return fs.stat(
-            fixturePath(`tmp/${projectId}-source-0.png`),
-            (error, stat) => {
-              if (error != null) {
-                return compare(
-                  `tmp/${projectId}-source.png`,
-                  `tmp/${projectId}-generated.png`,
-                  (error, same) => {
-                    if (error != null) {
-                      throw error
-                    }
-                    same.should.equal(true)
-                    return callback()
-                  }
-                )
-              } else {
-                return compareMultiplePages(projectId, error => {
-                  if (error != null) {
-                    throw error
-                  }
-                  return callback()
-                })
-              }
-            }
-          )
-        }
-      )
-    }
+  await convertToPng(`tmp/${projectId}.pdf`, `tmp/${projectId}-generated.png`)
+  await convertToPng(
+    `examples/${exampleDir}/output.pdf`,
+    `tmp/${projectId}-source.png`
   )
+  try {
+    await fsPromises.stat(fixturePath(`tmp/${projectId}-source-0.png`))
+    await compareMultiplePages(projectId)
+  } catch (error) {
+    const same = await compare(
+      `tmp/${projectId}-source.png`,
+      `tmp/${projectId}-generated.png`
+    )
+    same.should.equal(true)
+  }
 }
 
-const downloadAndComparePdf = function (projectId, exampleDir, url, callback) {
-  fetch(url)
-    .then(res => {
-      if (!res.ok) {
-        return callback(new Error('non success response: ' + res.statusText))
-      }
-
-      const dest = fs.createWriteStream(fixturePath(`tmp/${projectId}.pdf`))
-      pipeline(res.body, dest, err => {
-        if (err) return callback(err)
-
-        checkPdfInfo(`tmp/${projectId}.pdf`, (err, optimised) => {
-          if (err) return callback(err)
-
-          optimised.should.equal(true)
-          comparePdf(projectId, exampleDir, callback)
-        })
-      })
-    })
-    .catch(callback)
+const downloadAndComparePdf = async function (projectId, exampleDir, url) {
+  const res = await fetch(url)
+  if (!res.ok) {
+    throw new Error('non success response: ' + res.statusText)
+  }
+  const dest = fs.createWriteStream(fixturePath(`tmp/${projectId}.pdf`))
+  await pipeline(res.body, dest)
+  const optimised = await checkPdfInfo(`tmp/${projectId}.pdf`)
+  optimised.should.equal(true)
+  await comparePdf(projectId, exampleDir)
 }
 
 describe('Example Documents', function () {
   Client.runFakeFilestoreService(fixturePath('examples'))
 
-  before(function (done) {
-    ClsiApp.ensureRunning(done)
+  before(async function () {
+    await ClsiApp.ensureRunning()
   })
-  before(function (done) {
-    fs.rm(fixturePath('tmp'), { force: true, recursive: true }, done)
+  before(async function () {
+    await fsPromises.rm(fixturePath('tmp'), { force: true, recursive: true })
   })
-  before(function (done) {
-    fs.mkdir(fixturePath('tmp'), done)
+  before(async function () {
+    await fsPromises.mkdir(fixturePath('tmp'))
   })
-  after(function (done) {
-    fs.rm(fixturePath('tmp'), { force: true, recursive: true }, done)
+  after(async function () {
+    await fsPromises.rm(fixturePath('tmp'), { force: true, recursive: true })
   })
 
-  return Array.from(fs.readdirSync(fixturePath('examples'))).map(exampleDir =>
+  return fs.readdirSync(fixturePath('examples')).map(exampleDir =>
     (exampleDir =>
       describe(exampleDir, function () {
         before(function () {
-          return (this.project_id = Client.randomId() + '_' + exampleDir)
+          this.project_id = Client.randomId() + '_' + exampleDir
         })
 
-        it('should generate the correct pdf', function (done) {
+        it('should generate the correct pdf', async function () {
           this.timeout(MOCHA_LATEX_TIMEOUT)
-          return Client.compileDirectory(
+          const body = await Client.compileDirectory(
             this.project_id,
             fixturePath('examples'),
-            exampleDir,
-            (error, res, body) => {
-              if (
-                error ||
-                __guard__(
-                  body != null ? body.compile : undefined,
-                  x => x.status
-                ) === 'failure'
-              ) {
-                console.log('DEBUG: error', error, 'body', JSON.stringify(body))
-                return done(new Error('Compile failed'))
-              }
-              const pdf = Client.getOutputFile(body, 'pdf')
-              return downloadAndComparePdf(
-                this.project_id,
-                exampleDir,
-                pdf.url,
-                done
-              )
-            }
+            exampleDir
           )
+
+          if (body?.compile?.status === 'failure') {
+            throw new Error('Compile failed')
+          }
+          const pdf = Client.getOutputFile(body, 'pdf')
+          await downloadAndComparePdf(this.project_id, exampleDir, pdf.url)
         })
 
-        return it('should generate the correct pdf on the second run as well', function (done) {
+        it('should generate the correct pdf on the second run as well', async function () {
           this.timeout(MOCHA_LATEX_TIMEOUT)
-          return Client.compileDirectory(
+          const body = await Client.compileDirectory(
             this.project_id,
             fixturePath('examples'),
-            exampleDir,
-            (error, res, body) => {
-              if (
-                error ||
-                __guard__(
-                  body != null ? body.compile : undefined,
-                  x => x.status
-                ) === 'failure'
-              ) {
-                console.log('DEBUG: error', error, 'body', JSON.stringify(body))
-                return done(new Error('Compile failed'))
-              }
-              const pdf = Client.getOutputFile(body, 'pdf')
-              return downloadAndComparePdf(
-                this.project_id,
-                exampleDir,
-                pdf.url,
-                done
-              )
-            }
+            exampleDir
           )
+
+          if (body?.compile?.status === 'failure') {
+            throw new Error('Compile failed')
+          }
+
+          const pdf = Client.getOutputFile(body, 'pdf')
+          await downloadAndComparePdf(this.project_id, exampleDir, pdf.url)
         })
       }))(exampleDir)
   )
 })
-
-function __guard__(value, transform) {
-  return typeof value !== 'undefined' && value !== null
-    ? transform(value)
-    : undefined
-}

+ 29 - 50
services/clsi/test/acceptance/js/SimpleLatexFileTests.js

@@ -1,17 +1,10 @@
-// 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 Client = require('./helpers/Client')
-const request = require('request')
+const { fetchString, fetchNothing } = require('@overleaf/fetch-utils')
 const ClsiApp = require('./helpers/ClsiApp')
 const Settings = require('@overleaf/settings')
 
 describe('Simple LaTeX file', function () {
-  before(function (done) {
+  before(async function () {
     this.project_id = Client.randomId()
     this.request = {
       resources: [
@@ -30,62 +23,48 @@ Hello world
         metricsMethod: 'priority',
       },
     }
-    return ClsiApp.ensureRunning(() => {
-      return Client.compile(
-        this.project_id,
-        this.request,
-        (error, res, body) => {
-          this.error = error
-          this.res = res
-          this.body = body
-          return done()
-        }
-      )
-    })
+
+    await ClsiApp.ensureRunning()
+    try {
+      this.body = await Client.compile(this.project_id, this.request)
+    } catch (error) {
+      this.error = error
+    }
   })
 
   it('should return the PDF', function () {
     const pdf = Client.getOutputFile(this.body, 'pdf')
-    return pdf.type.should.equal('pdf')
+    pdf.type.should.equal('pdf')
   })
 
   it('should return the log', function () {
     const log = Client.getOutputFile(this.body, 'log')
-    return log.type.should.equal('log')
+    log.type.should.equal('log')
   })
 
-  it('should provide the pdf for download', function (done) {
+  it('should provide the pdf for download', async function () {
     const pdf = Client.getOutputFile(this.body, 'pdf')
-    return request.get(pdf.url, (error, res, body) => {
-      if (error) return done(error)
-      res.statusCode.should.equal(200)
-      return done()
-    })
+    const response = await fetchNothing(pdf.url)
+    response.status.should.equal(200)
   })
 
-  it('should provide the log for download', function (done) {
+  it('should provide the log for download', async function () {
     const log = Client.getOutputFile(this.body, 'pdf')
-    return request.get(log.url, (error, res, body) => {
-      if (error) return done(error)
-      res.statusCode.should.equal(200)
-      return done()
-    })
+    const response = await fetchNothing(log.url)
+    response.status.should.equal(200)
   })
 
-  it('should gather personalized metrics', function (done) {
-    request.get(`${Settings.apis.clsi.url}/metrics`, (err, res, body) => {
-      if (err) return done(err)
-      body
-        .split('\n')
-        .some(line => {
-          return (
-            line.startsWith('compile') &&
-            line.includes('path="clsi-perf"') &&
-            line.includes('method="priority"')
-          )
-        })
-        .should.equal(true)
-      done()
-    })
+  it('should gather personalized metrics', async function () {
+    const body = await fetchString(`${Settings.apis.clsi.url}/metrics`)
+    body
+      .split('\n')
+      .some(line => {
+        return (
+          line.startsWith('compile') &&
+          line.includes('path="clsi-perf"') &&
+          line.includes('method="priority"')
+        )
+      })
+      .should.equal(true)
   })
 })

+ 6 - 14
services/clsi/test/acceptance/js/Stats.js

@@ -1,16 +1,8 @@
-const request = require('request')
+const { fetchString } = require('@overleaf/fetch-utils')
 const Settings = require('@overleaf/settings')
-after(function (done) {
-  request(
-    {
-      url: `${Settings.apis.clsi.url}/metrics`,
-    },
-    (err, response, body) => {
-      if (err) return done(err)
-      console.error('-- metrics --')
-      console.error(body)
-      console.error('-- metrics --')
-      done()
-    }
-  )
+after(async function () {
+  const metrics = await fetchString(`${Settings.apis.clsi.url}/metrics`)
+  console.error('-- metrics --')
+  console.error(metrics)
+  console.error('-- metrics --')
 })

+ 29 - 16
services/clsi/test/acceptance/js/StopCompile.js

@@ -1,9 +1,12 @@
+const { promisify } = require('node:util')
 const Client = require('./helpers/Client')
 const ClsiApp = require('./helpers/ClsiApp')
 const { expect } = require('chai')
 
+const sleep = promisify(setTimeout)
+
 describe('Stop compile', function () {
-  before(function (done) {
+  before(async function () {
     this.request = {
       options: {
         timeout: 100,
@@ -22,25 +25,35 @@ describe('Stop compile', function () {
       ],
     }
     this.project_id = Client.randomId()
-    ClsiApp.ensureRunning(() => {
-      // start the compile in the background
-      Client.compile(this.project_id, this.request, (error, res, body) => {
-        this.compileResult = { error, res, body }
+    await ClsiApp.ensureRunning()
+
+    // start the compile in the background
+    Client.compile(this.project_id, this.request)
+      .then(body => {
+        this.compileResult = { body }
       })
-      // wait for 1 second before stopping the compile
-      setTimeout(() => {
-        Client.stopCompile(this.project_id, (error, res, body) => {
-          this.stopResult = { error, res, body }
-          setTimeout(done, 1000) // allow time for the compile request to terminate
-        })
-      }, 1000)
-    })
+      .catch(error => {
+        this.compileResult = { error }
+      })
+
+    // wait for 1 second before stopping the compile
+    await sleep(1000)
+
+    try {
+      const res = await Client.stopCompile(this.project_id)
+      this.stopResult = { res }
+    } catch (error) {
+      this.stopResult = { error }
+    }
+
+    // allow time for the compile request to terminate
+    await sleep(1000)
   })
 
   it('should force a compile response with an error status', function () {
-    expect(this.stopResult.error).to.be.null
-    expect(this.stopResult.res.statusCode).to.equal(204)
-    expect(this.compileResult.res.statusCode).to.equal(200)
+    expect(this.stopResult.error).not.to.exist
+    expect(this.stopResult.res.status).to.equal(204)
+    expect(this.compileResult.error).not.to.exist
     expect(this.compileResult.body.compile.status).to.equal('terminated')
     expect(this.compileResult.body.compile.error).to.equal('terminated')
   })

+ 55 - 119
services/clsi/test/acceptance/js/SynctexTests.js

@@ -1,22 +1,9 @@
-/* eslint-disable
-    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
- */
 const Client = require('./helpers/Client')
-const request = require('request')
 const { expect } = require('chai')
 const ClsiApp = require('./helpers/ClsiApp')
-const crypto = require('node:crypto')
 
 describe('Syncing', function () {
-  before(function (done) {
+  before(async function () {
     const content = `\
 \\documentclass{article}
 \\begin{document}
@@ -32,67 +19,45 @@ Hello world
       ],
     }
     this.project_id = Client.randomId()
-    return ClsiApp.ensureRunning(() => {
-      return Client.compile(
-        this.project_id,
-        this.request,
-        (error, res, body) => {
-          this.error = error
-          this.res = res
-          this.body = body
-          return done()
-        }
-      )
-    })
+    await ClsiApp.ensureRunning()
+    this.body = await Client.compile(this.project_id, this.request)
   })
 
   describe('from code to pdf', function () {
-    return it('should return the correct location', function (done) {
-      return Client.syncFromCode(
+    it('should return the correct location', async function () {
+      const pdfPositions = await Client.syncFromCode(
         this.project_id,
         'main.tex',
         3,
-        5,
-        (error, pdfPositions) => {
-          if (error != null) {
-            throw error
-          }
-          expect(pdfPositions).to.deep.equal({
-            pdf: [
-              {
-                page: 1,
-                h: 133.768356,
-                v: 134.764618,
-                height: 6.918498,
-                width: 343.71106,
-              },
-            ],
-            downloadedFromCache: false,
-          })
-          return done()
-        }
+        5
       )
+      expect(pdfPositions).to.deep.equal({
+        pdf: [
+          {
+            page: 1,
+            h: 133.768356,
+            v: 134.764618,
+            height: 6.918498,
+            width: 343.71106,
+          },
+        ],
+        downloadedFromCache: false,
+      })
     })
   })
 
   describe('from pdf to code', function () {
-    return it('should return the correct location', function (done) {
-      return Client.syncFromPdf(
+    it('should return the correct location', async function () {
+      const codePositions = await Client.syncFromPdf(
         this.project_id,
         1,
         100,
-        200,
-        (error, codePositions) => {
-          if (error != null) {
-            throw error
-          }
-          expect(codePositions).to.deep.equal({
-            code: [{ file: 'main.tex', line: 3, column: -1 }],
-            downloadedFromCache: false,
-          })
-          return done()
-        }
+        200
       )
+      expect(codePositions).to.deep.equal({
+        code: [{ file: 'main.tex', line: 3, column: -1 }],
+        downloadedFromCache: false,
+      })
     })
   })
 
@@ -101,39 +66,29 @@ Hello world
       this.other_project_id = Client.randomId()
     })
     describe('from code to pdf', function () {
-      it('should return a 404 response', function (done) {
-        return Client.syncFromCode(
-          this.other_project_id,
-          'main.tex',
-          3,
-          5,
-          (error, body) => {
-            expect(String(error)).to.include('statusCode=404')
-            expect(body).to.equal('Not Found')
-            return done()
-          }
-        )
+      it('should return a 404 response', async function () {
+        const rejects = () =>
+          expect(Client.syncFromCode(this.other_project_id, 'main.tex', 3, 5))
+            .to.eventually.be.rejected
+
+        await rejects().and.have.property('info').to.contain({ status: 404 })
+        await rejects().and.have.property('body', 'Not Found')
       })
     })
     describe('from pdf to code', function () {
-      it('should return a 404 response', function (done) {
-        return Client.syncFromPdf(
-          this.other_project_id,
-          1,
-          100,
-          200,
-          (error, body) => {
-            expect(String(error)).to.include('statusCode=404')
-            expect(body).to.equal('Not Found')
-            return done()
-          }
-        )
+      it('should return a 404 response', async function () {
+        const rejects = () =>
+          expect(Client.syncFromPdf(this.other_project_id, 1, 100, 200)).to
+            .eventually.be.rejected
+
+        await rejects().and.have.property('info').to.contain({ status: 404 })
+        await rejects().and.have.property('body', 'Not Found')
       })
     })
   })
 
   describe('when the synctex file is not available', function () {
-    before(function (done) {
+    before(async function () {
       this.broken_project_id = Client.randomId()
       const content = 'this is not valid tex' // not a valid tex file
       this.request = {
@@ -144,46 +99,27 @@ Hello world
           },
         ],
       }
-      Client.compile(
-        this.broken_project_id,
-        this.request,
-        (error, res, body) => {
-          this.error = error
-          this.res = res
-          this.body = body
-          return done()
-        }
-      )
+      this.body = await Client.compile(this.broken_project_id, this.request)
     })
 
     describe('from code to pdf', function () {
-      it('should return a 404 response', function (done) {
-        return Client.syncFromCode(
-          this.broken_project_id,
-          'main.tex',
-          3,
-          5,
-          (error, body) => {
-            expect(String(error)).to.include('statusCode=404')
-            expect(body).to.equal('Not Found')
-            return done()
-          }
-        )
+      it('should return a 404 response', async function () {
+        const rejects = () =>
+          expect(Client.syncFromCode(this.broken_project_id, 'main.tex', 3, 5))
+            .to.eventually.be.rejected
+
+        await rejects().and.have.property('info').to.contain({ status: 404 })
+        await rejects().and.have.property('body', 'Not Found')
       })
     })
     describe('from pdf to code', function () {
-      it('should return a 404 response', function (done) {
-        return Client.syncFromPdf(
-          this.broken_project_id,
-          1,
-          100,
-          200,
-          (error, body) => {
-            expect(String(error)).to.include('statusCode=404')
-            expect(body).to.equal('Not Found')
-            return done()
-          }
-        )
+      it('should return a 404 response', async function () {
+        const rejects = () =>
+          expect(Client.syncFromPdf(this.broken_project_id, 1, 100, 200)).to
+            .eventually.be.rejected
+
+        await rejects().and.have.property('info').to.contain({ status: 404 })
+        await rejects().and.have.property('body', 'Not Found')
       })
     })
   })

+ 7 - 28
services/clsi/test/acceptance/js/TimeoutTests.js

@@ -1,20 +1,9 @@
-/* eslint-disable
-    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 Client = require('./helpers/Client')
-const request = require('request')
 const ClsiApp = require('./helpers/ClsiApp')
 const { expect } = require('chai')
 
 describe('Timed out compile', function () {
-  before(function (done) {
+  before(async function () {
     this.request = {
       options: {
         timeout: 10,
@@ -33,34 +22,24 @@ describe('Timed out compile', function () {
       ],
     }
     this.project_id = Client.randomId()
-    return ClsiApp.ensureRunning(() => {
-      return Client.compile(
-        this.project_id,
-        this.request,
-        (error, res, body) => {
-          this.error = error
-          this.res = res
-          this.body = body
-          return done()
-        }
-      )
-    })
+    await ClsiApp.ensureRunning()
+    this.body = await Client.compile(this.project_id, this.request)
   })
 
   it('should return a timeout error', function () {
-    return this.body.compile.error.should.equal('container timed out')
+    this.body.compile.error.should.equal('container timed out')
   })
 
   it('should return a timedout status', function () {
-    return this.body.compile.status.should.equal('timedout')
+    this.body.compile.status.should.equal('timedout')
   })
 
   it('should return isInitialCompile flag', function () {
     expect(this.body.compile.stats.isInitialCompile).to.equal(1)
   })
 
-  return it('should return the log output file name', function () {
+  it('should return the log output file name', function () {
     const outputFilePaths = this.body.compile.outputFiles.map(x => x.path)
-    return outputFilePaths.should.include('output.log')
+    outputFilePaths.should.include('output.log')
   })
 })

+ 99 - 232
services/clsi/test/acceptance/js/UrlCachingTests.js

@@ -1,20 +1,9 @@
-/* eslint-disable
-    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
- */
 const express = require('express')
 const Path = require('node:path')
 const Client = require('./helpers/Client')
 const sinon = require('sinon')
 const ClsiApp = require('./helpers/ClsiApp')
-const request = require('request')
+const { fetchString } = require('@overleaf/fetch-utils')
 const Settings = require('@overleaf/settings')
 
 const Server = {
@@ -44,18 +33,18 @@ const Server = {
 
     app.get('/project/:projectId/file/:fileId', (req, res, next) => {
       this.getFile(req.url)
-      return res.send(`${req.params.projectId}:${req.params.fileId}`)
+      res.send(`${req.params.projectId}:${req.params.fileId}`)
     })
 
     app.get('/bucket/:bucket/key/*', (req, res, next) => {
       this.getFile(req.url)
-      return res.send(`${req.params.bucket}:${req.params[0]}`)
+      res.send(`${req.params.bucket}:${req.params[0]}`)
     })
 
     app.get('/:random_id/*', (req, res, next) => {
       this.getFile(req.url)
       req.url = `/${req.params[0]}`
-      return staticServer(req, res, next)
+      staticServer(req, res, next)
     })
 
     Client.startFakeFilestoreApp(app)
@@ -72,7 +61,7 @@ describe('Url Caching', function () {
   Server.run()
 
   describe('Retries', function () {
-    before(function (done) {
+    before(async function () {
       this.project_id = Client.randomId()
       this.happyFile = `${Server.randomId()}/lion.png`
       this.retryFileOnce = `fail/1/${Server.randomId()}`
@@ -110,14 +99,8 @@ describe('Url Caching', function () {
       }
 
       sinon.spy(Server, 'getFile')
-      ClsiApp.ensureRunning(() => {
-        Client.compile(this.project_id, this.request, (error, res, body) => {
-          this.error = error
-          this.res = res
-          this.body = body
-          done()
-        })
-      })
+      await ClsiApp.ensureRunning()
+      this.body = await Client.compile(this.project_id, this.request)
     })
 
     after(function () {
@@ -139,7 +122,7 @@ describe('Url Caching', function () {
   })
 
   describe('Downloading an image for the first time', function () {
-    before(function (done) {
+    before(async function () {
       this.project_id = Client.randomId()
       this.file = `${Server.randomId()}/lion.png`
       this.request = {
@@ -162,31 +145,21 @@ describe('Url Caching', function () {
       }
 
       sinon.spy(Server, 'getFile')
-      return ClsiApp.ensureRunning(() => {
-        return Client.compile(
-          this.project_id,
-          this.request,
-          (error, res, body) => {
-            this.error = error
-            this.res = res
-            this.body = body
-            return done()
-          }
-        )
-      })
+      await ClsiApp.ensureRunning()
+      this.body = await Client.compile(this.project_id, this.request)
     })
 
     afterEach(function () {
-      return Server.getFile.restore()
+      Server.getFile.restore()
     })
 
-    return it('should download the image', function () {
-      return Server.getFile.calledWith(`/${this.file}`).should.equal(true)
+    it('should download the image', function () {
+      Server.getFile.calledWith(`/${this.file}`).should.equal(true)
     })
   })
 
   describe('When an image is in the cache and the last modified date is unchanged', function () {
-    before(function (done) {
+    before(async function () {
       this.project_id = Client.randomId()
       this.file = `${Server.randomId()}/lion.png`
       this.request = {
@@ -209,54 +182,34 @@ describe('Url Caching', function () {
         ],
       }
 
-      return Client.compile(
-        this.project_id,
-        this.request,
-        (error, res, body) => {
-          this.error = error
-          this.res = res
-          this.body = body
-          sinon.spy(Server, 'getFile')
-          return Client.compile(
-            this.project_id,
-            this.request,
-            (error1, res1, body1) => {
-              this.error = error1
-              this.res = res1
-              this.body = body1
-              return done()
-            }
-          )
-        }
-      )
+      await Client.compile(this.project_id, this.request)
+      sinon.spy(Server, 'getFile')
+      await Client.compile(this.project_id, this.request)
     })
 
     after(function () {
-      return Server.getFile.restore()
+      Server.getFile.restore()
     })
 
     it('should not download the image again', function () {
-      return Server.getFile.called.should.equal(false)
+      Server.getFile.called.should.equal(false)
     })
 
-    it('should gather metrics', function (done) {
-      request.get(`${Settings.apis.clsi.url}/metrics`, (err, res, body) => {
-        if (err) return done(err)
-        body
-          .split('\n')
-          .some(line => {
-            return (
-              line.startsWith('url_source') && line.includes('path="unknown"')
-            )
-          })
-          .should.equal(true)
-        done()
-      })
+    it('should gather metrics', async function () {
+      const body = await fetchString(`${Settings.apis.clsi.url}/metrics`)
+      body
+        .split('\n')
+        .some(line => {
+          return (
+            line.startsWith('url_source') && line.includes('path="unknown"')
+          )
+        })
+        .should.equal(true)
     })
   })
 
   describe('When an image is in the cache and the last modified date is advanced', function () {
-    before(function (done) {
+    before(async function () {
       this.project_id = Client.randomId()
       this.file = `${Server.randomId()}/lion.png`
       this.request = {
@@ -279,40 +232,25 @@ describe('Url Caching', function () {
         ],
       }
 
-      return Client.compile(
-        this.project_id,
-        this.request,
-        (error, res, body) => {
-          this.error = error
-          this.res = res
-          this.body = body
-          sinon.spy(Server, 'getFile')
-          this.image_resource.modified = new Date(this.last_modified + 3000)
-          return Client.compile(
-            this.project_id,
-            this.request,
-            (error1, res1, body1) => {
-              this.error = error1
-              this.res = res1
-              this.body = body1
-              return done()
-            }
-          )
-        }
-      )
+      await Client.compile(this.project_id, this.request)
+
+      sinon.spy(Server, 'getFile')
+      this.image_resource.modified = new Date(this.last_modified + 3000)
+
+      await Client.compile(this.project_id, this.request)
     })
 
     afterEach(function () {
-      return Server.getFile.restore()
+      Server.getFile.restore()
     })
 
-    return it('should download the image again', function () {
-      return Server.getFile.called.should.equal(true)
+    it('should download the image again', function () {
+      Server.getFile.called.should.equal(true)
     })
   })
 
   describe('When an image is in the cache and the last modified date is further in the past', function () {
-    before(function (done) {
+    before(async function () {
       this.project_id = Client.randomId()
       this.file = `${Server.randomId()}/lion.png`
       this.request = {
@@ -335,40 +273,25 @@ describe('Url Caching', function () {
         ],
       }
 
-      return Client.compile(
-        this.project_id,
-        this.request,
-        (error, res, body) => {
-          this.error = error
-          this.res = res
-          this.body = body
-          sinon.spy(Server, 'getFile')
-          this.image_resource.modified = new Date(this.last_modified - 3000)
-          return Client.compile(
-            this.project_id,
-            this.request,
-            (error1, res1, body1) => {
-              this.error = error1
-              this.res = res1
-              this.body = body1
-              return done()
-            }
-          )
-        }
-      )
+      await Client.compile(this.project_id, this.request)
+
+      sinon.spy(Server, 'getFile')
+      this.image_resource.modified = new Date(this.last_modified - 3000)
+
+      await Client.compile(this.project_id, this.request)
     })
 
     afterEach(function () {
-      return Server.getFile.restore()
+      Server.getFile.restore()
     })
 
-    return it('should download the other revision', function () {
-      return Server.getFile.called.should.equal(true)
+    it('should download the other revision', function () {
+      Server.getFile.called.should.equal(true)
     })
   })
 
   describe('When an image is in the cache and the last modified date is not specified', function () {
-    before(function (done) {
+    before(async function () {
       this.project_id = Client.randomId()
       this.file = `${Server.randomId()}/lion.png`
       this.request = {
@@ -391,40 +314,25 @@ describe('Url Caching', function () {
         ],
       }
 
-      return Client.compile(
-        this.project_id,
-        this.request,
-        (error, res, body) => {
-          this.error = error
-          this.res = res
-          this.body = body
-          sinon.spy(Server, 'getFile')
-          delete this.image_resource.modified
-          return Client.compile(
-            this.project_id,
-            this.request,
-            (error1, res1, body1) => {
-              this.error = error1
-              this.res = res1
-              this.body = body1
-              return done()
-            }
-          )
-        }
-      )
+      await Client.compile(this.project_id, this.request)
+
+      sinon.spy(Server, 'getFile')
+      delete this.image_resource.modified
+
+      await Client.compile(this.project_id, this.request)
     })
 
     afterEach(function () {
-      return Server.getFile.restore()
+      Server.getFile.restore()
     })
 
-    return it('should download the image again', function () {
-      return Server.getFile.called.should.equal(true)
+    it('should download the image again', function () {
+      Server.getFile.called.should.equal(true)
     })
   })
 
   describe('After clearing the cache', function () {
-    before(function (done) {
+    before(async function () {
       this.project_id = Client.randomId()
       this.file = `${Server.randomId()}/lion.png`
       this.request = {
@@ -447,41 +355,26 @@ describe('Url Caching', function () {
         ],
       }
 
-      return Client.compile(this.project_id, this.request, error => {
-        if (error != null) {
-          throw error
-        }
-        return Client.clearCache(this.project_id, (error, res, body) => {
-          if (error != null) {
-            throw error
-          }
-          sinon.spy(Server, 'getFile')
-          return Client.compile(
-            this.project_id,
-            this.request,
-            (error1, res1, body1) => {
-              this.error = error1
-              this.res = res1
-              this.body = body1
-              return done()
-            }
-          )
-        })
-      })
+      await Client.compile(this.project_id, this.request)
+      await Client.clearCache(this.project_id)
+
+      sinon.spy(Server, 'getFile')
+
+      await Client.compile(this.project_id, this.request)
     })
 
     afterEach(function () {
-      return Server.getFile.restore()
+      Server.getFile.restore()
     })
 
-    return it('should download the image again', function () {
-      return Server.getFile.called.should.equal(true)
+    it('should download the image again', function () {
+      Server.getFile.called.should.equal(true)
     })
   })
 
   describe('fallbackURL', function () {
     describe('when the primary resource is available', function () {
-      before(function (done) {
+      before(async function () {
         this.project_id = Client.randomId()
         this.file = `/project/${Server.randomId()}/file/${Server.randomId()}`
         this.fallback = `/bucket/project-blobs/key/ab/cd/${Server.randomId()}`
@@ -506,22 +399,12 @@ describe('Url Caching', function () {
         }
 
         sinon.spy(Server, 'getFile')
-        return ClsiApp.ensureRunning(() => {
-          return Client.compile(
-            this.project_id,
-            this.request,
-            (error, res, body) => {
-              this.error = error
-              this.res = res
-              this.body = body
-              return done()
-            }
-          )
-        })
+        await ClsiApp.ensureRunning()
+        await Client.compile(this.project_id, this.request)
       })
 
       after(function () {
-        return Server.getFile.restore()
+        Server.getFile.restore()
       })
 
       it('should download from the primary', function () {
@@ -531,25 +414,22 @@ describe('Url Caching', function () {
         Server.getFile.calledWith(this.fallback).should.equal(false)
       })
 
-      it('should gather metrics', function (done) {
-        request.get(`${Settings.apis.clsi.url}/metrics`, (err, res, body) => {
-          if (err) return done(err)
-          body
-            .split('\n')
-            .some(line => {
-              return (
-                line.startsWith('url_source') &&
-                line.includes('path="user-files"')
-              )
-            })
-            .should.equal(true)
-          done()
-        })
+      it('should gather metrics', async function () {
+        const body = await fetchString(`${Settings.apis.clsi.url}/metrics`)
+        body
+          .split('\n')
+          .some(line => {
+            return (
+              line.startsWith('url_source') &&
+              line.includes('path="user-files"')
+            )
+          })
+          .should.equal(true)
       })
     })
 
     describe('when the primary resource is not available', function () {
-      before(function (done) {
+      before(async function () {
         this.project_id = Client.randomId()
         this.file = `/project/${Server.randomId()}/file/${Server.randomId()}`
         this.fallback = `/bucket/project-blobs/key/ab/cd/${Server.randomId()}`
@@ -574,22 +454,12 @@ describe('Url Caching', function () {
         }
 
         sinon.spy(Server, 'getFile')
-        return ClsiApp.ensureRunning(() => {
-          return Client.compile(
-            this.project_id,
-            this.request,
-            (error, res, body) => {
-              this.error = error
-              this.res = res
-              this.body = body
-              return done()
-            }
-          )
-        })
+        await ClsiApp.ensureRunning()
+        await Client.compile(this.project_id, this.request)
       })
 
       after(function () {
-        return Server.getFile.restore()
+        Server.getFile.restore()
       })
 
       it('should download from the fallback', function () {
@@ -597,20 +467,17 @@ describe('Url Caching', function () {
         Server.getFile.calledWith(this.fallback).should.equal(true)
       })
 
-      it('should gather metrics', function (done) {
-        request.get(`${Settings.apis.clsi.url}/metrics`, (err, res, body) => {
-          if (err) return done(err)
-          body
-            .split('\n')
-            .some(line => {
-              return (
-                line.startsWith('url_source') &&
-                line.includes('path="project-blobs"')
-              )
-            })
-            .should.equal(true)
-          done()
-        })
+      it('should gather metrics', async function () {
+        const body = await fetchString(`${Settings.apis.clsi.url}/metrics`)
+        body
+          .split('\n')
+          .some(line => {
+            return (
+              line.startsWith('url_source') &&
+              line.includes('path="project-blobs"')
+            )
+          })
+          .should.equal(true)
       })
     })
   })

+ 19 - 46
services/clsi/test/acceptance/js/WordcountTests.js

@@ -1,23 +1,11 @@
-/* eslint-disable
-    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
- */
 const Client = require('./helpers/Client')
-const request = require('request')
 const { expect } = require('chai')
 const path = require('node:path')
 const fs = require('node:fs')
 const ClsiApp = require('./helpers/ClsiApp')
 
 describe('Syncing', function () {
-  before(function (done) {
+  before(async function () {
     this.request = {
       resources: [
         {
@@ -30,41 +18,26 @@ describe('Syncing', function () {
       ],
     }
     this.project_id = Client.randomId()
-    return ClsiApp.ensureRunning(() => {
-      return Client.compile(
-        this.project_id,
-        this.request,
-        (error, res, body) => {
-          this.error = error
-          this.res = res
-          this.body = body
-          return done()
-        }
-      )
-    })
+    await ClsiApp.ensureRunning()
+    this.body = await Client.compile(this.project_id, this.request)
   })
 
-  return describe('wordcount file', function () {
-    return it('should return wordcount info', function (done) {
-      return Client.wordcount(this.project_id, 'main.tex', (error, result) => {
-        if (error != null) {
-          throw error
-        }
-        expect(result).to.deep.equal({
-          texcount: {
-            encode: 'utf8',
-            textWords: 2281,
-            headWords: 2,
-            outside: 0,
-            headers: 2,
-            elements: 0,
-            mathInline: 6,
-            mathDisplay: 0,
-            errors: 0,
-            messages: '',
-          },
-        })
-        return done()
+  describe('wordcount file', function () {
+    it('should return wordcount info', async function () {
+      const result = await Client.wordcount(this.project_id, 'main.tex')
+      expect(result).to.deep.equal({
+        texcount: {
+          encode: 'utf8',
+          textWords: 2281,
+          headWords: 2,
+          outside: 0,
+          headers: 2,
+          elements: 0,
+          mathInline: 6,
+          mathDisplay: 0,
+          errors: 0,
+          messages: '',
+        },
       })
     })
   })

+ 168 - 231
services/clsi/test/acceptance/js/helpers/Client.js

@@ -1,258 +1,195 @@
-/* eslint-disable
-    no-unused-vars,
-*/
-// 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 Client
 const express = require('express')
-const request = require('request')
+const { fetchJson, fetchNothing } = require('@overleaf/fetch-utils')
 const fs = require('node:fs')
+const fsPromises = require('node:fs/promises')
 const Settings = require('@overleaf/settings')
 
-module.exports = Client = {
-  host: Settings.apis.clsi.url,
+const host = Settings.apis.clsi.url
 
-  randomId() {
-    return Math.random().toString(16).slice(2)
-  },
+function randomId() {
+  return Math.random().toString(16).slice(2)
+}
 
-  compile(projectId, data, callback) {
-    if (callback == null) {
-      callback = function () {}
-    }
-    if (data) {
-      // Enable pdf caching unless disabled explicitly.
-      data.options = Object.assign({}, { enablePdfCaching: true }, data.options)
-    }
-    return request.post(
-      {
-        url: `${this.host}/project/${projectId}/compile`,
-        json: {
-          compile: data,
-        },
-      },
-      callback
-    )
-  },
+function compile(projectId, data) {
+  if (data) {
+    // Enable pdf caching unless disabled explicitly.
+    data.options = Object.assign({}, { enablePdfCaching: true }, data.options)
+  }
+  return fetchJson(`${host}/project/${projectId}/compile`, {
+    method: 'POST',
+    json: {
+      compile: data,
+    },
+  })
+}
 
-  stopCompile(projectId, callback) {
-    if (callback == null) {
-      callback = function () {}
-    }
-    return request.post(
-      { url: `${this.host}/project/${projectId}/compile/stop` },
-      callback
-    )
-  },
+async function stopCompile(projectId) {
+  return await fetchNothing(`${host}/project/${projectId}/compile/stop`, {
+    method: 'POST',
+  })
+}
 
-  clearCache(projectId, callback) {
-    if (callback == null) {
-      callback = function () {}
-    }
-    return request.del(`${this.host}/project/${projectId}`, callback)
-  },
+async function clearCache(projectId) {
+  await fetchNothing(`${host}/project/${projectId}`, {
+    method: 'DELETE',
+  })
+}
 
-  getOutputFile(response, type) {
-    for (const file of Array.from(response.compile.outputFiles)) {
-      if (file.type === type && file.url.match(`output.${type}`)) {
-        return file
-      }
+function getOutputFile(response, type) {
+  for (const file of response.compile.outputFiles) {
+    if (file.type === type && file.url.match(`output.${type}`)) {
+      return file
     }
-    return null
-  },
+  }
+  return null
+}
 
-  runFakeFilestoreService(directory) {
-    const app = express()
-    app.use(express.static(directory))
-    this.startFakeFilestoreApp(app)
-  },
+function runFakeFilestoreService(directory) {
+  const app = express()
+  app.use(express.static(directory))
+  this.startFakeFilestoreApp(app)
+}
 
-  startFakeFilestoreApp(app) {
-    let server
-    before(function (done) {
-      server = app.listen(error => {
-        if (error) {
-          done(new Error('error starting server: ' + error.message))
-        } else {
-          const addr = server.address()
-          Settings.filestoreDomainOveride = `http://127.0.0.1:${addr.port}`
-          done()
-        }
-      })
-    })
-    after(function (done) {
-      server.close(done)
+function startFakeFilestoreApp(app) {
+  let server
+  before(function (done) {
+    server = app.listen(error => {
+      if (error) {
+        done(new Error('error starting server: ' + error.message))
+      } else {
+        const addr = server.address()
+        Settings.filestoreDomainOveride = `http://127.0.0.1:${addr.port}`
+        done()
+      }
     })
-  },
+  })
+  after(function (done) {
+    server.close(done)
+  })
+}
 
-  syncFromCode(projectId, file, line, column, callback) {
-    Client.syncFromCodeWithImage(projectId, file, line, column, '', callback)
-  },
+function syncFromCode(projectId, file, line, column) {
+  return syncFromCodeWithImage(projectId, file, line, column, '')
+}
 
-  syncFromCodeWithImage(projectId, file, line, column, imageName, callback) {
-    if (callback == null) {
-      callback = function () {}
-    }
-    return request.get(
-      {
-        url: `${this.host}/project/${projectId}/sync/code`,
-        qs: {
-          imageName,
-          file,
-          line,
-          column,
-        },
-        json: true,
-      },
-      (error, response, body) => {
-        if (error != null) {
-          return callback(error)
-        }
-        if (response.statusCode !== 200) {
-          return callback(new Error(`statusCode=${response.statusCode}`), body)
-        }
-        return callback(null, body)
-      }
-    )
-  },
+async function syncFromCodeWithImage(projectId, file, line, column, imageName) {
+  const url = new URL(`${host}/project/${projectId}/sync/code`)
+  url.searchParams.append('imageName', imageName)
+  url.searchParams.append('file', file)
+  url.searchParams.append('line', line)
+  url.searchParams.append('column', column)
+  return await fetchJson(url)
+}
 
-  syncFromPdf(projectId, page, h, v, callback) {
-    Client.syncFromPdfWithImage(projectId, page, h, v, '', callback)
-  },
+function syncFromPdf(projectId, page, h, v) {
+  return syncFromPdfWithImage(projectId, page, h, v, '')
+}
 
-  syncFromPdfWithImage(projectId, page, h, v, imageName, callback) {
-    if (callback == null) {
-      callback = function () {}
-    }
-    return request.get(
-      {
-        url: `${this.host}/project/${projectId}/sync/pdf`,
-        qs: {
-          imageName,
-          page,
-          h,
-          v,
-        },
-        json: true,
-      },
-      (error, response, body) => {
-        if (error != null) {
-          return callback(error)
-        }
-        if (response.statusCode !== 200) {
-          return callback(new Error(`statusCode=${response.statusCode}`), body)
-        }
-        return callback(null, body)
-      }
-    )
-  },
+function syncFromPdfWithImage(projectId, page, h, v, imageName) {
+  const url = new URL(`${host}/project/${projectId}/sync/pdf`)
+  url.searchParams.append('imageName', imageName)
+  url.searchParams.append('page', page)
+  url.searchParams.append('h', h)
+  url.searchParams.append('v', v)
+  return fetchJson(url)
+}
 
-  compileDirectory(projectId, baseDirectory, directory, callback) {
-    if (callback == null) {
-      callback = function () {}
-    }
-    const resources = []
-    let entities = fs.readdirSync(`${baseDirectory}/${directory}`)
-    let rootResourcePath = 'main.tex'
-    while (entities.length > 0) {
-      const entity = entities.pop()
-      const stat = fs.statSync(`${baseDirectory}/${directory}/${entity}`)
-      if (stat.isDirectory()) {
-        entities = entities.concat(
-          fs
-            .readdirSync(`${baseDirectory}/${directory}/${entity}`)
-            .map(subEntity => {
-              if (subEntity === 'main.tex') {
-                rootResourcePath = `${entity}/${subEntity}`
-              }
-              return `${entity}/${subEntity}`
-            })
-        )
-      } else if (stat.isFile() && entity !== 'output.pdf') {
-        const extension = entity.split('.').pop()
-        if (
-          [
-            'tex',
-            'bib',
-            'cls',
-            'sty',
-            'pdf_tex',
-            'Rtex',
-            'ist',
-            'md',
-            'Rmd',
-            'Rnw',
-          ].indexOf(extension) > -1
-        ) {
-          resources.push({
-            path: entity,
-            content: fs
-              .readFileSync(`${baseDirectory}/${directory}/${entity}`)
-              .toString(),
-          })
-        } else if (
-          ['eps', 'ttf', 'png', 'jpg', 'pdf', 'jpeg'].indexOf(extension) > -1
-        ) {
-          resources.push({
-            path: entity,
-            url: `http://filestore/${directory}/${entity}`,
-            modified: stat.mtime,
+function wordcount(projectId, file) {
+  const image = undefined
+  return wordcountWithImage(projectId, file, image)
+}
+
+async function wordcountWithImage(projectId, file, image) {
+  const url = new URL(`${host}/project/${projectId}/wordcount`)
+  if (image) {
+    url.searchParams.append('image', image)
+  }
+  url.searchParams.append('file', file)
+  return await fetchJson(url)
+}
+
+async function compileDirectory(projectId, baseDirectory, directory) {
+  const resources = []
+  let entities = fs.readdirSync(`${baseDirectory}/${directory}`)
+  let rootResourcePath = 'main.tex'
+  while (entities.length > 0) {
+    const entity = entities.pop()
+    const stat = fs.statSync(`${baseDirectory}/${directory}/${entity}`)
+    if (stat.isDirectory()) {
+      entities = entities.concat(
+        fs
+          .readdirSync(`${baseDirectory}/${directory}/${entity}`)
+          .map(subEntity => {
+            if (subEntity === 'main.tex') {
+              rootResourcePath = `${entity}/${subEntity}`
+            }
+            return `${entity}/${subEntity}`
           })
-        }
+      )
+    } else if (stat.isFile() && entity !== 'output.pdf') {
+      const extension = entity.split('.').pop()
+      if (
+        [
+          'tex',
+          'bib',
+          'cls',
+          'sty',
+          'pdf_tex',
+          'Rtex',
+          'ist',
+          'md',
+          'Rmd',
+          'Rnw',
+        ].indexOf(extension) > -1
+      ) {
+        resources.push({
+          path: entity,
+          content: fs
+            .readFileSync(`${baseDirectory}/${directory}/${entity}`)
+            .toString(),
+        })
+      } else if (
+        ['eps', 'ttf', 'png', 'jpg', 'pdf', 'jpeg'].indexOf(extension) > -1
+      ) {
+        resources.push({
+          path: entity,
+          url: `http://filestore/${directory}/${entity}`,
+          modified: stat.mtime,
+        })
       }
     }
+  }
 
-    return fs.readFile(
-      `${baseDirectory}/${directory}/options.json`,
-      (error, body) => {
-        const req = {
-          resources,
-          rootResourcePath,
-        }
+  const req = {
+    resources,
+    rootResourcePath,
+  }
 
-        if (error == null) {
-          body = JSON.parse(body)
-          req.options = body
-        }
-
-        return this.compile(projectId, req, callback)
-      }
+  try {
+    const options = await fsPromises.readFile(
+      `${baseDirectory}/${directory}/options.json`
     )
-  },
+    req.options = JSON.parse(options)
+  } catch (error) {
+    // noop
+  }
 
-  wordcount(projectId, file, callback) {
-    const image = undefined
-    Client.wordcountWithImage(projectId, file, image, callback)
-  },
+  return await compile(projectId, req)
+}
 
-  wordcountWithImage(projectId, file, image, callback) {
-    if (callback == null) {
-      callback = function () {}
-    }
-    return request.get(
-      {
-        url: `${this.host}/project/${projectId}/wordcount`,
-        qs: {
-          image,
-          file,
-        },
-      },
-      (error, response, body) => {
-        if (error != null) {
-          return callback(error)
-        }
-        if (response.statusCode !== 200) {
-          return callback(new Error(`statusCode=${response.statusCode}`), body)
-        }
-        return callback(null, JSON.parse(body))
-      }
-    )
-  },
+module.exports = {
+  randomId,
+  compile,
+  stopCompile,
+  clearCache,
+  getOutputFile,
+  runFakeFilestoreService,
+  startFakeFilestoreApp,
+  syncFromCode,
+  syncFromCodeWithImage,
+  syncFromPdf,
+  syncFromPdfWithImage,
+  compileDirectory,
+  wordcount,
+  wordcountWithImage,
 }

+ 26 - 45
services/clsi/test/acceptance/js/helpers/ClsiApp.js

@@ -1,50 +1,31 @@
-// 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
- * DS103: Rewrite code to no longer use __guard__
- * DS205: Consider reworking code to avoid use of IIFEs
- * DS207: Consider shorter variations of null checks
- * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
- */
 const app = require('../../../../app')
 const Settings = require('@overleaf/settings')
 
-module.exports = {
-  running: false,
-  initing: false,
-  callbacks: [],
-  ensureRunning(callback) {
-    if (callback == null) {
-      callback = function () {}
-    }
-    if (this.running) {
-      return callback()
-    } else if (this.initing) {
-      return this.callbacks.push(callback)
-    } else {
-      this.initing = true
-      this.callbacks.push(callback)
-      return app.listen(
-        Settings.internal.clsi.port,
-        Settings.internal.clsi.host,
-        error => {
-          if (error != null) {
-            throw error
-          }
-          this.running = true
-
-          return (() => {
-            const result = []
-            for (callback of Array.from(this.callbacks)) {
-              result.push(callback())
-            }
-            return result
-          })()
+function startApp() {
+  return new Promise((resolve, reject) => {
+    app.listen(
+      Settings.internal.clsi.port,
+      Settings.internal.clsi.host,
+      error => {
+        if (error) {
+          reject(error)
+        } else {
+          resolve()
         }
-      )
-    }
-  },
+      }
+    )
+  })
+}
+
+let appStartedPromise
+
+async function ensureRunning() {
+  if (!appStartedPromise) {
+    appStartedPromise = startApp()
+  }
+  await appStartedPromise
+}
+
+module.exports = {
+  ensureRunning,
 }