From e8bc186ca0a45a7679bd4bac5a52f5ef87b20201 Mon Sep 17 00:00:00 2001 From: Miguel Serrano Date: Wed, 1 Oct 2025 10:36:36 +0200 Subject: [PATCH] Merge pull request #28752 from overleaf/msm-clsi-acceptance-async-await [clsi] async/await migration in acceptance tests GitOrigin-RevId: d614fabb6d568dc5c955603fb923fb40b871a703 --- .../acceptance/js/AllowedImageNamesTests.js | 185 ++++---- .../acceptance/js/BrokenLatexFileTests.js | 50 +- .../test/acceptance/js/DeleteOldFilesTest.js | 51 +- .../acceptance/js/ExampleDocumentTests.js | 344 +++++--------- .../acceptance/js/SimpleLatexFileTests.js | 79 ++-- services/clsi/test/acceptance/js/Stats.js | 20 +- .../clsi/test/acceptance/js/StopCompile.js | 45 +- .../clsi/test/acceptance/js/SynctexTests.js | 174 +++---- .../clsi/test/acceptance/js/TimeoutTests.js | 35 +- .../test/acceptance/js/UrlCachingTests.js | 331 ++++--------- .../clsi/test/acceptance/js/WordcountTests.js | 65 +-- .../clsi/test/acceptance/js/helpers/Client.js | 441 ++++++++---------- .../test/acceptance/js/helpers/ClsiApp.js | 71 ++- 13 files changed, 692 insertions(+), 1199 deletions(-) diff --git a/services/clsi/test/acceptance/js/AllowedImageNamesTests.js b/services/clsi/test/acceptance/js/AllowedImageNamesTests.js index 9cd7a65930..62d4dc8222 100644 --- a/services/clsi/test/acceptance/js/AllowedImageNamesTests.js +++ b/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 }) }) }) diff --git a/services/clsi/test/acceptance/js/BrokenLatexFileTests.js b/services/clsi/test/acceptance/js/BrokenLatexFileTests.js index 46d07da092..738a13a81f 100644 --- a/services/clsi/test/acceptance/js/BrokenLatexFileTests.js +++ b/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 () { diff --git a/services/clsi/test/acceptance/js/DeleteOldFilesTest.js b/services/clsi/test/acceptance/js/DeleteOldFilesTest.js index 09eea1a948..e5e230e49f 100644 --- a/services/clsi/test/acceptance/js/DeleteOldFilesTest.js +++ b/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') }) }) }) diff --git a/services/clsi/test/acceptance/js/ExampleDocumentTests.js b/services/clsi/test/acceptance/js/ExampleDocumentTests.js index b463584501..dc2f0fe669 100644 --- a/services/clsi/test/acceptance/js/ExampleDocumentTests.js +++ b/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 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() - } 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) +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) } }) - } - return compareNext(0, callback) + }) } -const comparePdf = function (projectId, exampleDir, callback) { - if (callback == null) { - callback = function () {} +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 { + resolve(false) + } + }) + proc.on('error', error => reject(error)) + }) +} + +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 -} diff --git a/services/clsi/test/acceptance/js/SimpleLatexFileTests.js b/services/clsi/test/acceptance/js/SimpleLatexFileTests.js index e2256b2f7f..b8e2724197 100644 --- a/services/clsi/test/acceptance/js/SimpleLatexFileTests.js +++ b/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) }) }) diff --git a/services/clsi/test/acceptance/js/Stats.js b/services/clsi/test/acceptance/js/Stats.js index 4f071abe5f..98e6a4cacd 100644 --- a/services/clsi/test/acceptance/js/Stats.js +++ b/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 --') }) diff --git a/services/clsi/test/acceptance/js/StopCompile.js b/services/clsi/test/acceptance/js/StopCompile.js index 103a70f37d..33a5160bff 100644 --- a/services/clsi/test/acceptance/js/StopCompile.js +++ b/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') }) diff --git a/services/clsi/test/acceptance/js/SynctexTests.js b/services/clsi/test/acceptance/js/SynctexTests.js index 049f260259..13fc213c02 100644 --- a/services/clsi/test/acceptance/js/SynctexTests.js +++ b/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') }) }) }) diff --git a/services/clsi/test/acceptance/js/TimeoutTests.js b/services/clsi/test/acceptance/js/TimeoutTests.js index e9175d223c..70926e3aee 100644 --- a/services/clsi/test/acceptance/js/TimeoutTests.js +++ b/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') }) }) diff --git a/services/clsi/test/acceptance/js/UrlCachingTests.js b/services/clsi/test/acceptance/js/UrlCachingTests.js index 9fc9608204..b698af5488 100644 --- a/services/clsi/test/acceptance/js/UrlCachingTests.js +++ b/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) }) }) }) diff --git a/services/clsi/test/acceptance/js/WordcountTests.js b/services/clsi/test/acceptance/js/WordcountTests.js index 626b5d7034..50e2506d6a 100644 --- a/services/clsi/test/acceptance/js/WordcountTests.js +++ b/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: '', + }, }) }) }) diff --git a/services/clsi/test/acceptance/js/helpers/Client.js b/services/clsi/test/acceptance/js/helpers/Client.js index 49bf7390c6..6b8012e983 100644 --- a/services/clsi/test/acceptance/js/helpers/Client.js +++ b/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) - }, - - 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 - ) - }, - - stopCompile(projectId, callback) { - if (callback == null) { - callback = function () {} - } - return request.post( - { url: `${this.host}/project/${projectId}/compile/stop` }, - callback - ) - }, - - clearCache(projectId, callback) { - if (callback == null) { - callback = function () {} - } - return request.del(`${this.host}/project/${projectId}`, callback) - }, - - getOutputFile(response, type) { - for (const file of Array.from(response.compile.outputFiles)) { - if (file.type === type && file.url.match(`output.${type}`)) { - return file - } - } - return null - }, - - 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) - }) - }, - - syncFromCode(projectId, file, line, column, callback) { - Client.syncFromCodeWithImage(projectId, file, line, column, '', callback) - }, - - 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) - } - ) - }, - - syncFromPdf(projectId, page, h, v, callback) { - Client.syncFromPdfWithImage(projectId, page, h, v, '', callback) - }, - - 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) - } - ) - }, - - 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, - }) - } - } - } - - return fs.readFile( - `${baseDirectory}/${directory}/options.json`, - (error, body) => { - const req = { - resources, - rootResourcePath, - } - - if (error == null) { - body = JSON.parse(body) - req.options = body - } - - return this.compile(projectId, req, callback) - } - ) - }, - - wordcount(projectId, file, callback) { - const image = undefined - Client.wordcountWithImage(projectId, file, image, callback) - }, - - 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)) - } - ) - }, +function randomId() { + return Math.random().toString(16).slice(2) +} + +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, + }, + }) +} + +async function stopCompile(projectId) { + return await fetchNothing(`${host}/project/${projectId}/compile/stop`, { + method: 'POST', + }) +} + +async function clearCache(projectId) { + await fetchNothing(`${host}/project/${projectId}`, { + method: 'DELETE', + }) +} + +function getOutputFile(response, type) { + for (const file of response.compile.outputFiles) { + if (file.type === type && file.url.match(`output.${type}`)) { + return file + } + } + return null +} + +function runFakeFilestoreService(directory) { + const app = express() + app.use(express.static(directory)) + this.startFakeFilestoreApp(app) +} + +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) + }) +} + +function syncFromCode(projectId, file, line, column) { + return syncFromCodeWithImage(projectId, file, line, column, '') +} + +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) +} + +function syncFromPdf(projectId, page, h, v) { + return syncFromPdfWithImage(projectId, page, h, v, '') +} + +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) +} + +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, + }) + } + } + } + + const req = { + resources, + rootResourcePath, + } + + try { + const options = await fsPromises.readFile( + `${baseDirectory}/${directory}/options.json` + ) + req.options = JSON.parse(options) + } catch (error) { + // noop + } + + return await compile(projectId, req) +} + +module.exports = { + randomId, + compile, + stopCompile, + clearCache, + getOutputFile, + runFakeFilestoreService, + startFakeFilestoreApp, + syncFromCode, + syncFromCodeWithImage, + syncFromPdf, + syncFromPdfWithImage, + compileDirectory, + wordcount, + wordcountWithImage, } diff --git a/services/clsi/test/acceptance/js/helpers/ClsiApp.js b/services/clsi/test/acceptance/js/helpers/ClsiApp.js index 38308e9129..5d5bc57233 100644 --- a/services/clsi/test/acceptance/js/helpers/ClsiApp.js +++ b/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, }