mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-05-23 17:19:37 +02:00
Merge pull request #28752 from overleaf/msm-clsi-acceptance-async-await
[clsi] async/await migration in acceptance tests GitOrigin-RevId: d614fabb6d568dc5c955603fb923fb40b871a703
This commit is contained in:
@@ -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
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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 () {
|
||||
|
||||
@@ -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')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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 --')
|
||||
})
|
||||
|
||||
@@ -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')
|
||||
})
|
||||
|
||||
@@ -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')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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: '',
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user