prettier: convert test/unit decaffeinated files to Prettier format

This commit is contained in:
mserranom 2020-02-19 12:15:37 +01:00
parent 89360bfe77
commit a62d8186e9
18 changed files with 4233 additions and 3427 deletions

View file

@ -9,267 +9,299 @@
* DS102: Remove unnecessary code created because of implicit returns * DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/ */
const SandboxedModule = require('sandboxed-module'); const SandboxedModule = require('sandboxed-module')
const sinon = require('sinon'); const sinon = require('sinon')
require('chai').should(); require('chai').should()
const modulePath = require('path').join(__dirname, '../../../app/js/CompileController'); const modulePath = require('path').join(
const tk = require("timekeeper"); __dirname,
'../../../app/js/CompileController'
)
const tk = require('timekeeper')
describe("CompileController", function() { describe('CompileController', function() {
beforeEach(function() { beforeEach(function() {
this.CompileController = SandboxedModule.require(modulePath, { requires: { this.CompileController = SandboxedModule.require(modulePath, {
"./CompileManager": (this.CompileManager = {}), requires: {
"./RequestParser": (this.RequestParser = {}), './CompileManager': (this.CompileManager = {}),
"settings-sharelatex": (this.Settings = { './RequestParser': (this.RequestParser = {}),
apis: { 'settings-sharelatex': (this.Settings = {
clsi: { apis: {
url: "http://clsi.example.com" clsi: {
} url: 'http://clsi.example.com'
} }
}), }
"./ProjectPersistenceManager": (this.ProjectPersistenceManager = {}), }),
"logger-sharelatex": (this.logger = { log: sinon.stub(), error: sinon.stub(), err:sinon.stub(), warn: sinon.stub()}) './ProjectPersistenceManager': (this.ProjectPersistenceManager = {}),
} 'logger-sharelatex': (this.logger = {
}); log: sinon.stub(),
this.Settings.externalUrl = "http://www.example.com"; error: sinon.stub(),
this.req = {}; err: sinon.stub(),
this.res = {}; warn: sinon.stub()
return this.next = sinon.stub(); })
}); }
})
this.Settings.externalUrl = 'http://www.example.com'
this.req = {}
this.res = {}
return (this.next = sinon.stub())
})
describe("compile", function() { describe('compile', function() {
beforeEach(function() { beforeEach(function() {
this.req.body = { this.req.body = {
compile: "mock-body" compile: 'mock-body'
}; }
this.req.params = this.req.params = { project_id: (this.project_id = 'project-id-123') }
{project_id: (this.project_id = "project-id-123")}; this.request = {
this.request = { compile: 'mock-parsed-request'
compile: "mock-parsed-request" }
}; this.request_with_project_id = {
this.request_with_project_id = { compile: this.request.compile,
compile: this.request.compile, project_id: this.project_id
project_id: this.project_id }
}; this.output_files = [
this.output_files = [{ {
path: "output.pdf", path: 'output.pdf',
type: "pdf", type: 'pdf',
build: 1234 build: 1234
}, { },
path: "output.log", {
type: "log", path: 'output.log',
build: 1234 type: 'log',
}]; build: 1234
this.RequestParser.parse = sinon.stub().callsArgWith(1, null, this.request); }
this.ProjectPersistenceManager.markProjectAsJustAccessed = sinon.stub().callsArg(1); ]
this.res.status = sinon.stub().returnsThis(); this.RequestParser.parse = sinon
return this.res.send = sinon.stub(); .stub()
}); .callsArgWith(1, null, this.request)
this.ProjectPersistenceManager.markProjectAsJustAccessed = sinon
.stub()
.callsArg(1)
this.res.status = sinon.stub().returnsThis()
return (this.res.send = sinon.stub())
})
describe("successfully", function() { describe('successfully', function() {
beforeEach(function() { beforeEach(function() {
this.CompileManager.doCompileWithLock = sinon.stub().callsArgWith(1, null, this.output_files); this.CompileManager.doCompileWithLock = sinon
return this.CompileController.compile(this.req, this.res); .stub()
}); .callsArgWith(1, null, this.output_files)
return this.CompileController.compile(this.req, this.res)
})
it("should parse the request", function() { it('should parse the request', function() {
return this.RequestParser.parse return this.RequestParser.parse
.calledWith(this.req.body) .calledWith(this.req.body)
.should.equal(true); .should.equal(true)
}); })
it("should run the compile for the specified project", function() { it('should run the compile for the specified project', function() {
return this.CompileManager.doCompileWithLock return this.CompileManager.doCompileWithLock
.calledWith(this.request_with_project_id) .calledWith(this.request_with_project_id)
.should.equal(true); .should.equal(true)
}); })
it("should mark the project as accessed", function() { it('should mark the project as accessed', function() {
return this.ProjectPersistenceManager.markProjectAsJustAccessed return this.ProjectPersistenceManager.markProjectAsJustAccessed
.calledWith(this.project_id) .calledWith(this.project_id)
.should.equal(true); .should.equal(true)
}); })
return it("should return the JSON response", function() { return it('should return the JSON response', function() {
this.res.status.calledWith(200).should.equal(true); this.res.status.calledWith(200).should.equal(true)
return this.res.send return this.res.send
.calledWith({ .calledWith({
compile: { compile: {
status: "success", status: 'success',
error: null, error: null,
outputFiles: this.output_files.map(file => { outputFiles: this.output_files.map(file => {
return { return {
url: `${this.Settings.apis.clsi.url}/project/${this.project_id}/build/${file.build}/output/${file.path}`, url: `${this.Settings.apis.clsi.url}/project/${this.project_id}/build/${file.build}/output/${file.path}`,
path: file.path, path: file.path,
type: file.type, type: file.type,
build: file.build build: file.build
}; }
}) })
} }
}) })
.should.equal(true); .should.equal(true)
}); })
}); })
describe("with an error", function() {
beforeEach(function() {
this.CompileManager.doCompileWithLock = sinon.stub().callsArgWith(1, new Error(this.message = "error message"), null);
return this.CompileController.compile(this.req, this.res);
});
return it("should return the JSON response with the error", function() {
this.res.status.calledWith(500).should.equal(true);
return this.res.send
.calledWith({
compile: {
status: "error",
error: this.message,
outputFiles: []
}
})
.should.equal(true);
});
});
describe("when the request times out", function() { describe('with an error', function() {
beforeEach(function() { beforeEach(function() {
this.error = new Error(this.message = "container timed out"); this.CompileManager.doCompileWithLock = sinon
this.error.timedout = true; .stub()
this.CompileManager.doCompileWithLock = sinon.stub().callsArgWith(1, this.error, null); .callsArgWith(1, new Error((this.message = 'error message')), null)
return this.CompileController.compile(this.req, this.res); return this.CompileController.compile(this.req, this.res)
}); })
return it("should return the JSON response with the timeout status", function() {
this.res.status.calledWith(200).should.equal(true);
return this.res.send
.calledWith({
compile: {
status: "timedout",
error: this.message,
outputFiles: []
}
})
.should.equal(true);
});
});
return describe("when the request returns no output files", function() { return it('should return the JSON response with the error', function() {
beforeEach(function() { this.res.status.calledWith(500).should.equal(true)
this.CompileManager.doCompileWithLock = sinon.stub().callsArgWith(1, null, []); return this.res.send
return this.CompileController.compile(this.req, this.res); .calledWith({
}); compile: {
status: 'error',
return it("should return the JSON response with the failure status", function() { error: this.message,
this.res.status.calledWith(200).should.equal(true); outputFiles: []
return this.res.send }
.calledWith({ })
compile: { .should.equal(true)
error: null, })
status: "failure", })
outputFiles: []
}
})
.should.equal(true);
});
});
});
describe("syncFromCode", function() { describe('when the request times out', function() {
beforeEach(function() { beforeEach(function() {
this.file = "main.tex"; this.error = new Error((this.message = 'container timed out'))
this.line = 42; this.error.timedout = true
this.column = 5; this.CompileManager.doCompileWithLock = sinon
this.project_id = "mock-project-id"; .stub()
this.req.params = .callsArgWith(1, this.error, null)
{project_id: this.project_id}; return this.CompileController.compile(this.req, this.res)
this.req.query = { })
file: this.file,
line: this.line.toString(),
column: this.column.toString()
};
this.res.json = sinon.stub();
this.CompileManager.syncFromCode = sinon.stub().callsArgWith(5, null, (this.pdfPositions = ["mock-positions"])); return it('should return the JSON response with the timeout status', function() {
return this.CompileController.syncFromCode(this.req, this.res, this.next); this.res.status.calledWith(200).should.equal(true)
}); return this.res.send
.calledWith({
compile: {
status: 'timedout',
error: this.message,
outputFiles: []
}
})
.should.equal(true)
})
})
it("should find the corresponding location in the PDF", function() { return describe('when the request returns no output files', function() {
return this.CompileManager.syncFromCode beforeEach(function() {
.calledWith(this.project_id, undefined, this.file, this.line, this.column) this.CompileManager.doCompileWithLock = sinon
.should.equal(true); .stub()
}); .callsArgWith(1, null, [])
return this.CompileController.compile(this.req, this.res)
})
return it("should return the positions", function() { return it('should return the JSON response with the failure status', function() {
return this.res.json this.res.status.calledWith(200).should.equal(true)
.calledWith({ return this.res.send
pdf: this.pdfPositions .calledWith({
}) compile: {
.should.equal(true); error: null,
}); status: 'failure',
}); outputFiles: []
}
})
.should.equal(true)
})
})
})
describe("syncFromPdf", function() { describe('syncFromCode', function() {
beforeEach(function() { beforeEach(function() {
this.page = 5; this.file = 'main.tex'
this.h = 100.23; this.line = 42
this.v = 45.67; this.column = 5
this.project_id = "mock-project-id"; this.project_id = 'mock-project-id'
this.req.params = this.req.params = { project_id: this.project_id }
{project_id: this.project_id}; this.req.query = {
this.req.query = { file: this.file,
page: this.page.toString(), line: this.line.toString(),
h: this.h.toString(), column: this.column.toString()
v: this.v.toString() }
}; this.res.json = sinon.stub()
this.res.json = sinon.stub();
this.CompileManager.syncFromPdf = sinon.stub().callsArgWith(5, null, (this.codePositions = ["mock-positions"])); this.CompileManager.syncFromCode = sinon
return this.CompileController.syncFromPdf(this.req, this.res, this.next); .stub()
}); .callsArgWith(5, null, (this.pdfPositions = ['mock-positions']))
return this.CompileController.syncFromCode(this.req, this.res, this.next)
})
it("should find the corresponding location in the code", function() { it('should find the corresponding location in the PDF', function() {
return this.CompileManager.syncFromPdf return this.CompileManager.syncFromCode
.calledWith(this.project_id, undefined, this.page, this.h, this.v) .calledWith(
.should.equal(true); this.project_id,
}); undefined,
this.file,
this.line,
this.column
)
.should.equal(true)
})
return it("should return the positions", function() { return it('should return the positions', function() {
return this.res.json return this.res.json
.calledWith({ .calledWith({
code: this.codePositions pdf: this.pdfPositions
}) })
.should.equal(true); .should.equal(true)
}); })
}); })
return describe("wordcount", function() { describe('syncFromPdf', function() {
beforeEach(function() { beforeEach(function() {
this.file = "main.tex"; this.page = 5
this.project_id = "mock-project-id"; this.h = 100.23
this.req.params = this.v = 45.67
{project_id: this.project_id}; this.project_id = 'mock-project-id'
this.req.query = { this.req.params = { project_id: this.project_id }
file: this.file, this.req.query = {
image: (this.image = "example.com/image") page: this.page.toString(),
}; h: this.h.toString(),
this.res.json = sinon.stub(); v: this.v.toString()
}
this.res.json = sinon.stub()
this.CompileManager.wordcount = sinon.stub().callsArgWith(4, null, (this.texcount = ["mock-texcount"])); this.CompileManager.syncFromPdf = sinon
return this.CompileController.wordcount(this.req, this.res, this.next); .stub()
}); .callsArgWith(5, null, (this.codePositions = ['mock-positions']))
return this.CompileController.syncFromPdf(this.req, this.res, this.next)
})
it("should return the word count of a file", function() { it('should find the corresponding location in the code', function() {
return this.CompileManager.wordcount return this.CompileManager.syncFromPdf
.calledWith(this.project_id, undefined, this.file, this.image) .calledWith(this.project_id, undefined, this.page, this.h, this.v)
.should.equal(true); .should.equal(true)
}); })
return it("should return the texcount info", function() { return it('should return the positions', function() {
return this.res.json return this.res.json
.calledWith({ .calledWith({
texcount: this.texcount code: this.codePositions
}) })
.should.equal(true); .should.equal(true)
}); })
}); })
});
return describe('wordcount', function() {
beforeEach(function() {
this.file = 'main.tex'
this.project_id = 'mock-project-id'
this.req.params = { project_id: this.project_id }
this.req.query = {
file: this.file,
image: (this.image = 'example.com/image')
}
this.res.json = sinon.stub()
this.CompileManager.wordcount = sinon
.stub()
.callsArgWith(4, null, (this.texcount = ['mock-texcount']))
return this.CompileController.wordcount(this.req, this.res, this.next)
})
it('should return the word count of a file', function() {
return this.CompileManager.wordcount
.calledWith(this.project_id, undefined, this.file, this.image)
.should.equal(true)
})
return it('should return the texcount info', function() {
return this.res.json
.calledWith({
texcount: this.texcount
})
.should.equal(true)
})
})
})

View file

@ -13,423 +13,539 @@
* DS102: Remove unnecessary code created because of implicit returns * DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/ */
const SandboxedModule = require('sandboxed-module'); const SandboxedModule = require('sandboxed-module')
const sinon = require('sinon'); const sinon = require('sinon')
require('chai').should(); require('chai').should()
const modulePath = require('path').join(__dirname, '../../../app/js/CompileManager'); const modulePath = require('path').join(
const tk = require("timekeeper"); __dirname,
const { EventEmitter } = require("events"); '../../../app/js/CompileManager'
const Path = require("path"); )
const tk = require('timekeeper')
const { EventEmitter } = require('events')
const Path = require('path')
describe("CompileManager", function() { describe('CompileManager', function() {
beforeEach(function() { beforeEach(function() {
this.CompileManager = SandboxedModule.require(modulePath, { requires: { this.CompileManager = SandboxedModule.require(modulePath, {
"./LatexRunner": (this.LatexRunner = {}), requires: {
"./ResourceWriter": (this.ResourceWriter = {}), './LatexRunner': (this.LatexRunner = {}),
"./OutputFileFinder": (this.OutputFileFinder = {}), './ResourceWriter': (this.ResourceWriter = {}),
"./OutputCacheManager": (this.OutputCacheManager = {}), './OutputFileFinder': (this.OutputFileFinder = {}),
"settings-sharelatex": (this.Settings = { './OutputCacheManager': (this.OutputCacheManager = {}),
path: { 'settings-sharelatex': (this.Settings = {
compilesDir: "/compiles/dir" path: {
}, compilesDir: '/compiles/dir'
synctexBaseDir() { return "/compile"; }, },
clsi: { synctexBaseDir() {
docker: { return '/compile'
image: "SOMEIMAGE" },
} clsi: {
} docker: {
}), image: 'SOMEIMAGE'
}
}
}),
"logger-sharelatex": (this.logger = { log: sinon.stub() , info() {}}), 'logger-sharelatex': (this.logger = { log: sinon.stub(), info() {} }),
"child_process": (this.child_process = {}), child_process: (this.child_process = {}),
"./CommandRunner": (this.CommandRunner = {}), './CommandRunner': (this.CommandRunner = {}),
"./DraftModeManager": (this.DraftModeManager = {}), './DraftModeManager': (this.DraftModeManager = {}),
"./TikzManager": (this.TikzManager = {}), './TikzManager': (this.TikzManager = {}),
"./LockManager": (this.LockManager = {}), './LockManager': (this.LockManager = {}),
"fs": (this.fs = {}), fs: (this.fs = {}),
"fs-extra": (this.fse = { ensureDir: sinon.stub().callsArg(1) }) 'fs-extra': (this.fse = { ensureDir: sinon.stub().callsArg(1) })
} }
}); })
this.callback = sinon.stub(); this.callback = sinon.stub()
this.project_id = "project-id-123"; this.project_id = 'project-id-123'
return this.user_id = "1234"; return (this.user_id = '1234')
}); })
describe("doCompileWithLock", function() { describe('doCompileWithLock', function() {
beforeEach(function() { beforeEach(function() {
this.request = { this.request = {
resources: (this.resources = "mock-resources"), resources: (this.resources = 'mock-resources'),
project_id: this.project_id, project_id: this.project_id,
user_id: this.user_id user_id: this.user_id
}; }
this.output_files = ["foo", "bar"]; this.output_files = ['foo', 'bar']
this.Settings.compileDir = "compiles"; this.Settings.compileDir = 'compiles'
this.compileDir = `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}`; this.compileDir = `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}`
this.CompileManager.doCompile = sinon.stub().callsArgWith(1, null, this.output_files); this.CompileManager.doCompile = sinon
return this.LockManager.runWithLock = (lockFile, runner, callback) => .stub()
runner((err, ...result) => callback(err, ...Array.from(result))) .callsArgWith(1, null, this.output_files)
; return (this.LockManager.runWithLock = (lockFile, runner, callback) =>
}); runner((err, ...result) => callback(err, ...Array.from(result))))
})
describe("when the project is not locked", function() { describe('when the project is not locked', function() {
beforeEach(function() { beforeEach(function() {
return this.CompileManager.doCompileWithLock(this.request, this.callback); return this.CompileManager.doCompileWithLock(
}); this.request,
this.callback
)
})
it("should ensure that the compile directory exists", function() { it('should ensure that the compile directory exists', function() {
return this.fse.ensureDir.calledWith(this.compileDir) return this.fse.ensureDir.calledWith(this.compileDir).should.equal(true)
.should.equal(true); })
});
it("should call doCompile with the request", function() { it('should call doCompile with the request', function() {
return this.CompileManager.doCompile return this.CompileManager.doCompile
.calledWith(this.request) .calledWith(this.request)
.should.equal(true); .should.equal(true)
}); })
return it("should call the callback with the output files", function() { return it('should call the callback with the output files', function() {
return this.callback.calledWithExactly(null, this.output_files) return this.callback
.should.equal(true); .calledWithExactly(null, this.output_files)
}); .should.equal(true)
}); })
})
return describe("when the project is locked", function() { return describe('when the project is locked', function() {
beforeEach(function() { beforeEach(function() {
this.error = new Error("locked"); this.error = new Error('locked')
this.LockManager.runWithLock = (lockFile, runner, callback) => { this.LockManager.runWithLock = (lockFile, runner, callback) => {
return callback(this.error); return callback(this.error)
}; }
return this.CompileManager.doCompileWithLock(this.request, this.callback); return this.CompileManager.doCompileWithLock(
}); this.request,
this.callback
)
})
it("should ensure that the compile directory exists", function() { it('should ensure that the compile directory exists', function() {
return this.fse.ensureDir.calledWith(this.compileDir) return this.fse.ensureDir.calledWith(this.compileDir).should.equal(true)
.should.equal(true); })
});
it("should not call doCompile with the request", function() { it('should not call doCompile with the request', function() {
return this.CompileManager.doCompile return this.CompileManager.doCompile.called.should.equal(false)
.called.should.equal(false); })
});
return it("should call the callback with the error", function() { return it('should call the callback with the error', function() {
return this.callback.calledWithExactly(this.error) return this.callback.calledWithExactly(this.error).should.equal(true)
.should.equal(true); })
}); })
}); })
});
describe("doCompile", function() { describe('doCompile', function() {
beforeEach(function() { beforeEach(function() {
this.output_files = [{ this.output_files = [
path: "output.log", {
type: "log" path: 'output.log',
}, { type: 'log'
path: "output.pdf", },
type: "pdf" {
}]; path: 'output.pdf',
this.build_files = [{ type: 'pdf'
path: "output.log", }
type: "log", ]
build: 1234 this.build_files = [
}, { {
path: "output.pdf", path: 'output.log',
type: "pdf", type: 'log',
build: 1234 build: 1234
}]; },
this.request = { {
resources: (this.resources = "mock-resources"), path: 'output.pdf',
rootResourcePath: (this.rootResourcePath = "main.tex"), type: 'pdf',
project_id: this.project_id, build: 1234
user_id: this.user_id, }
compiler: (this.compiler = "pdflatex"), ]
timeout: (this.timeout = 42000), this.request = {
imageName: (this.image = "example.com/image"), resources: (this.resources = 'mock-resources'),
flags: (this.flags = ["-file-line-error"]) rootResourcePath: (this.rootResourcePath = 'main.tex'),
}; project_id: this.project_id,
this.env = {}; user_id: this.user_id,
this.Settings.compileDir = "compiles"; compiler: (this.compiler = 'pdflatex'),
this.compileDir = `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}`; timeout: (this.timeout = 42000),
this.ResourceWriter.syncResourcesToDisk = sinon.stub().callsArgWith(2, null, this.resources); imageName: (this.image = 'example.com/image'),
this.LatexRunner.runLatex = sinon.stub().callsArg(2); flags: (this.flags = ['-file-line-error'])
this.OutputFileFinder.findOutputFiles = sinon.stub().callsArgWith(2, null, this.output_files); }
this.OutputCacheManager.saveOutputFiles = sinon.stub().callsArgWith(2, null, this.build_files); this.env = {}
this.DraftModeManager.injectDraftMode = sinon.stub().callsArg(1); this.Settings.compileDir = 'compiles'
return this.TikzManager.checkMainFile = sinon.stub().callsArg(3, false); this.compileDir = `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}`
}); this.ResourceWriter.syncResourcesToDisk = sinon
.stub()
.callsArgWith(2, null, this.resources)
this.LatexRunner.runLatex = sinon.stub().callsArg(2)
this.OutputFileFinder.findOutputFiles = sinon
.stub()
.callsArgWith(2, null, this.output_files)
this.OutputCacheManager.saveOutputFiles = sinon
.stub()
.callsArgWith(2, null, this.build_files)
this.DraftModeManager.injectDraftMode = sinon.stub().callsArg(1)
return (this.TikzManager.checkMainFile = sinon.stub().callsArg(3, false))
})
describe("normally", function() { describe('normally', function() {
beforeEach(function() { beforeEach(function() {
return this.CompileManager.doCompile(this.request, this.callback); return this.CompileManager.doCompile(this.request, this.callback)
}); })
it("should write the resources to disk", function() { it('should write the resources to disk', function() {
return this.ResourceWriter.syncResourcesToDisk return this.ResourceWriter.syncResourcesToDisk
.calledWith(this.request, this.compileDir) .calledWith(this.request, this.compileDir)
.should.equal(true); .should.equal(true)
}); })
it("should run LaTeX", function() { it('should run LaTeX', function() {
return this.LatexRunner.runLatex return this.LatexRunner.runLatex
.calledWith(`${this.project_id}-${this.user_id}`, { .calledWith(`${this.project_id}-${this.user_id}`, {
directory: this.compileDir, directory: this.compileDir,
mainFile: this.rootResourcePath, mainFile: this.rootResourcePath,
compiler: this.compiler, compiler: this.compiler,
timeout: this.timeout, timeout: this.timeout,
image: this.image, image: this.image,
flags: this.flags, flags: this.flags,
environment: this.env environment: this.env
}) })
.should.equal(true); .should.equal(true)
}); })
it("should find the output files", function() { it('should find the output files', function() {
return this.OutputFileFinder.findOutputFiles return this.OutputFileFinder.findOutputFiles
.calledWith(this.resources, this.compileDir) .calledWith(this.resources, this.compileDir)
.should.equal(true); .should.equal(true)
}); })
it("should return the output files", function() { it('should return the output files', function() {
return this.callback.calledWith(null, this.build_files).should.equal(true); return this.callback
}); .calledWith(null, this.build_files)
.should.equal(true)
})
return it("should not inject draft mode by default", function() { return it('should not inject draft mode by default', function() {
return this.DraftModeManager.injectDraftMode.called.should.equal(false); return this.DraftModeManager.injectDraftMode.called.should.equal(false)
}); })
}); })
describe("with draft mode", function() { describe('with draft mode', function() {
beforeEach(function() { beforeEach(function() {
this.request.draft = true; this.request.draft = true
return this.CompileManager.doCompile(this.request, this.callback); return this.CompileManager.doCompile(this.request, this.callback)
}); })
return it("should inject the draft mode header", function() { return it('should inject the draft mode header', function() {
return this.DraftModeManager.injectDraftMode return this.DraftModeManager.injectDraftMode
.calledWith(this.compileDir + "/" + this.rootResourcePath) .calledWith(this.compileDir + '/' + this.rootResourcePath)
.should.equal(true); .should.equal(true)
}); })
}); })
describe("with a check option", function() { describe('with a check option', function() {
beforeEach(function() { beforeEach(function() {
this.request.check = "error"; this.request.check = 'error'
return this.CompileManager.doCompile(this.request, this.callback); return this.CompileManager.doCompile(this.request, this.callback)
}); })
return it("should run chktex", function() { return it('should run chktex', function() {
return this.LatexRunner.runLatex return this.LatexRunner.runLatex
.calledWith(`${this.project_id}-${this.user_id}`, { .calledWith(`${this.project_id}-${this.user_id}`, {
directory: this.compileDir, directory: this.compileDir,
mainFile: this.rootResourcePath, mainFile: this.rootResourcePath,
compiler: this.compiler, compiler: this.compiler,
timeout: this.timeout, timeout: this.timeout,
image: this.image, image: this.image,
flags: this.flags, flags: this.flags,
environment: {'CHKTEX_OPTIONS': '-nall -e9 -e10 -w15 -w16', 'CHKTEX_EXIT_ON_ERROR':1, 'CHKTEX_ULIMIT_OPTIONS': '-t 5 -v 64000'} environment: {
}) CHKTEX_OPTIONS: '-nall -e9 -e10 -w15 -w16',
.should.equal(true); CHKTEX_EXIT_ON_ERROR: 1,
}); CHKTEX_ULIMIT_OPTIONS: '-t 5 -v 64000'
}); }
})
.should.equal(true)
})
})
return describe("with a knitr file and check options", function() { return describe('with a knitr file and check options', function() {
beforeEach(function() { beforeEach(function() {
this.request.rootResourcePath = "main.Rtex"; this.request.rootResourcePath = 'main.Rtex'
this.request.check = "error"; this.request.check = 'error'
return this.CompileManager.doCompile(this.request, this.callback); return this.CompileManager.doCompile(this.request, this.callback)
}); })
return it("should not run chktex", function() { return it('should not run chktex', function() {
return this.LatexRunner.runLatex return this.LatexRunner.runLatex
.calledWith(`${this.project_id}-${this.user_id}`, { .calledWith(`${this.project_id}-${this.user_id}`, {
directory: this.compileDir, directory: this.compileDir,
mainFile: "main.Rtex", mainFile: 'main.Rtex',
compiler: this.compiler, compiler: this.compiler,
timeout: this.timeout, timeout: this.timeout,
image: this.image, image: this.image,
flags: this.flags, flags: this.flags,
environment: this.env environment: this.env
}) })
.should.equal(true); .should.equal(true)
}); })
}); })
}); })
describe("clearProject", function() { describe('clearProject', function() {
describe("succesfully", function() { describe('succesfully', function() {
beforeEach(function() { beforeEach(function() {
this.Settings.compileDir = "compiles"; this.Settings.compileDir = 'compiles'
this.fs.lstat = sinon.stub().callsArgWith(1, null,{isDirectory(){ return true; }}); this.fs.lstat = sinon.stub().callsArgWith(1, null, {
this.proc = new EventEmitter(); isDirectory() {
this.proc.stdout = new EventEmitter(); return true
this.proc.stderr = new EventEmitter(); }
this.child_process.spawn = sinon.stub().returns(this.proc); })
this.CompileManager.clearProject(this.project_id, this.user_id, this.callback); this.proc = new EventEmitter()
return this.proc.emit("close", 0); this.proc.stdout = new EventEmitter()
}); this.proc.stderr = new EventEmitter()
this.child_process.spawn = sinon.stub().returns(this.proc)
this.CompileManager.clearProject(
this.project_id,
this.user_id,
this.callback
)
return this.proc.emit('close', 0)
})
it("should remove the project directory", function() { it('should remove the project directory', function() {
return this.child_process.spawn return this.child_process.spawn
.calledWith("rm", ["-r", `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}`]) .calledWith('rm', [
.should.equal(true); '-r',
}); `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}`
])
.should.equal(true)
})
return it("should call the callback", function() { return it('should call the callback', function() {
return this.callback.called.should.equal(true); return this.callback.called.should.equal(true)
}); })
}); })
return describe("with a non-success status code", function() { return describe('with a non-success status code', function() {
beforeEach(function() { beforeEach(function() {
this.Settings.compileDir = "compiles"; this.Settings.compileDir = 'compiles'
this.fs.lstat = sinon.stub().callsArgWith(1, null,{isDirectory(){ return true; }}); this.fs.lstat = sinon.stub().callsArgWith(1, null, {
this.proc = new EventEmitter(); isDirectory() {
this.proc.stdout = new EventEmitter(); return true
this.proc.stderr = new EventEmitter(); }
this.child_process.spawn = sinon.stub().returns(this.proc); })
this.CompileManager.clearProject(this.project_id, this.user_id, this.callback); this.proc = new EventEmitter()
this.proc.stderr.emit("data", (this.error = "oops")); this.proc.stdout = new EventEmitter()
return this.proc.emit("close", 1); this.proc.stderr = new EventEmitter()
}); this.child_process.spawn = sinon.stub().returns(this.proc)
this.CompileManager.clearProject(
this.project_id,
this.user_id,
this.callback
)
this.proc.stderr.emit('data', (this.error = 'oops'))
return this.proc.emit('close', 1)
})
it("should remove the project directory", function() { it('should remove the project directory', function() {
return this.child_process.spawn return this.child_process.spawn
.calledWith("rm", ["-r", `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}`]) .calledWith('rm', [
.should.equal(true); '-r',
}); `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}`
])
.should.equal(true)
})
return it("should call the callback with an error from the stderr", function() { return it('should call the callback with an error from the stderr', function() {
this.callback this.callback.calledWith(new Error()).should.equal(true)
.calledWith(new Error())
.should.equal(true);
return this.callback.args[0][0].message.should.equal(`rm -r ${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id} failed: ${this.error}`); return this.callback.args[0][0].message.should.equal(
}); `rm -r ${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id} failed: ${this.error}`
}); )
}); })
})
})
describe("syncing", function() { describe('syncing', function() {
beforeEach(function() { beforeEach(function() {
this.page = 1; this.page = 1
this.h = 42.23; this.h = 42.23
this.v = 87.56; this.v = 87.56
this.width = 100.01; this.width = 100.01
this.height = 234.56; this.height = 234.56
this.line = 5; this.line = 5
this.column = 3; this.column = 3
this.file_name = "main.tex"; this.file_name = 'main.tex'
this.child_process.execFile = sinon.stub(); this.child_process.execFile = sinon.stub()
return this.Settings.path.synctexBaseDir = project_id => `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}`; return (this.Settings.path.synctexBaseDir = project_id =>
}); `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}`)
})
describe("syncFromCode", function() { describe('syncFromCode', function() {
beforeEach(function() { beforeEach(function() {
this.fs.stat = sinon.stub().callsArgWith(1, null,{isFile(){ return true; }}); this.fs.stat = sinon.stub().callsArgWith(1, null, {
this.stdout = `NODE\t${this.page}\t${this.h}\t${this.v}\t${this.width}\t${this.height}\n`; isFile() {
this.CommandRunner.run = sinon.stub().callsArgWith(6, null, {stdout:this.stdout}); return true
return this.CompileManager.syncFromCode(this.project_id, this.user_id, this.file_name, this.line, this.column, this.callback); }
}); })
this.stdout = `NODE\t${this.page}\t${this.h}\t${this.v}\t${this.width}\t${this.height}\n`
this.CommandRunner.run = sinon
.stub()
.callsArgWith(6, null, { stdout: this.stdout })
return this.CompileManager.syncFromCode(
this.project_id,
this.user_id,
this.file_name,
this.line,
this.column,
this.callback
)
})
it("should execute the synctex binary", function() { it('should execute the synctex binary', function() {
const bin_path = Path.resolve(__dirname + "/../../../bin/synctex"); const bin_path = Path.resolve(__dirname + '/../../../bin/synctex')
const synctex_path = `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}/output.pdf`; const synctex_path = `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}/output.pdf`
const file_path = `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}/${this.file_name}`; const file_path = `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}/${this.file_name}`
return this.CommandRunner.run return this.CommandRunner.run
.calledWith( .calledWith(
`${this.project_id}-${this.user_id}`, `${this.project_id}-${this.user_id}`,
['/opt/synctex', 'code', synctex_path, file_path, this.line, this.column], [
`${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}`, '/opt/synctex',
this.Settings.clsi.docker.image, 'code',
60000, synctex_path,
{} file_path,
).should.equal(true); this.line,
}); this.column
],
`${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}`,
this.Settings.clsi.docker.image,
60000,
{}
)
.should.equal(true)
})
return it("should call the callback with the parsed output", function() { return it('should call the callback with the parsed output', function() {
return this.callback return this.callback
.calledWith(null, [{ .calledWith(null, [
page: this.page, {
h: this.h, page: this.page,
v: this.v, h: this.h,
height: this.height, v: this.v,
width: this.width height: this.height,
}]) width: this.width
.should.equal(true); }
}); ])
}); .should.equal(true)
})
})
return describe("syncFromPdf", function() { return describe('syncFromPdf', function() {
beforeEach(function() { beforeEach(function() {
this.fs.stat = sinon.stub().callsArgWith(1, null,{isFile(){ return true; }}); this.fs.stat = sinon.stub().callsArgWith(1, null, {
this.stdout = `NODE\t${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}/${this.file_name}\t${this.line}\t${this.column}\n`; isFile() {
this.CommandRunner.run = sinon.stub().callsArgWith(6, null, {stdout:this.stdout}); return true
return this.CompileManager.syncFromPdf(this.project_id, this.user_id, this.page, this.h, this.v, this.callback); }
}); })
this.stdout = `NODE\t${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}/${this.file_name}\t${this.line}\t${this.column}\n`
this.CommandRunner.run = sinon
.stub()
.callsArgWith(6, null, { stdout: this.stdout })
return this.CompileManager.syncFromPdf(
this.project_id,
this.user_id,
this.page,
this.h,
this.v,
this.callback
)
})
it("should execute the synctex binary", function() { it('should execute the synctex binary', function() {
const bin_path = Path.resolve(__dirname + "/../../../bin/synctex"); const bin_path = Path.resolve(__dirname + '/../../../bin/synctex')
const synctex_path = `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}/output.pdf`; const synctex_path = `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}/output.pdf`
return this.CommandRunner.run return this.CommandRunner.run
.calledWith( .calledWith(
`${this.project_id}-${this.user_id}`, `${this.project_id}-${this.user_id}`,
['/opt/synctex', "pdf", synctex_path, this.page, this.h, this.v], ['/opt/synctex', 'pdf', synctex_path, this.page, this.h, this.v],
`${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}`, `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}`,
this.Settings.clsi.docker.image, this.Settings.clsi.docker.image,
60000, 60000,
{}).should.equal(true); {}
}); )
.should.equal(true)
})
return it("should call the callback with the parsed output", function() { return it('should call the callback with the parsed output', function() {
return this.callback return this.callback
.calledWith(null, [{ .calledWith(null, [
file: this.file_name, {
line: this.line, file: this.file_name,
column: this.column line: this.line,
}]) column: this.column
.should.equal(true); }
}); ])
}); .should.equal(true)
}); })
})
})
return describe("wordcount", function() { return describe('wordcount', function() {
beforeEach(function() { beforeEach(function() {
this.CommandRunner.run = sinon.stub().callsArg(6); this.CommandRunner.run = sinon.stub().callsArg(6)
this.fs.readFile = sinon.stub().callsArgWith(2, null, (this.stdout = "Encoding: ascii\nWords in text: 2")); this.fs.readFile = sinon
this.callback = sinon.stub(); .stub()
.callsArgWith(
2,
null,
(this.stdout = 'Encoding: ascii\nWords in text: 2')
)
this.callback = sinon.stub()
this.project_id; this.project_id
this.timeout = 60 * 1000; this.timeout = 60 * 1000
this.file_name = "main.tex"; this.file_name = 'main.tex'
this.Settings.path.compilesDir = "/local/compile/directory"; this.Settings.path.compilesDir = '/local/compile/directory'
this.image = "example.com/image"; this.image = 'example.com/image'
return this.CompileManager.wordcount(this.project_id, this.user_id, this.file_name, this.image, this.callback); return this.CompileManager.wordcount(
}); this.project_id,
this.user_id,
this.file_name,
this.image,
this.callback
)
})
it("should run the texcount command", function() { it('should run the texcount command', function() {
this.directory = `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}`; this.directory = `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}`
this.file_path = `$COMPILE_DIR/${this.file_name}`; this.file_path = `$COMPILE_DIR/${this.file_name}`
this.command =[ "texcount", "-nocol", "-inc", this.file_path, `-out=${this.file_path}.wc`]; this.command = [
'texcount',
'-nocol',
'-inc',
this.file_path,
`-out=${this.file_path}.wc`
]
return this.CommandRunner.run return this.CommandRunner.run
.calledWith(`${this.project_id}-${this.user_id}`, this.command, this.directory, this.image, this.timeout, {}) .calledWith(
.should.equal(true); `${this.project_id}-${this.user_id}`,
}); this.command,
this.directory,
this.image,
this.timeout,
{}
)
.should.equal(true)
})
return it("should call the callback with the parsed output", function() { return it('should call the callback with the parsed output', function() {
return this.callback return this.callback
.calledWith(null, { .calledWith(null, {
encode: "ascii", encode: 'ascii',
textWords: 2, textWords: 2,
headWords: 0, headWords: 0,
outside: 0, outside: 0,
headers: 0, headers: 0,
elements: 0, elements: 0,
mathInline: 0, mathInline: 0,
mathDisplay: 0, mathDisplay: 0,
errors: 0, errors: 0,
messages: "" messages: ''
}) })
.should.equal(true); .should.equal(true)
}); })
}); })
}); })

View file

@ -10,73 +10,72 @@
* DS102: Remove unnecessary code created because of implicit returns * DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/ */
const SandboxedModule = require('sandboxed-module'); const SandboxedModule = require('sandboxed-module')
const sinon = require('sinon'); const sinon = require('sinon')
require('chai').should(); require('chai').should()
const modulePath = require('path').join(__dirname, '../../../app/js/ContentTypeMapper'); const modulePath = require('path').join(
__dirname,
'../../../app/js/ContentTypeMapper'
)
describe('ContentTypeMapper', function() { describe('ContentTypeMapper', function() {
beforeEach(function() {
return (this.ContentTypeMapper = SandboxedModule.require(modulePath))
})
beforeEach(function() { return describe('map', function() {
return this.ContentTypeMapper = SandboxedModule.require(modulePath); it('should map .txt to text/plain', function() {
}); const content_type = this.ContentTypeMapper.map('example.txt')
return content_type.should.equal('text/plain')
})
return describe('map', function() { it('should map .csv to text/csv', function() {
const content_type = this.ContentTypeMapper.map('example.csv')
return content_type.should.equal('text/csv')
})
it('should map .txt to text/plain', function() { it('should map .pdf to application/pdf', function() {
const content_type = this.ContentTypeMapper.map('example.txt'); const content_type = this.ContentTypeMapper.map('example.pdf')
return content_type.should.equal('text/plain'); return content_type.should.equal('application/pdf')
}); })
it('should map .csv to text/csv', function() { it('should fall back to octet-stream', function() {
const content_type = this.ContentTypeMapper.map('example.csv'); const content_type = this.ContentTypeMapper.map('example.unknown')
return content_type.should.equal('text/csv'); return content_type.should.equal('application/octet-stream')
}); })
it('should map .pdf to application/pdf', function() { describe('coercing web files to plain text', function() {
const content_type = this.ContentTypeMapper.map('example.pdf'); it('should map .js to plain text', function() {
return content_type.should.equal('application/pdf'); const content_type = this.ContentTypeMapper.map('example.js')
}); return content_type.should.equal('text/plain')
})
it('should fall back to octet-stream', function() { it('should map .html to plain text', function() {
const content_type = this.ContentTypeMapper.map('example.unknown'); const content_type = this.ContentTypeMapper.map('example.html')
return content_type.should.equal('application/octet-stream'); return content_type.should.equal('text/plain')
}); })
describe('coercing web files to plain text', function() { return it('should map .css to plain text', function() {
const content_type = this.ContentTypeMapper.map('example.css')
return content_type.should.equal('text/plain')
})
})
it('should map .js to plain text', function() { return describe('image files', function() {
const content_type = this.ContentTypeMapper.map('example.js'); it('should map .png to image/png', function() {
return content_type.should.equal('text/plain'); const content_type = this.ContentTypeMapper.map('example.png')
}); return content_type.should.equal('image/png')
})
it('should map .html to plain text', function() { it('should map .jpeg to image/jpeg', function() {
const content_type = this.ContentTypeMapper.map('example.html'); const content_type = this.ContentTypeMapper.map('example.jpeg')
return content_type.should.equal('text/plain'); return content_type.should.equal('image/jpeg')
}); })
return it('should map .css to plain text', function() { return it('should map .svg to text/plain to protect against XSS (SVG can execute JS)', function() {
const content_type = this.ContentTypeMapper.map('example.css'); const content_type = this.ContentTypeMapper.map('example.svg')
return content_type.should.equal('text/plain'); return content_type.should.equal('text/plain')
}); })
}); })
})
return describe('image files', function() { })
it('should map .png to image/png', function() {
const content_type = this.ContentTypeMapper.map('example.png');
return content_type.should.equal('image/png');
});
it('should map .jpeg to image/jpeg', function() {
const content_type = this.ContentTypeMapper.map('example.jpeg');
return content_type.should.equal('image/jpeg');
});
return it('should map .svg to text/plain to protect against XSS (SVG can execute JS)', function() {
const content_type = this.ContentTypeMapper.map('example.svg');
return content_type.should.equal('text/plain');
});
});
});
});

View file

@ -9,185 +9,244 @@
* DS102: Remove unnecessary code created because of implicit returns * DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/ */
const SandboxedModule = require('sandboxed-module'); const SandboxedModule = require('sandboxed-module')
const sinon = require('sinon'); const sinon = require('sinon')
require('chai').should(); require('chai').should()
require("coffee-script"); require('coffee-script')
const modulePath = require('path').join(__dirname, '../../../app/coffee/DockerLockManager'); const modulePath = require('path').join(
__dirname,
'../../../app/coffee/DockerLockManager'
)
describe("LockManager", function() { describe('LockManager', function() {
beforeEach(function() { beforeEach(function() {
return this.LockManager = SandboxedModule.require(modulePath, { requires: { return (this.LockManager = SandboxedModule.require(modulePath, {
"settings-sharelatex": (this.Settings = requires: {
{clsi: {docker: {}}}), 'settings-sharelatex': (this.Settings = { clsi: { docker: {} } }),
"logger-sharelatex": (this.logger = { log: sinon.stub(), error: sinon.stub() }) 'logger-sharelatex': (this.logger = {
} log: sinon.stub(),
});}); error: sinon.stub()
})
}
}))
})
return describe("runWithLock", function() { return describe('runWithLock', function() {
describe("with a single lock", function() { describe('with a single lock', function() {
beforeEach(function(done) { beforeEach(function(done) {
this.callback = sinon.stub(); this.callback = sinon.stub()
return this.LockManager.runWithLock("lock-one", releaseLock => return this.LockManager.runWithLock(
setTimeout(() => releaseLock(null, "hello", "world") 'lock-one',
, 100) releaseLock =>
setTimeout(() => releaseLock(null, 'hello', 'world'), 100),
, (err, ...args) => {
this.callback(err,...Array.from(args));
return done();
});
});
return it("should call the callback", function() { (err, ...args) => {
return this.callback.calledWith(null,"hello","world").should.equal(true); this.callback(err, ...Array.from(args))
}); return done()
}); }
)
})
describe("with two locks", function() { return it('should call the callback', function() {
beforeEach(function(done) { return this.callback
this.callback1 = sinon.stub(); .calledWith(null, 'hello', 'world')
this.callback2 = sinon.stub(); .should.equal(true)
this.LockManager.runWithLock("lock-one", releaseLock => })
setTimeout(() => releaseLock(null, "hello", "world","one") })
, 100)
, (err, ...args) => {
return this.callback1(err,...Array.from(args));
});
return this.LockManager.runWithLock("lock-two", releaseLock =>
setTimeout(() => releaseLock(null, "hello", "world","two")
, 200)
, (err, ...args) => {
this.callback2(err,...Array.from(args));
return done();
});
});
it("should call the first callback", function() { describe('with two locks', function() {
return this.callback1.calledWith(null,"hello","world","one").should.equal(true); beforeEach(function(done) {
}); this.callback1 = sinon.stub()
this.callback2 = sinon.stub()
this.LockManager.runWithLock(
'lock-one',
releaseLock =>
setTimeout(() => releaseLock(null, 'hello', 'world', 'one'), 100),
return it("should call the second callback", function() { (err, ...args) => {
return this.callback2.calledWith(null,"hello","world","two").should.equal(true); return this.callback1(err, ...Array.from(args))
}); }
}); )
return this.LockManager.runWithLock(
'lock-two',
releaseLock =>
setTimeout(() => releaseLock(null, 'hello', 'world', 'two'), 200),
return describe("with lock contention", function() { (err, ...args) => {
describe("where the first lock is released quickly", function() { this.callback2(err, ...Array.from(args))
beforeEach(function(done) { return done()
this.LockManager.MAX_LOCK_WAIT_TIME = 1000; }
this.LockManager.LOCK_TEST_INTERVAL = 100; )
this.callback1 = sinon.stub(); })
this.callback2 = sinon.stub();
this.LockManager.runWithLock("lock", releaseLock =>
setTimeout(() => releaseLock(null, "hello", "world","one")
, 100)
, (err, ...args) => {
return this.callback1(err,...Array.from(args));
});
return this.LockManager.runWithLock("lock", releaseLock =>
setTimeout(() => releaseLock(null, "hello", "world","two")
, 200)
, (err, ...args) => {
this.callback2(err,...Array.from(args));
return done();
});
});
it("should call the first callback", function() { it('should call the first callback', function() {
return this.callback1.calledWith(null,"hello","world","one").should.equal(true); return this.callback1
}); .calledWith(null, 'hello', 'world', 'one')
.should.equal(true)
})
return it("should call the second callback", function() { return it('should call the second callback', function() {
return this.callback2.calledWith(null,"hello","world","two").should.equal(true); return this.callback2
}); .calledWith(null, 'hello', 'world', 'two')
}); .should.equal(true)
})
})
describe("where the first lock is held longer than the waiting time", function() { return describe('with lock contention', function() {
beforeEach(function(done) { describe('where the first lock is released quickly', function() {
let doneTwo; beforeEach(function(done) {
this.LockManager.MAX_LOCK_HOLD_TIME = 10000; this.LockManager.MAX_LOCK_WAIT_TIME = 1000
this.LockManager.MAX_LOCK_WAIT_TIME = 1000; this.LockManager.LOCK_TEST_INTERVAL = 100
this.LockManager.LOCK_TEST_INTERVAL = 100; this.callback1 = sinon.stub()
this.callback1 = sinon.stub(); this.callback2 = sinon.stub()
this.callback2 = sinon.stub(); this.LockManager.runWithLock(
let doneOne = (doneTwo = false); 'lock',
const finish = function(key) { releaseLock =>
if (key === 1) { doneOne = true; } setTimeout(() => releaseLock(null, 'hello', 'world', 'one'), 100),
if (key === 2) { doneTwo = true; }
if (doneOne && doneTwo) { return done(); }
};
this.LockManager.runWithLock("lock", releaseLock =>
setTimeout(() => releaseLock(null, "hello", "world","one")
, 1100)
, (err, ...args) => {
this.callback1(err,...Array.from(args));
return finish(1);
});
return this.LockManager.runWithLock("lock", releaseLock =>
setTimeout(() => releaseLock(null, "hello", "world","two")
, 100)
, (err, ...args) => {
this.callback2(err,...Array.from(args));
return finish(2);
});
});
it("should call the first callback", function() { (err, ...args) => {
return this.callback1.calledWith(null,"hello","world","one").should.equal(true); return this.callback1(err, ...Array.from(args))
}); }
)
return this.LockManager.runWithLock(
'lock',
releaseLock =>
setTimeout(() => releaseLock(null, 'hello', 'world', 'two'), 200),
return it("should call the second callback with an error", function() { (err, ...args) => {
const error = sinon.match.instanceOf(Error); this.callback2(err, ...Array.from(args))
return this.callback2.calledWith(error).should.equal(true); return done()
}); }
}); )
})
return describe("where the first lock is held longer than the max holding time", function() { it('should call the first callback', function() {
beforeEach(function(done) { return this.callback1
let doneTwo; .calledWith(null, 'hello', 'world', 'one')
this.LockManager.MAX_LOCK_HOLD_TIME = 1000; .should.equal(true)
this.LockManager.MAX_LOCK_WAIT_TIME = 2000; })
this.LockManager.LOCK_TEST_INTERVAL = 100;
this.callback1 = sinon.stub();
this.callback2 = sinon.stub();
let doneOne = (doneTwo = false);
const finish = function(key) {
if (key === 1) { doneOne = true; }
if (key === 2) { doneTwo = true; }
if (doneOne && doneTwo) { return done(); }
};
this.LockManager.runWithLock("lock", releaseLock =>
setTimeout(() => releaseLock(null, "hello", "world","one")
, 1500)
, (err, ...args) => {
this.callback1(err,...Array.from(args));
return finish(1);
});
return this.LockManager.runWithLock("lock", releaseLock =>
setTimeout(() => releaseLock(null, "hello", "world","two")
, 100)
, (err, ...args) => {
this.callback2(err,...Array.from(args));
return finish(2);
});
});
it("should call the first callback", function() { return it('should call the second callback', function() {
return this.callback1.calledWith(null,"hello","world","one").should.equal(true); return this.callback2
}); .calledWith(null, 'hello', 'world', 'two')
.should.equal(true)
})
})
return it("should call the second callback", function() { describe('where the first lock is held longer than the waiting time', function() {
return this.callback2.calledWith(null,"hello","world","two").should.equal(true); beforeEach(function(done) {
}); let doneTwo
}); this.LockManager.MAX_LOCK_HOLD_TIME = 10000
}); this.LockManager.MAX_LOCK_WAIT_TIME = 1000
}); this.LockManager.LOCK_TEST_INTERVAL = 100
}); this.callback1 = sinon.stub()
this.callback2 = sinon.stub()
let doneOne = (doneTwo = false)
const finish = function(key) {
if (key === 1) {
doneOne = true
}
if (key === 2) {
doneTwo = true
}
if (doneOne && doneTwo) {
return done()
}
}
this.LockManager.runWithLock(
'lock',
releaseLock =>
setTimeout(
() => releaseLock(null, 'hello', 'world', 'one'),
1100
),
(err, ...args) => {
this.callback1(err, ...Array.from(args))
return finish(1)
}
)
return this.LockManager.runWithLock(
'lock',
releaseLock =>
setTimeout(() => releaseLock(null, 'hello', 'world', 'two'), 100),
(err, ...args) => {
this.callback2(err, ...Array.from(args))
return finish(2)
}
)
})
it('should call the first callback', function() {
return this.callback1
.calledWith(null, 'hello', 'world', 'one')
.should.equal(true)
})
return it('should call the second callback with an error', function() {
const error = sinon.match.instanceOf(Error)
return this.callback2.calledWith(error).should.equal(true)
})
})
return describe('where the first lock is held longer than the max holding time', function() {
beforeEach(function(done) {
let doneTwo
this.LockManager.MAX_LOCK_HOLD_TIME = 1000
this.LockManager.MAX_LOCK_WAIT_TIME = 2000
this.LockManager.LOCK_TEST_INTERVAL = 100
this.callback1 = sinon.stub()
this.callback2 = sinon.stub()
let doneOne = (doneTwo = false)
const finish = function(key) {
if (key === 1) {
doneOne = true
}
if (key === 2) {
doneTwo = true
}
if (doneOne && doneTwo) {
return done()
}
}
this.LockManager.runWithLock(
'lock',
releaseLock =>
setTimeout(
() => releaseLock(null, 'hello', 'world', 'one'),
1500
),
(err, ...args) => {
this.callback1(err, ...Array.from(args))
return finish(1)
}
)
return this.LockManager.runWithLock(
'lock',
releaseLock =>
setTimeout(() => releaseLock(null, 'hello', 'world', 'two'), 100),
(err, ...args) => {
this.callback2(err, ...Array.from(args))
return finish(2)
}
)
})
it('should call the first callback', function() {
return this.callback1
.calledWith(null, 'hello', 'world', 'one')
.should.equal(true)
})
return it('should call the second callback', function() {
return this.callback2
.calledWith(null, 'hello', 'world', 'two')
.should.equal(true)
})
})
})
})
})

File diff suppressed because it is too large Load diff

View file

@ -8,75 +8,79 @@
* DS102: Remove unnecessary code created because of implicit returns * DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/ */
const SandboxedModule = require('sandboxed-module'); const SandboxedModule = require('sandboxed-module')
const sinon = require('sinon'); const sinon = require('sinon')
require('chai').should(); require('chai').should()
const modulePath = require('path').join(__dirname, '../../../app/js/DraftModeManager'); const modulePath = require('path').join(
__dirname,
'../../../app/js/DraftModeManager'
)
describe('DraftModeManager', function() { describe('DraftModeManager', function() {
beforeEach(function() { beforeEach(function() {
return this.DraftModeManager = SandboxedModule.require(modulePath, { requires: { return (this.DraftModeManager = SandboxedModule.require(modulePath, {
"fs": (this.fs = {}), requires: {
"logger-sharelatex": (this.logger = {log() {}}) fs: (this.fs = {}),
} 'logger-sharelatex': (this.logger = { log() {} })
});}); }
}))
describe("_injectDraftOption", function() { })
it("should add draft option into documentclass with existing options", function() {
return this.DraftModeManager
._injectDraftOption(`\
\\documentclass[a4paper,foo=bar]{article}\
`)
.should.equal(`\
\\documentclass[draft,a4paper,foo=bar]{article}\
`);
});
return it("should add draft option into documentclass with no options", function() { describe('_injectDraftOption', function() {
return this.DraftModeManager it('should add draft option into documentclass with existing options', function() {
._injectDraftOption(`\ return this.DraftModeManager._injectDraftOption(`\
\\documentclass{article}\ \\documentclass[a4paper,foo=bar]{article}\
`).should.equal(`\
\\documentclass[draft,a4paper,foo=bar]{article}\
`) `)
.should.equal(`\ })
return it('should add draft option into documentclass with no options', function() {
return this.DraftModeManager._injectDraftOption(`\
\\documentclass{article}\
`).should.equal(`\
\\documentclass[draft]{article}\ \\documentclass[draft]{article}\
`); `)
}); })
}); })
return describe("injectDraftMode", function() { return describe('injectDraftMode', function() {
beforeEach(function() { beforeEach(function() {
this.filename = "/mock/filename.tex"; this.filename = '/mock/filename.tex'
this.callback = sinon.stub(); this.callback = sinon.stub()
const content = `\ const content = `\
\\documentclass{article} \\documentclass{article}
\\begin{document} \\begin{document}
Hello world Hello world
\\end{document}\ \\end{document}\
`; `
this.fs.readFile = sinon.stub().callsArgWith(2, null, content); this.fs.readFile = sinon.stub().callsArgWith(2, null, content)
this.fs.writeFile = sinon.stub().callsArg(2); this.fs.writeFile = sinon.stub().callsArg(2)
return this.DraftModeManager.injectDraftMode(this.filename, this.callback); return this.DraftModeManager.injectDraftMode(this.filename, this.callback)
}); })
it("should read the file", function() { it('should read the file', function() {
return this.fs.readFile return this.fs.readFile
.calledWith(this.filename, "utf8") .calledWith(this.filename, 'utf8')
.should.equal(true); .should.equal(true)
}); })
it("should write the modified file", function() { it('should write the modified file', function() {
return this.fs.writeFile return this.fs.writeFile
.calledWith(this.filename, `\ .calledWith(
this.filename,
`\
\\documentclass[draft]{article} \\documentclass[draft]{article}
\\begin{document} \\begin{document}
Hello world Hello world
\\end{document}\ \\end{document}\
`) `
.should.equal(true); )
}); .should.equal(true)
})
return it("should call the callback", function() {
return this.callback.called.should.equal(true); return it('should call the callback', function() {
}); return this.callback.called.should.equal(true)
}); })
}); })
})

View file

@ -9,103 +9,129 @@
* DS102: Remove unnecessary code created because of implicit returns * DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/ */
const SandboxedModule = require('sandboxed-module'); const SandboxedModule = require('sandboxed-module')
const sinon = require('sinon'); const sinon = require('sinon')
require('chai').should(); require('chai').should()
const modulePath = require('path').join(__dirname, '../../../app/js/LatexRunner'); const modulePath = require('path').join(
const Path = require("path"); __dirname,
'../../../app/js/LatexRunner'
)
const Path = require('path')
describe("LatexRunner", function() { describe('LatexRunner', function() {
beforeEach(function() { beforeEach(function() {
let Timer; let Timer
this.LatexRunner = SandboxedModule.require(modulePath, { requires: { this.LatexRunner = SandboxedModule.require(modulePath, {
"settings-sharelatex": (this.Settings = { requires: {
docker: { 'settings-sharelatex': (this.Settings = {
socketPath: "/var/run/docker.sock" docker: {
} socketPath: '/var/run/docker.sock'
}), }
"logger-sharelatex": (this.logger = { log: sinon.stub(), error: sinon.stub() }), }),
"./Metrics": { 'logger-sharelatex': (this.logger = {
Timer: (Timer = class Timer { log: sinon.stub(),
done() {} error: sinon.stub()
}) }),
}, './Metrics': {
"./CommandRunner": (this.CommandRunner = {}) Timer: (Timer = class Timer {
} done() {}
}); })
},
'./CommandRunner': (this.CommandRunner = {})
}
})
this.directory = "/local/compile/directory"; this.directory = '/local/compile/directory'
this.mainFile = "main-file.tex"; this.mainFile = 'main-file.tex'
this.compiler = "pdflatex"; this.compiler = 'pdflatex'
this.image = "example.com/image"; this.image = 'example.com/image'
this.callback = sinon.stub(); this.callback = sinon.stub()
this.project_id = "project-id-123"; this.project_id = 'project-id-123'
return this.env = {'foo': '123'};}); return (this.env = { foo: '123' })
})
return describe("runLatex", function() { return describe('runLatex', function() {
beforeEach(function() { beforeEach(function() {
return this.CommandRunner.run = sinon.stub().callsArg(6); return (this.CommandRunner.run = sinon.stub().callsArg(6))
}); })
describe("normally", function() { describe('normally', function() {
beforeEach(function() { beforeEach(function() {
return this.LatexRunner.runLatex(this.project_id, { return this.LatexRunner.runLatex(
directory: this.directory, this.project_id,
mainFile: this.mainFile, {
compiler: this.compiler, directory: this.directory,
timeout: (this.timeout = 42000), mainFile: this.mainFile,
image: this.image, compiler: this.compiler,
environment: this.env timeout: (this.timeout = 42000),
}, image: this.image,
this.callback); environment: this.env
}); },
this.callback
)
})
return it("should run the latex command", function() { return it('should run the latex command', function() {
return this.CommandRunner.run return this.CommandRunner.run
.calledWith(this.project_id, sinon.match.any, this.directory, this.image, this.timeout, this.env) .calledWith(
.should.equal(true); this.project_id,
}); sinon.match.any,
}); this.directory,
this.image,
this.timeout,
this.env
)
.should.equal(true)
})
})
describe("with an .Rtex main file", function() { describe('with an .Rtex main file', function() {
beforeEach(function() { beforeEach(function() {
return this.LatexRunner.runLatex(this.project_id, { return this.LatexRunner.runLatex(
directory: this.directory, this.project_id,
mainFile: "main-file.Rtex", {
compiler: this.compiler, directory: this.directory,
image: this.image, mainFile: 'main-file.Rtex',
timeout: (this.timeout = 42000) compiler: this.compiler,
}, image: this.image,
this.callback); timeout: (this.timeout = 42000)
}); },
this.callback
)
})
return it("should run the latex command on the equivalent .tex file", function() { return it('should run the latex command on the equivalent .tex file', function() {
const command = this.CommandRunner.run.args[0][1]; const command = this.CommandRunner.run.args[0][1]
const mainFile = command.slice(-1)[0]; const mainFile = command.slice(-1)[0]
return mainFile.should.equal("$COMPILE_DIR/main-file.tex"); return mainFile.should.equal('$COMPILE_DIR/main-file.tex')
}); })
}); })
return describe("with a flags option", function() { return describe('with a flags option', function() {
beforeEach(function() { beforeEach(function() {
return this.LatexRunner.runLatex(this.project_id, { return this.LatexRunner.runLatex(
directory: this.directory, this.project_id,
mainFile: this.mainFile, {
compiler: this.compiler, directory: this.directory,
image: this.image, mainFile: this.mainFile,
timeout: (this.timeout = 42000), compiler: this.compiler,
flags: ["-file-line-error", "-halt-on-error"] image: this.image,
}, timeout: (this.timeout = 42000),
this.callback); flags: ['-file-line-error', '-halt-on-error']
}); },
this.callback
)
})
return it("should include the flags in the command", function() { return it('should include the flags in the command', function() {
const command = this.CommandRunner.run.args[0][1]; const command = this.CommandRunner.run.args[0][1]
const flags = command.filter(arg => (arg === "-file-line-error") || (arg === "-halt-on-error")); const flags = command.filter(
flags.length.should.equal(2); arg => arg === '-file-line-error' || arg === '-halt-on-error'
flags[0].should.equal("-file-line-error"); )
return flags[1].should.equal("-halt-on-error"); flags.length.should.equal(2)
}); flags[0].should.equal('-file-line-error')
}); return flags[1].should.equal('-halt-on-error')
}); })
}); })
})
})

View file

@ -9,75 +9,85 @@
* DS102: Remove unnecessary code created because of implicit returns * DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/ */
const SandboxedModule = require('sandboxed-module'); const SandboxedModule = require('sandboxed-module')
const sinon = require('sinon'); const sinon = require('sinon')
require('chai').should(); require('chai').should()
const modulePath = require('path').join(__dirname, '../../../app/js/LockManager'); const modulePath = require('path').join(
const Path = require("path"); __dirname,
const Errors = require("../../../app/js/Errors"); '../../../app/js/LockManager'
)
const Path = require('path')
const Errors = require('../../../app/js/Errors')
describe("DockerLockManager", function() { describe('DockerLockManager', function() {
beforeEach(function() { beforeEach(function() {
this.LockManager = SandboxedModule.require(modulePath, { requires: { this.LockManager = SandboxedModule.require(modulePath, {
"settings-sharelatex": {}, requires: {
"logger-sharelatex": (this.logger = { log: sinon.stub(), error: sinon.stub(), err() {} }), 'settings-sharelatex': {},
"fs": { 'logger-sharelatex': (this.logger = {
lstat:sinon.stub().callsArgWith(1), log: sinon.stub(),
readdir: sinon.stub().callsArgWith(1) error: sinon.stub(),
}, err() {}
"lockfile": (this.Lockfile = {}) }),
} fs: {
}); lstat: sinon.stub().callsArgWith(1),
return this.lockFile = "/local/compile/directory/.project-lock"; readdir: sinon.stub().callsArgWith(1)
}); },
lockfile: (this.Lockfile = {})
}
})
return (this.lockFile = '/local/compile/directory/.project-lock')
})
return describe("runWithLock", function() { return describe('runWithLock', function() {
beforeEach(function() { beforeEach(function() {
this.runner = sinon.stub().callsArgWith(0, null, "foo", "bar"); this.runner = sinon.stub().callsArgWith(0, null, 'foo', 'bar')
return this.callback = sinon.stub(); return (this.callback = sinon.stub())
}); })
describe("normally", function() { describe('normally', function() {
beforeEach(function() { beforeEach(function() {
this.Lockfile.lock = sinon.stub().callsArgWith(2, null); this.Lockfile.lock = sinon.stub().callsArgWith(2, null)
this.Lockfile.unlock = sinon.stub().callsArgWith(1, null); this.Lockfile.unlock = sinon.stub().callsArgWith(1, null)
return this.LockManager.runWithLock(this.lockFile, this.runner, this.callback); return this.LockManager.runWithLock(
}); this.lockFile,
this.runner,
this.callback
)
})
it("should run the compile", function() { it('should run the compile', function() {
return this.runner return this.runner.calledWith().should.equal(true)
.calledWith() })
.should.equal(true);
});
return it("should call the callback with the response from the compile", function() { return it('should call the callback with the response from the compile', function() {
return this.callback return this.callback
.calledWithExactly(null, "foo", "bar") .calledWithExactly(null, 'foo', 'bar')
.should.equal(true); .should.equal(true)
}); })
}); })
return describe("when the project is locked", function() { return describe('when the project is locked', function() {
beforeEach(function() { beforeEach(function() {
this.error = new Error(); this.error = new Error()
this.error.code = "EEXIST"; this.error.code = 'EEXIST'
this.Lockfile.lock = sinon.stub().callsArgWith(2,this.error); this.Lockfile.lock = sinon.stub().callsArgWith(2, this.error)
this.Lockfile.unlock = sinon.stub().callsArgWith(1, null); this.Lockfile.unlock = sinon.stub().callsArgWith(1, null)
return this.LockManager.runWithLock(this.lockFile, this.runner, this.callback); return this.LockManager.runWithLock(
}); this.lockFile,
this.runner,
this.callback
)
})
it("should not run the compile", function() { it('should not run the compile', function() {
return this.runner return this.runner.called.should.equal(false)
.called })
.should.equal(false);
});
return it("should return an error", function() { return it('should return an error', function() {
const error = new Errors.AlreadyCompilingError(); const error = new Errors.AlreadyCompilingError()
return this.callback return this.callback.calledWithExactly(error).should.equal(true)
.calledWithExactly(error) })
.should.equal(true); })
}); })
}); })
});
});

View file

@ -10,90 +10,96 @@
* DS102: Remove unnecessary code created because of implicit returns * DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/ */
const SandboxedModule = require('sandboxed-module'); const SandboxedModule = require('sandboxed-module')
const sinon = require('sinon'); const sinon = require('sinon')
require('chai').should(); require('chai').should()
const modulePath = require('path').join(__dirname, '../../../app/js/OutputFileFinder'); const modulePath = require('path').join(
const path = require("path"); __dirname,
const { expect } = require("chai"); '../../../app/js/OutputFileFinder'
const { EventEmitter } = require("events"); )
const path = require('path')
const { expect } = require('chai')
const { EventEmitter } = require('events')
describe("OutputFileFinder", function() { describe('OutputFileFinder', function() {
beforeEach(function() { beforeEach(function() {
this.OutputFileFinder = SandboxedModule.require(modulePath, { requires: { this.OutputFileFinder = SandboxedModule.require(modulePath, {
"fs": (this.fs = {}), requires: {
"child_process": { spawn: (this.spawn = sinon.stub()) fs: (this.fs = {}),
}, child_process: { spawn: (this.spawn = sinon.stub()) },
"logger-sharelatex": { log: sinon.stub(), warn: sinon.stub() } 'logger-sharelatex': { log: sinon.stub(), warn: sinon.stub() }
} }
}); })
this.directory = "/test/dir"; this.directory = '/test/dir'
return this.callback = sinon.stub(); return (this.callback = sinon.stub())
}); })
describe("findOutputFiles", function() { describe('findOutputFiles', function() {
beforeEach(function() { beforeEach(function() {
this.resource_path = "resource/path.tex"; this.resource_path = 'resource/path.tex'
this.output_paths = ["output.pdf", "extra/file.tex"]; this.output_paths = ['output.pdf', 'extra/file.tex']
this.all_paths = this.output_paths.concat([this.resource_path]); this.all_paths = this.output_paths.concat([this.resource_path])
this.resources = [ this.resources = [{ path: (this.resource_path = 'resource/path.tex') }]
{path: (this.resource_path = "resource/path.tex")} this.OutputFileFinder._getAllFiles = sinon
]; .stub()
this.OutputFileFinder._getAllFiles = sinon.stub().callsArgWith(1, null, this.all_paths); .callsArgWith(1, null, this.all_paths)
return this.OutputFileFinder.findOutputFiles(this.resources, this.directory, (error, outputFiles) => { return this.OutputFileFinder.findOutputFiles(
this.outputFiles = outputFiles; this.resources,
this.directory,
}); (error, outputFiles) => {
}); this.outputFiles = outputFiles
}
)
})
return it("should only return the output files, not directories or resource paths", function() { return it('should only return the output files, not directories or resource paths', function() {
return expect(this.outputFiles).to.deep.equal([{ return expect(this.outputFiles).to.deep.equal([
path: "output.pdf", {
type: "pdf" path: 'output.pdf',
}, { type: 'pdf'
path: "extra/file.tex", },
type: "tex" {
}]); path: 'extra/file.tex',
}); type: 'tex'
}); }
])
return describe("_getAllFiles", function() { })
beforeEach(function() { })
this.proc = new EventEmitter();
this.proc.stdout = new EventEmitter();
this.spawn.returns(this.proc);
this.directory = "/base/dir";
return this.OutputFileFinder._getAllFiles(this.directory, this.callback);
});
describe("successfully", function() {
beforeEach(function() {
this.proc.stdout.emit(
"data",
["/base/dir/main.tex", "/base/dir/chapters/chapter1.tex"].join("\n") + "\n"
);
return this.proc.emit("close", 0);
});
return it("should call the callback with the relative file paths", function() {
return this.callback.calledWith(
null,
["main.tex", "chapters/chapter1.tex"]
).should.equal(true);
});
});
return describe("when the directory doesn't exist", function() { return describe('_getAllFiles', function() {
beforeEach(function() { beforeEach(function() {
return this.proc.emit("close", 1); this.proc = new EventEmitter()
}); this.proc.stdout = new EventEmitter()
this.spawn.returns(this.proc)
return it("should call the callback with a blank array", function() { this.directory = '/base/dir'
return this.callback.calledWith( return this.OutputFileFinder._getAllFiles(this.directory, this.callback)
null, })
[]
).should.equal(true); describe('successfully', function() {
}); beforeEach(function() {
}); this.proc.stdout.emit(
}); 'data',
}); ['/base/dir/main.tex', '/base/dir/chapters/chapter1.tex'].join('\n') +
'\n'
)
return this.proc.emit('close', 0)
})
return it('should call the callback with the relative file paths', function() {
return this.callback
.calledWith(null, ['main.tex', 'chapters/chapter1.tex'])
.should.equal(true)
})
})
return describe("when the directory doesn't exist", function() {
beforeEach(function() {
return this.proc.emit('close', 1)
})
return it('should call the callback with a blank array', function() {
return this.callback.calledWith(null, []).should.equal(true)
})
})
})
})

View file

@ -10,139 +10,187 @@
* DS102: Remove unnecessary code created because of implicit returns * DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/ */
const SandboxedModule = require('sandboxed-module'); const SandboxedModule = require('sandboxed-module')
const sinon = require('sinon'); const sinon = require('sinon')
require('chai').should(); require('chai').should()
const modulePath = require('path').join(__dirname, '../../../app/js/OutputFileOptimiser'); const modulePath = require('path').join(
const path = require("path"); __dirname,
const { expect } = require("chai"); '../../../app/js/OutputFileOptimiser'
const { EventEmitter } = require("events"); )
const path = require('path')
const { expect } = require('chai')
const { EventEmitter } = require('events')
describe("OutputFileOptimiser", function() { describe('OutputFileOptimiser', function() {
beforeEach(function() { beforeEach(function() {
this.OutputFileOptimiser = SandboxedModule.require(modulePath, { requires: { this.OutputFileOptimiser = SandboxedModule.require(modulePath, {
"fs": (this.fs = {}), requires: {
"path": (this.Path = {}), fs: (this.fs = {}),
"child_process": { spawn: (this.spawn = sinon.stub()) path: (this.Path = {}),
}, child_process: { spawn: (this.spawn = sinon.stub()) },
"logger-sharelatex": { log: sinon.stub(), warn: sinon.stub() }, 'logger-sharelatex': { log: sinon.stub(), warn: sinon.stub() },
"./Metrics" : {} './Metrics': {}
} }
}); })
this.directory = "/test/dir"; this.directory = '/test/dir'
return this.callback = sinon.stub(); return (this.callback = sinon.stub())
}); })
describe("optimiseFile", function() { describe('optimiseFile', function() {
beforeEach(function() { beforeEach(function() {
this.src = "./output.pdf"; this.src = './output.pdf'
return this.dst = "./output.pdf"; return (this.dst = './output.pdf')
}); })
describe("when the file is not a pdf file", function() { describe('when the file is not a pdf file', function() {
beforeEach(function(done){ beforeEach(function(done) {
this.src = "./output.log"; this.src = './output.log'
this.OutputFileOptimiser.checkIfPDFIsOptimised = sinon.stub().callsArgWith(1, null, false); this.OutputFileOptimiser.checkIfPDFIsOptimised = sinon
this.OutputFileOptimiser.optimisePDF = sinon.stub().callsArgWith(2, null); .stub()
return this.OutputFileOptimiser.optimiseFile(this.src, this.dst, done); .callsArgWith(1, null, false)
}); this.OutputFileOptimiser.optimisePDF = sinon
.stub()
.callsArgWith(2, null)
return this.OutputFileOptimiser.optimiseFile(this.src, this.dst, done)
})
it("should not check if the file is optimised", function() { it('should not check if the file is optimised', function() {
return this.OutputFileOptimiser.checkIfPDFIsOptimised.calledWith(this.src).should.equal(false); return this.OutputFileOptimiser.checkIfPDFIsOptimised
}); .calledWith(this.src)
.should.equal(false)
})
return it("should not optimise the file", function() { return it('should not optimise the file', function() {
return this.OutputFileOptimiser.optimisePDF.calledWith(this.src, this.dst).should.equal(false); return this.OutputFileOptimiser.optimisePDF
}); .calledWith(this.src, this.dst)
}); .should.equal(false)
})
})
describe("when the pdf file is not optimised", function() { describe('when the pdf file is not optimised', function() {
beforeEach(function(done) { beforeEach(function(done) {
this.OutputFileOptimiser.checkIfPDFIsOptimised = sinon.stub().callsArgWith(1, null, false); this.OutputFileOptimiser.checkIfPDFIsOptimised = sinon
this.OutputFileOptimiser.optimisePDF = sinon.stub().callsArgWith(2, null); .stub()
return this.OutputFileOptimiser.optimiseFile(this.src, this.dst, done); .callsArgWith(1, null, false)
}); this.OutputFileOptimiser.optimisePDF = sinon
.stub()
.callsArgWith(2, null)
return this.OutputFileOptimiser.optimiseFile(this.src, this.dst, done)
})
it("should check if the pdf is optimised", function() { it('should check if the pdf is optimised', function() {
return this.OutputFileOptimiser.checkIfPDFIsOptimised.calledWith(this.src).should.equal(true); return this.OutputFileOptimiser.checkIfPDFIsOptimised
}); .calledWith(this.src)
.should.equal(true)
})
return it("should optimise the pdf", function() { return it('should optimise the pdf', function() {
return this.OutputFileOptimiser.optimisePDF.calledWith(this.src, this.dst).should.equal(true); return this.OutputFileOptimiser.optimisePDF
}); .calledWith(this.src, this.dst)
}); .should.equal(true)
})
})
return describe("when the pdf file is optimised", function() { return describe('when the pdf file is optimised', function() {
beforeEach(function(done) { beforeEach(function(done) {
this.OutputFileOptimiser.checkIfPDFIsOptimised = sinon.stub().callsArgWith(1, null, true); this.OutputFileOptimiser.checkIfPDFIsOptimised = sinon
this.OutputFileOptimiser.optimisePDF = sinon.stub().callsArgWith(2, null); .stub()
return this.OutputFileOptimiser.optimiseFile(this.src, this.dst, done); .callsArgWith(1, null, true)
}); this.OutputFileOptimiser.optimisePDF = sinon
.stub()
.callsArgWith(2, null)
return this.OutputFileOptimiser.optimiseFile(this.src, this.dst, done)
})
it("should check if the pdf is optimised", function() { it('should check if the pdf is optimised', function() {
return this.OutputFileOptimiser.checkIfPDFIsOptimised.calledWith(this.src).should.equal(true); return this.OutputFileOptimiser.checkIfPDFIsOptimised
}); .calledWith(this.src)
.should.equal(true)
})
return it("should not optimise the pdf", function() { return it('should not optimise the pdf', function() {
return this.OutputFileOptimiser.optimisePDF.calledWith(this.src, this.dst).should.equal(false); return this.OutputFileOptimiser.optimisePDF
}); .calledWith(this.src, this.dst)
}); .should.equal(false)
}); })
})
})
return describe("checkIfPDFISOptimised", function() { return describe('checkIfPDFISOptimised', function() {
beforeEach(function() { beforeEach(function() {
this.callback = sinon.stub(); this.callback = sinon.stub()
this.fd = 1234; this.fd = 1234
this.fs.open = sinon.stub().yields(null, this.fd); this.fs.open = sinon.stub().yields(null, this.fd)
this.fs.read = sinon.stub().withArgs(this.fd).yields(null, 100, new Buffer("hello /Linearized 1")); this.fs.read = sinon
this.fs.close = sinon.stub().withArgs(this.fd).yields(null); .stub()
return this.OutputFileOptimiser.checkIfPDFIsOptimised(this.src, this.callback); .withArgs(this.fd)
}); .yields(null, 100, new Buffer('hello /Linearized 1'))
this.fs.close = sinon
.stub()
.withArgs(this.fd)
.yields(null)
return this.OutputFileOptimiser.checkIfPDFIsOptimised(
this.src,
this.callback
)
})
describe("for a linearised file", function() { describe('for a linearised file', function() {
beforeEach(function() { beforeEach(function() {
this.fs.read = sinon.stub().withArgs(this.fd).yields(null, 100, new Buffer("hello /Linearized 1")); this.fs.read = sinon
return this.OutputFileOptimiser.checkIfPDFIsOptimised(this.src, this.callback); .stub()
}); .withArgs(this.fd)
.yields(null, 100, new Buffer('hello /Linearized 1'))
return this.OutputFileOptimiser.checkIfPDFIsOptimised(
this.src,
this.callback
)
})
it("should open the file", function() { it('should open the file', function() {
return this.fs.open.calledWith(this.src, "r").should.equal(true); return this.fs.open.calledWith(this.src, 'r').should.equal(true)
}); })
it("should read the header", function() { it('should read the header', function() {
return this.fs.read.calledWith(this.fd).should.equal(true); return this.fs.read.calledWith(this.fd).should.equal(true)
}); })
it("should close the file", function() { it('should close the file', function() {
return this.fs.close.calledWith(this.fd).should.equal(true); return this.fs.close.calledWith(this.fd).should.equal(true)
}); })
return it("should call the callback with a true result", function() { return it('should call the callback with a true result', function() {
return this.callback.calledWith(null, true).should.equal(true); return this.callback.calledWith(null, true).should.equal(true)
}); })
}); })
return describe("for an unlinearised file", function() { return describe('for an unlinearised file', function() {
beforeEach(function() { beforeEach(function() {
this.fs.read = sinon.stub().withArgs(this.fd).yields(null, 100, new Buffer("hello not linearized 1")); this.fs.read = sinon
return this.OutputFileOptimiser.checkIfPDFIsOptimised(this.src, this.callback); .stub()
}); .withArgs(this.fd)
.yields(null, 100, new Buffer('hello not linearized 1'))
return this.OutputFileOptimiser.checkIfPDFIsOptimised(
this.src,
this.callback
)
})
it("should open the file", function() { it('should open the file', function() {
return this.fs.open.calledWith(this.src, "r").should.equal(true); return this.fs.open.calledWith(this.src, 'r').should.equal(true)
}); })
it("should read the header", function() { it('should read the header', function() {
return this.fs.read.calledWith(this.fd).should.equal(true); return this.fs.read.calledWith(this.fd).should.equal(true)
}); })
it("should close the file", function() { it('should close the file', function() {
return this.fs.close.calledWith(this.fd).should.equal(true); return this.fs.close.calledWith(this.fd).should.equal(true)
}); })
return it("should call the callback with a false result", function() { return it('should call the callback with a false result', function() {
return this.callback.calledWith(null, false).should.equal(true); return this.callback.calledWith(null, false).should.equal(true)
}); })
}); })
}); })
}); })

View file

@ -11,79 +11,90 @@
* DS102: Remove unnecessary code created because of implicit returns * DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/ */
const SandboxedModule = require('sandboxed-module'); const SandboxedModule = require('sandboxed-module')
const sinon = require('sinon'); const sinon = require('sinon')
require('chai').should(); require('chai').should()
const modulePath = require('path').join(__dirname, '../../../app/js/ProjectPersistenceManager'); const modulePath = require('path').join(
const tk = require("timekeeper"); __dirname,
'../../../app/js/ProjectPersistenceManager'
)
const tk = require('timekeeper')
describe("ProjectPersistenceManager", function() { describe('ProjectPersistenceManager', function() {
beforeEach(function() { beforeEach(function() {
this.ProjectPersistenceManager = SandboxedModule.require(modulePath, { requires: { this.ProjectPersistenceManager = SandboxedModule.require(modulePath, {
"./UrlCache": (this.UrlCache = {}), requires: {
"./CompileManager": (this.CompileManager = {}), './UrlCache': (this.UrlCache = {}),
"logger-sharelatex": (this.logger = { log: sinon.stub() }), './CompileManager': (this.CompileManager = {}),
"./db": (this.db = {}) 'logger-sharelatex': (this.logger = { log: sinon.stub() }),
} './db': (this.db = {})
}); }
this.callback = sinon.stub(); })
this.project_id = "project-id-123"; this.callback = sinon.stub()
return this.user_id = "1234"; this.project_id = 'project-id-123'
}); return (this.user_id = '1234')
})
describe("clearExpiredProjects", function() { describe('clearExpiredProjects', function() {
beforeEach(function() { beforeEach(function() {
this.project_ids = [ this.project_ids = ['project-id-1', 'project-id-2']
"project-id-1", this.ProjectPersistenceManager._findExpiredProjectIds = sinon
"project-id-2" .stub()
]; .callsArgWith(0, null, this.project_ids)
this.ProjectPersistenceManager._findExpiredProjectIds = sinon.stub().callsArgWith(0, null, this.project_ids); this.ProjectPersistenceManager.clearProjectFromCache = sinon
this.ProjectPersistenceManager.clearProjectFromCache = sinon.stub().callsArg(1); .stub()
this.CompileManager.clearExpiredProjects = sinon.stub().callsArg(1); .callsArg(1)
return this.ProjectPersistenceManager.clearExpiredProjects(this.callback); this.CompileManager.clearExpiredProjects = sinon.stub().callsArg(1)
}); return this.ProjectPersistenceManager.clearExpiredProjects(this.callback)
})
it("should clear each expired project", function() { it('should clear each expired project', function() {
return Array.from(this.project_ids).map((project_id) => return Array.from(this.project_ids).map(project_id =>
this.ProjectPersistenceManager.clearProjectFromCache this.ProjectPersistenceManager.clearProjectFromCache
.calledWith(project_id) .calledWith(project_id)
.should.equal(true)); .should.equal(true)
}); )
})
return it("should call the callback", function() { return it('should call the callback', function() {
return this.callback.called.should.equal(true); return this.callback.called.should.equal(true)
}); })
}); })
return describe("clearProject", function() { return describe('clearProject', function() {
beforeEach(function() { beforeEach(function() {
this.ProjectPersistenceManager._clearProjectFromDatabase = sinon.stub().callsArg(1); this.ProjectPersistenceManager._clearProjectFromDatabase = sinon
this.UrlCache.clearProject = sinon.stub().callsArg(1); .stub()
this.CompileManager.clearProject = sinon.stub().callsArg(2); .callsArg(1)
return this.ProjectPersistenceManager.clearProject(this.project_id, this.user_id, this.callback); this.UrlCache.clearProject = sinon.stub().callsArg(1)
}); this.CompileManager.clearProject = sinon.stub().callsArg(2)
return this.ProjectPersistenceManager.clearProject(
this.project_id,
this.user_id,
this.callback
)
})
it("should clear the project from the database", function() { it('should clear the project from the database', function() {
return this.ProjectPersistenceManager._clearProjectFromDatabase return this.ProjectPersistenceManager._clearProjectFromDatabase
.calledWith(this.project_id) .calledWith(this.project_id)
.should.equal(true); .should.equal(true)
}); })
it("should clear all the cached Urls for the project", function() { it('should clear all the cached Urls for the project', function() {
return this.UrlCache.clearProject return this.UrlCache.clearProject
.calledWith(this.project_id) .calledWith(this.project_id)
.should.equal(true); .should.equal(true)
}); })
it("should clear the project compile folder", function() { it('should clear the project compile folder', function() {
return this.CompileManager.clearProject return this.CompileManager.clearProject
.calledWith(this.project_id, this.user_id) .calledWith(this.project_id, this.user_id)
.should.equal(true); .should.equal(true)
}); })
return it("should call the callback", function() { return it('should call the callback', function() {
return this.callback.called.should.equal(true); return this.callback.called.should.equal(true)
}); })
}); })
}); })

View file

@ -9,378 +9,412 @@
* DS102: Remove unnecessary code created because of implicit returns * DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/ */
const SandboxedModule = require('sandboxed-module'); const SandboxedModule = require('sandboxed-module')
const sinon = require('sinon'); const sinon = require('sinon')
require('chai').should(); require('chai').should()
const { expect } = require('chai'); const { expect } = require('chai')
const modulePath = require('path').join(__dirname, '../../../app/js/RequestParser'); const modulePath = require('path').join(
const tk = require("timekeeper"); __dirname,
'../../../app/js/RequestParser'
)
const tk = require('timekeeper')
describe("RequestParser", function() { describe('RequestParser', function() {
beforeEach(function() { beforeEach(function() {
tk.freeze(); tk.freeze()
this.callback = sinon.stub(); this.callback = sinon.stub()
this.validResource = { this.validResource = {
path: "main.tex", path: 'main.tex',
date: "12:00 01/02/03", date: '12:00 01/02/03',
content: "Hello world" content: 'Hello world'
}; }
this.validRequest = { this.validRequest = {
compile: { compile: {
token: "token-123", token: 'token-123',
options: { options: {
imageName: "basicImageName/here:2017-1", imageName: 'basicImageName/here:2017-1',
compiler: "pdflatex", compiler: 'pdflatex',
timeout: 42 timeout: 42
}, },
resources: [] resources: []
} }
}; }
return this.RequestParser = SandboxedModule.require(modulePath, { requires: { return (this.RequestParser = SandboxedModule.require(modulePath, {
"settings-sharelatex": (this.settings = {}) requires: {
} 'settings-sharelatex': (this.settings = {})
});}); }
}))
})
afterEach(function() { return tk.reset(); }); afterEach(function() {
return tk.reset()
})
describe("without a top level object", function() { describe('without a top level object', function() {
beforeEach(function() { beforeEach(function() {
return this.RequestParser.parse([], this.callback); return this.RequestParser.parse([], this.callback)
}); })
return it("should return an error", function() { return it('should return an error', function() {
return this.callback.calledWith("top level object should have a compile attribute") return this.callback
.should.equal(true); .calledWith('top level object should have a compile attribute')
}); .should.equal(true)
}); })
})
describe("without a compile attribute", function() { describe('without a compile attribute', function() {
beforeEach(function() { beforeEach(function() {
return this.RequestParser.parse({}, this.callback); return this.RequestParser.parse({}, this.callback)
}); })
return it("should return an error", function() { return it('should return an error', function() {
return this.callback.calledWith("top level object should have a compile attribute") return this.callback
.should.equal(true); .calledWith('top level object should have a compile attribute')
}); .should.equal(true)
}); })
})
describe("without a valid compiler", function() { describe('without a valid compiler', function() {
beforeEach(function() { beforeEach(function() {
this.validRequest.compile.options.compiler = "not-a-compiler"; this.validRequest.compile.options.compiler = 'not-a-compiler'
return this.RequestParser.parse(this.validRequest, this.callback); return this.RequestParser.parse(this.validRequest, this.callback)
}); })
return it("should return an error", function() { return it('should return an error', function() {
return this.callback.calledWith("compiler attribute should be one of: pdflatex, latex, xelatex, lualatex") return this.callback
.should.equal(true); .calledWith(
}); 'compiler attribute should be one of: pdflatex, latex, xelatex, lualatex'
}); )
.should.equal(true)
})
})
describe("without a compiler specified", function() { describe('without a compiler specified', function() {
beforeEach(function() { beforeEach(function() {
delete this.validRequest.compile.options.compiler; delete this.validRequest.compile.options.compiler
return this.RequestParser.parse(this.validRequest, (error, data) => { return this.RequestParser.parse(this.validRequest, (error, data) => {
this.data = data; this.data = data
})
}); })
});
return it("should set the compiler to pdflatex by default", function() { return it('should set the compiler to pdflatex by default', function() {
return this.data.compiler.should.equal("pdflatex"); return this.data.compiler.should.equal('pdflatex')
}); })
}); })
describe("with imageName set", function() { describe('with imageName set', function() {
beforeEach(function() { beforeEach(function() {
return this.RequestParser.parse(this.validRequest, (error, data) => { return this.RequestParser.parse(this.validRequest, (error, data) => {
this.data = data; this.data = data
})
}); })
});
return it("should set the imageName", function() { return it('should set the imageName', function() {
return this.data.imageName.should.equal("basicImageName/here:2017-1"); return this.data.imageName.should.equal('basicImageName/here:2017-1')
}); })
}); })
describe("with flags set", function() { describe('with flags set', function() {
beforeEach(function() { beforeEach(function() {
this.validRequest.compile.options.flags = ["-file-line-error"]; this.validRequest.compile.options.flags = ['-file-line-error']
return this.RequestParser.parse(this.validRequest, (error, data) => { return this.RequestParser.parse(this.validRequest, (error, data) => {
this.data = data; this.data = data
})
}); })
});
return it("should set the flags attribute", function() { return it('should set the flags attribute', function() {
return expect(this.data.flags).to.deep.equal(["-file-line-error"]); return expect(this.data.flags).to.deep.equal(['-file-line-error'])
}); })
}); })
describe("with flags not specified", function() { describe('with flags not specified', function() {
beforeEach(function() { beforeEach(function() {
return this.RequestParser.parse(this.validRequest, (error, data) => { return this.RequestParser.parse(this.validRequest, (error, data) => {
this.data = data; this.data = data
})
}); })
});
return it("it should have an empty flags list", function() { return it('it should have an empty flags list', function() {
return expect(this.data.flags).to.deep.equal([]); return expect(this.data.flags).to.deep.equal([])
}); })
}); })
describe("without a timeout specified", function() { describe('without a timeout specified', function() {
beforeEach(function() { beforeEach(function() {
delete this.validRequest.compile.options.timeout; delete this.validRequest.compile.options.timeout
return this.RequestParser.parse(this.validRequest, (error, data) => { return this.RequestParser.parse(this.validRequest, (error, data) => {
this.data = data; this.data = data
})
}); })
});
return it("should set the timeout to MAX_TIMEOUT", function() { return it('should set the timeout to MAX_TIMEOUT', function() {
return this.data.timeout.should.equal(this.RequestParser.MAX_TIMEOUT * 1000); return this.data.timeout.should.equal(
}); this.RequestParser.MAX_TIMEOUT * 1000
}); )
})
})
describe("with a timeout larger than the maximum", function() { describe('with a timeout larger than the maximum', function() {
beforeEach(function() { beforeEach(function() {
this.validRequest.compile.options.timeout = this.RequestParser.MAX_TIMEOUT + 1; this.validRequest.compile.options.timeout =
return this.RequestParser.parse(this.validRequest, (error, data) => { this.RequestParser.MAX_TIMEOUT + 1
this.data = data; return this.RequestParser.parse(this.validRequest, (error, data) => {
this.data = data
}); })
}); })
return it("should set the timeout to MAX_TIMEOUT", function() { return it('should set the timeout to MAX_TIMEOUT', function() {
return this.data.timeout.should.equal(this.RequestParser.MAX_TIMEOUT * 1000); return this.data.timeout.should.equal(
}); this.RequestParser.MAX_TIMEOUT * 1000
}); )
})
})
describe("with a timeout", function() { describe('with a timeout', function() {
beforeEach(function() { beforeEach(function() {
return this.RequestParser.parse(this.validRequest, (error, data) => { return this.RequestParser.parse(this.validRequest, (error, data) => {
this.data = data; this.data = data
})
}); })
});
return it("should set the timeout (in milliseconds)", function() { return it('should set the timeout (in milliseconds)', function() {
return this.data.timeout.should.equal(this.validRequest.compile.options.timeout * 1000); return this.data.timeout.should.equal(
}); this.validRequest.compile.options.timeout * 1000
}); )
})
})
describe("with a resource without a path", function() { describe('with a resource without a path', function() {
beforeEach(function() { beforeEach(function() {
delete this.validResource.path; delete this.validResource.path
this.validRequest.compile.resources.push(this.validResource); this.validRequest.compile.resources.push(this.validResource)
return this.RequestParser.parse(this.validRequest, this.callback); return this.RequestParser.parse(this.validRequest, this.callback)
}); })
return it("should return an error", function() { return it('should return an error', function() {
return this.callback.calledWith("all resources should have a path attribute") return this.callback
.should.equal(true); .calledWith('all resources should have a path attribute')
}); .should.equal(true)
}); })
})
describe("with a resource with a path", function() { describe('with a resource with a path', function() {
beforeEach(function() { beforeEach(function() {
this.validResource.path = (this.path = "test.tex"); this.validResource.path = this.path = 'test.tex'
this.validRequest.compile.resources.push(this.validResource); this.validRequest.compile.resources.push(this.validResource)
this.RequestParser.parse(this.validRequest, this.callback); this.RequestParser.parse(this.validRequest, this.callback)
return this.data = this.callback.args[0][1];}); return (this.data = this.callback.args[0][1])
})
return it("should return the path in the parsed response", function() { return it('should return the path in the parsed response', function() {
return this.data.resources[0].path.should.equal(this.path); return this.data.resources[0].path.should.equal(this.path)
}); })
}); })
describe("with a resource with a malformed modified date", function() { describe('with a resource with a malformed modified date', function() {
beforeEach(function() { beforeEach(function() {
this.validResource.modified = "not-a-date"; this.validResource.modified = 'not-a-date'
this.validRequest.compile.resources.push(this.validResource); this.validRequest.compile.resources.push(this.validResource)
return this.RequestParser.parse(this.validRequest, this.callback); return this.RequestParser.parse(this.validRequest, this.callback)
}); })
return it("should return an error", function() { return it('should return an error', function() {
return this.callback return this.callback
.calledWith( .calledWith(
"resource modified date could not be understood: "+ 'resource modified date could not be understood: ' +
this.validResource.modified this.validResource.modified
) )
.should.equal(true); .should.equal(true)
}); })
}); })
describe("with a resource with a valid date", function() { describe('with a resource with a valid date', function() {
beforeEach(function() { beforeEach(function() {
this.date = "12:00 01/02/03"; this.date = '12:00 01/02/03'
this.validResource.modified = this.date; this.validResource.modified = this.date
this.validRequest.compile.resources.push(this.validResource); this.validRequest.compile.resources.push(this.validResource)
this.RequestParser.parse(this.validRequest, this.callback); this.RequestParser.parse(this.validRequest, this.callback)
return this.data = this.callback.args[0][1];}); return (this.data = this.callback.args[0][1])
})
return it("should return the date as a Javascript Date object", function() { return it('should return the date as a Javascript Date object', function() {
(this.data.resources[0].modified instanceof Date).should.equal(true); ;(this.data.resources[0].modified instanceof Date).should.equal(true)
return this.data.resources[0].modified.getTime().should.equal(Date.parse(this.date)); return this.data.resources[0].modified
}); .getTime()
}); .should.equal(Date.parse(this.date))
})
})
describe("with a resource without either a content or URL attribute", function() { describe('with a resource without either a content or URL attribute', function() {
beforeEach(function() { beforeEach(function() {
delete this.validResource.url; delete this.validResource.url
delete this.validResource.content; delete this.validResource.content
this.validRequest.compile.resources.push(this.validResource); this.validRequest.compile.resources.push(this.validResource)
return this.RequestParser.parse(this.validRequest, this.callback); return this.RequestParser.parse(this.validRequest, this.callback)
}); })
return it("should return an error", function() { return it('should return an error', function() {
return this.callback.calledWith("all resources should have either a url or content attribute") return this.callback
.should.equal(true); .calledWith(
}); 'all resources should have either a url or content attribute'
}); )
.should.equal(true)
})
})
describe("with a resource where the content is not a string", function() { describe('with a resource where the content is not a string', function() {
beforeEach(function() { beforeEach(function() {
this.validResource.content = []; this.validResource.content = []
this.validRequest.compile.resources.push(this.validResource); this.validRequest.compile.resources.push(this.validResource)
return this.RequestParser.parse((this.validRequest), this.callback); return this.RequestParser.parse(this.validRequest, this.callback)
}); })
return it("should return an error", function() { return it('should return an error', function() {
return this.callback.calledWith("content attribute should be a string") return this.callback
.should.equal(true); .calledWith('content attribute should be a string')
}); .should.equal(true)
}); })
})
describe("with a resource where the url is not a string", function() { describe('with a resource where the url is not a string', function() {
beforeEach(function() { beforeEach(function() {
this.validResource.url = []; this.validResource.url = []
this.validRequest.compile.resources.push(this.validResource); this.validRequest.compile.resources.push(this.validResource)
return this.RequestParser.parse((this.validRequest), this.callback); return this.RequestParser.parse(this.validRequest, this.callback)
}); })
return it("should return an error", function() { return it('should return an error', function() {
return this.callback.calledWith("url attribute should be a string") return this.callback
.should.equal(true); .calledWith('url attribute should be a string')
}); .should.equal(true)
}); })
})
describe("with a resource with a url", function() { describe('with a resource with a url', function() {
beforeEach(function() { beforeEach(function() {
this.validResource.url = (this.url = "www.example.com"); this.validResource.url = this.url = 'www.example.com'
this.validRequest.compile.resources.push(this.validResource); this.validRequest.compile.resources.push(this.validResource)
this.RequestParser.parse((this.validRequest), this.callback); this.RequestParser.parse(this.validRequest, this.callback)
return this.data = this.callback.args[0][1];}); return (this.data = this.callback.args[0][1])
})
return it("should return the url in the parsed response", function() { return it('should return the url in the parsed response', function() {
return this.data.resources[0].url.should.equal(this.url); return this.data.resources[0].url.should.equal(this.url)
}); })
}); })
describe("with a resource with a content attribute", function() { describe('with a resource with a content attribute', function() {
beforeEach(function() { beforeEach(function() {
this.validResource.content = (this.content = "Hello world"); this.validResource.content = this.content = 'Hello world'
this.validRequest.compile.resources.push(this.validResource); this.validRequest.compile.resources.push(this.validResource)
this.RequestParser.parse((this.validRequest), this.callback); this.RequestParser.parse(this.validRequest, this.callback)
return this.data = this.callback.args[0][1];}); return (this.data = this.callback.args[0][1])
})
return it("should return the content in the parsed response", function() { return it('should return the content in the parsed response', function() {
return this.data.resources[0].content.should.equal(this.content); return this.data.resources[0].content.should.equal(this.content)
}); })
}); })
describe("without a root resource path", function() { describe('without a root resource path', function() {
beforeEach(function() { beforeEach(function() {
delete this.validRequest.compile.rootResourcePath; delete this.validRequest.compile.rootResourcePath
this.RequestParser.parse((this.validRequest), this.callback); this.RequestParser.parse(this.validRequest, this.callback)
return this.data = this.callback.args[0][1];}); return (this.data = this.callback.args[0][1])
})
return it("should set the root resource path to 'main.tex' by default", function() { return it("should set the root resource path to 'main.tex' by default", function() {
return this.data.rootResourcePath.should.equal("main.tex"); return this.data.rootResourcePath.should.equal('main.tex')
}); })
}); })
describe("with a root resource path", function() { describe('with a root resource path', function() {
beforeEach(function() { beforeEach(function() {
this.validRequest.compile.rootResourcePath = (this.path = "test.tex"); this.validRequest.compile.rootResourcePath = this.path = 'test.tex'
this.RequestParser.parse((this.validRequest), this.callback); this.RequestParser.parse(this.validRequest, this.callback)
return this.data = this.callback.args[0][1];}); return (this.data = this.callback.args[0][1])
})
return it("should return the root resource path in the parsed response", function() { return it('should return the root resource path in the parsed response', function() {
return this.data.rootResourcePath.should.equal(this.path); return this.data.rootResourcePath.should.equal(this.path)
}); })
}); })
describe("with a root resource path that is not a string", function() { describe('with a root resource path that is not a string', function() {
beforeEach(function() { beforeEach(function() {
this.validRequest.compile.rootResourcePath = []; this.validRequest.compile.rootResourcePath = []
return this.RequestParser.parse((this.validRequest), this.callback); return this.RequestParser.parse(this.validRequest, this.callback)
}); })
return it("should return an error", function() { return it('should return an error', function() {
return this.callback.calledWith("rootResourcePath attribute should be a string") return this.callback
.should.equal(true); .calledWith('rootResourcePath attribute should be a string')
}); .should.equal(true)
}); })
})
describe("with a root resource path that needs escaping", function() { describe('with a root resource path that needs escaping', function() {
beforeEach(function() { beforeEach(function() {
this.badPath = "`rm -rf foo`.tex"; this.badPath = '`rm -rf foo`.tex'
this.goodPath = "rm -rf foo.tex"; this.goodPath = 'rm -rf foo.tex'
this.validRequest.compile.rootResourcePath = this.badPath; this.validRequest.compile.rootResourcePath = this.badPath
this.validRequest.compile.resources.push({ this.validRequest.compile.resources.push({
path: this.badPath, path: this.badPath,
date: "12:00 01/02/03", date: '12:00 01/02/03',
content: "Hello world" content: 'Hello world'
}); })
this.RequestParser.parse(this.validRequest, this.callback); this.RequestParser.parse(this.validRequest, this.callback)
return this.data = this.callback.args[0][1];}); return (this.data = this.callback.args[0][1])
})
it("should return the escaped resource", function() { it('should return the escaped resource', function() {
return this.data.rootResourcePath.should.equal(this.goodPath); return this.data.rootResourcePath.should.equal(this.goodPath)
}); })
return it("should also escape the resource path", function() { return it('should also escape the resource path', function() {
return this.data.resources[0].path.should.equal(this.goodPath); return this.data.resources[0].path.should.equal(this.goodPath)
}); })
}); })
describe("with a root resource path that has a relative path", function() { describe('with a root resource path that has a relative path', function() {
beforeEach(function() { beforeEach(function() {
this.validRequest.compile.rootResourcePath = "foo/../../bar.tex"; this.validRequest.compile.rootResourcePath = 'foo/../../bar.tex'
this.RequestParser.parse(this.validRequest, this.callback); this.RequestParser.parse(this.validRequest, this.callback)
return this.data = this.callback.args[0][1];}); return (this.data = this.callback.args[0][1])
})
return it("should return an error", function() { return it('should return an error', function() {
return this.callback.calledWith("relative path in root resource") return this.callback
.should.equal(true); .calledWith('relative path in root resource')
}); .should.equal(true)
}); })
})
describe("with a root resource path that has unescaped + relative path", function() { describe('with a root resource path that has unescaped + relative path', function() {
beforeEach(function() { beforeEach(function() {
this.validRequest.compile.rootResourcePath = "foo/#../bar.tex"; this.validRequest.compile.rootResourcePath = 'foo/#../bar.tex'
this.RequestParser.parse(this.validRequest, this.callback); this.RequestParser.parse(this.validRequest, this.callback)
return this.data = this.callback.args[0][1];}); return (this.data = this.callback.args[0][1])
})
return it("should return an error", function() { return it('should return an error', function() {
return this.callback.calledWith("relative path in root resource") return this.callback
.should.equal(true); .calledWith('relative path in root resource')
}); .should.equal(true)
}); })
})
return describe("with an unknown syncType", function() { return describe('with an unknown syncType', function() {
beforeEach(function() { beforeEach(function() {
this.validRequest.compile.options.syncType = "unexpected"; this.validRequest.compile.options.syncType = 'unexpected'
this.RequestParser.parse(this.validRequest, this.callback); this.RequestParser.parse(this.validRequest, this.callback)
return this.data = this.callback.args[0][1];}); return (this.data = this.callback.args[0][1])
})
return it("should return an error", function() { return it('should return an error', function() {
return this.callback.calledWith("syncType attribute should be one of: full, incremental") return this.callback
.should.equal(true); .calledWith('syncType attribute should be one of: full, incremental')
}); .should.equal(true)
}); })
}); })
})

View file

@ -9,145 +9,200 @@
* DS102: Remove unnecessary code created because of implicit returns * DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/ */
const SandboxedModule = require('sandboxed-module'); const SandboxedModule = require('sandboxed-module')
const sinon = require('sinon'); const sinon = require('sinon')
const should = require('chai').should(); const should = require('chai').should()
const modulePath = require('path').join(__dirname, '../../../app/js/ResourceStateManager'); const modulePath = require('path').join(
const Path = require("path"); __dirname,
const Errors = require("../../../app/js/Errors"); '../../../app/js/ResourceStateManager'
)
const Path = require('path')
const Errors = require('../../../app/js/Errors')
describe("ResourceStateManager", function() { describe('ResourceStateManager', function() {
beforeEach(function() { beforeEach(function() {
this.ResourceStateManager = SandboxedModule.require(modulePath, { requires: { this.ResourceStateManager = SandboxedModule.require(modulePath, {
"fs": (this.fs = {}), requires: {
"logger-sharelatex": {log: sinon.stub(), err: sinon.stub()}, fs: (this.fs = {}),
"./SafeReader": (this.SafeReader = {}) 'logger-sharelatex': { log: sinon.stub(), err: sinon.stub() },
} './SafeReader': (this.SafeReader = {})
}); }
this.basePath = "/path/to/write/files/to"; })
this.resources = [ this.basePath = '/path/to/write/files/to'
{path: "resource-1-mock"}, this.resources = [
{path: "resource-2-mock"}, { path: 'resource-1-mock' },
{path: "resource-3-mock"} { path: 'resource-2-mock' },
]; { path: 'resource-3-mock' }
this.state = "1234567890"; ]
this.resourceFileName = `${this.basePath}/.project-sync-state`; this.state = '1234567890'
this.resourceFileContents = `${this.resources[0].path}\n${this.resources[1].path}\n${this.resources[2].path}\nstateHash:${this.state}`; this.resourceFileName = `${this.basePath}/.project-sync-state`
return this.callback = sinon.stub(); this.resourceFileContents = `${this.resources[0].path}\n${this.resources[1].path}\n${this.resources[2].path}\nstateHash:${this.state}`
}); return (this.callback = sinon.stub())
})
describe("saveProjectState", function() { describe('saveProjectState', function() {
beforeEach(function() { beforeEach(function() {
return this.fs.writeFile = sinon.stub().callsArg(2); return (this.fs.writeFile = sinon.stub().callsArg(2))
}); })
describe("when the state is specified", function() { describe('when the state is specified', function() {
beforeEach(function() { beforeEach(function() {
return this.ResourceStateManager.saveProjectState(this.state, this.resources, this.basePath, this.callback); return this.ResourceStateManager.saveProjectState(
}); this.state,
this.resources,
this.basePath,
this.callback
)
})
it("should write the resource list to disk", function() { it('should write the resource list to disk', function() {
return this.fs.writeFile return this.fs.writeFile
.calledWith(this.resourceFileName, this.resourceFileContents) .calledWith(this.resourceFileName, this.resourceFileContents)
.should.equal(true); .should.equal(true)
}); })
return it("should call the callback", function() { return it('should call the callback', function() {
return this.callback.called.should.equal(true); return this.callback.called.should.equal(true)
}); })
}); })
return describe("when the state is undefined", function() { return describe('when the state is undefined', function() {
beforeEach(function() { beforeEach(function() {
this.state = undefined; this.state = undefined
this.fs.unlink = sinon.stub().callsArg(1); this.fs.unlink = sinon.stub().callsArg(1)
return this.ResourceStateManager.saveProjectState(this.state, this.resources, this.basePath, this.callback); return this.ResourceStateManager.saveProjectState(
}); this.state,
this.resources,
this.basePath,
this.callback
)
})
it("should unlink the resource file", function() { it('should unlink the resource file', function() {
return this.fs.unlink return this.fs.unlink
.calledWith(this.resourceFileName) .calledWith(this.resourceFileName)
.should.equal(true); .should.equal(true)
}); })
it("should not write the resource list to disk", function() { it('should not write the resource list to disk', function() {
return this.fs.writeFile.called.should.equal(false); return this.fs.writeFile.called.should.equal(false)
}); })
return it("should call the callback", function() { return it('should call the callback', function() {
return this.callback.called.should.equal(true); return this.callback.called.should.equal(true)
}); })
}); })
}); })
describe("checkProjectStateMatches", function() { describe('checkProjectStateMatches', function() {
describe('when the state matches', function() {
beforeEach(function() {
this.SafeReader.readFile = sinon
.stub()
.callsArgWith(3, null, this.resourceFileContents)
return this.ResourceStateManager.checkProjectStateMatches(
this.state,
this.basePath,
this.callback
)
})
describe("when the state matches", function() { it('should read the resource file', function() {
beforeEach(function() { return this.SafeReader.readFile
this.SafeReader.readFile = sinon.stub().callsArgWith(3, null, this.resourceFileContents); .calledWith(this.resourceFileName)
return this.ResourceStateManager.checkProjectStateMatches(this.state, this.basePath, this.callback); .should.equal(true)
}); })
it("should read the resource file", function() { return it('should call the callback with the results', function() {
return this.SafeReader.readFile return this.callback
.calledWith(this.resourceFileName) .calledWithMatch(null, this.resources)
.should.equal(true); .should.equal(true)
}); })
})
return it("should call the callback with the results", function() { return describe('when the state does not match', function() {
return this.callback.calledWithMatch(null, this.resources).should.equal(true); beforeEach(function() {
}); this.SafeReader.readFile = sinon
}); .stub()
.callsArgWith(3, null, this.resourceFileContents)
return this.ResourceStateManager.checkProjectStateMatches(
'not-the-original-state',
this.basePath,
this.callback
)
})
return describe("when the state does not match", function() { return it('should call the callback with an error', function() {
beforeEach(function() { const error = new Errors.FilesOutOfSyncError(
this.SafeReader.readFile = sinon.stub().callsArgWith(3, null, this.resourceFileContents); 'invalid state for incremental update'
return this.ResourceStateManager.checkProjectStateMatches("not-the-original-state", this.basePath, this.callback); )
}); return this.callback.calledWith(error).should.equal(true)
})
})
})
return it("should call the callback with an error", function() { return describe('checkResourceFiles', function() {
const error = new Errors.FilesOutOfSyncError("invalid state for incremental update"); describe('when all the files are present', function() {
return this.callback.calledWith(error).should.equal(true); beforeEach(function() {
}); this.allFiles = [
}); this.resources[0].path,
}); this.resources[1].path,
this.resources[2].path
]
return this.ResourceStateManager.checkResourceFiles(
this.resources,
this.allFiles,
this.basePath,
this.callback
)
})
return describe("checkResourceFiles", function() { return it('should call the callback', function() {
describe("when all the files are present", function() { return this.callback.calledWithExactly().should.equal(true)
beforeEach(function() { })
this.allFiles = [ this.resources[0].path, this.resources[1].path, this.resources[2].path]; })
return this.ResourceStateManager.checkResourceFiles(this.resources, this.allFiles, this.basePath, this.callback);
});
return it("should call the callback", function() { describe('when there is a missing file', function() {
return this.callback.calledWithExactly().should.equal(true); beforeEach(function() {
}); this.allFiles = [this.resources[0].path, this.resources[1].path]
}); this.fs.stat = sinon.stub().callsArgWith(1, new Error())
return this.ResourceStateManager.checkResourceFiles(
this.resources,
this.allFiles,
this.basePath,
this.callback
)
})
describe("when there is a missing file", function() { return it('should call the callback with an error', function() {
beforeEach(function() { const error = new Errors.FilesOutOfSyncError(
this.allFiles = [ this.resources[0].path, this.resources[1].path]; 'resource files missing in incremental update'
this.fs.stat = sinon.stub().callsArgWith(1, new Error()); )
return this.ResourceStateManager.checkResourceFiles(this.resources, this.allFiles, this.basePath, this.callback); return this.callback.calledWith(error).should.equal(true)
}); })
})
return it("should call the callback with an error", function() { return describe('when a resource contains a relative path', function() {
const error = new Errors.FilesOutOfSyncError("resource files missing in incremental update"); beforeEach(function() {
return this.callback.calledWith(error).should.equal(true); this.resources[0].path = '../foo/bar.tex'
}); this.allFiles = [
}); this.resources[0].path,
this.resources[1].path,
return describe("when a resource contains a relative path", function() { this.resources[2].path
beforeEach(function() { ]
this.resources[0].path = "../foo/bar.tex"; return this.ResourceStateManager.checkResourceFiles(
this.allFiles = [ this.resources[0].path, this.resources[1].path, this.resources[2].path]; this.resources,
return this.ResourceStateManager.checkResourceFiles(this.resources, this.allFiles, this.basePath, this.callback); this.allFiles,
}); this.basePath,
this.callback
return it("should call the callback with an error", function() { )
return this.callback.calledWith(new Error("relative path in resource file list")).should.equal(true); })
});
});
});
});
return it('should call the callback with an error', function() {
return this.callback
.calledWith(new Error('relative path in resource file list'))
.should.equal(true)
})
})
})
})

View file

@ -10,405 +10,491 @@
* DS206: Consider reworking classes to avoid initClass * DS206: Consider reworking classes to avoid initClass
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/ */
const SandboxedModule = require('sandboxed-module'); const SandboxedModule = require('sandboxed-module')
const sinon = require('sinon'); const sinon = require('sinon')
const should = require('chai').should(); const should = require('chai').should()
const modulePath = require('path').join(__dirname, '../../../app/js/ResourceWriter'); const modulePath = require('path').join(
const path = require("path"); __dirname,
'../../../app/js/ResourceWriter'
)
const path = require('path')
describe("ResourceWriter", function() { describe('ResourceWriter', function() {
beforeEach(function() { beforeEach(function() {
let Timer; let Timer
this.ResourceWriter = SandboxedModule.require(modulePath, { requires: { this.ResourceWriter = SandboxedModule.require(modulePath, {
"fs": (this.fs = { requires: {
mkdir: sinon.stub().callsArg(1), fs: (this.fs = {
unlink: sinon.stub().callsArg(1) mkdir: sinon.stub().callsArg(1),
}), unlink: sinon.stub().callsArg(1)
"./ResourceStateManager": (this.ResourceStateManager = {}), }),
"wrench": (this.wrench = {}), './ResourceStateManager': (this.ResourceStateManager = {}),
"./UrlCache" : (this.UrlCache = {}), wrench: (this.wrench = {}),
"mkdirp" : (this.mkdirp = sinon.stub().callsArg(1)), './UrlCache': (this.UrlCache = {}),
"./OutputFileFinder": (this.OutputFileFinder = {}), mkdirp: (this.mkdirp = sinon.stub().callsArg(1)),
"logger-sharelatex": {log: sinon.stub(), err: sinon.stub()}, './OutputFileFinder': (this.OutputFileFinder = {}),
"./Metrics": (this.Metrics = { 'logger-sharelatex': { log: sinon.stub(), err: sinon.stub() },
Timer: (Timer = (function() { './Metrics': (this.Metrics = {
Timer = class Timer { Timer: (Timer = (function() {
static initClass() { Timer = class Timer {
this.prototype.done = sinon.stub(); static initClass() {
} this.prototype.done = sinon.stub()
}; }
Timer.initClass(); }
return Timer; Timer.initClass()
})()) return Timer
}) })())
} })
} }
); })
this.project_id = "project-id-123"; this.project_id = 'project-id-123'
this.basePath = "/path/to/write/files/to"; this.basePath = '/path/to/write/files/to'
return this.callback = sinon.stub(); return (this.callback = sinon.stub())
}); })
describe("syncResourcesToDisk on a full request", function() { describe('syncResourcesToDisk on a full request', function() {
beforeEach(function() { beforeEach(function() {
this.resources = [ this.resources = ['resource-1-mock', 'resource-2-mock', 'resource-3-mock']
"resource-1-mock", this.ResourceWriter._writeResourceToDisk = sinon.stub().callsArg(3)
"resource-2-mock", this.ResourceWriter._removeExtraneousFiles = sinon.stub().callsArg(2)
"resource-3-mock" this.ResourceStateManager.saveProjectState = sinon.stub().callsArg(3)
]; return this.ResourceWriter.syncResourcesToDisk(
this.ResourceWriter._writeResourceToDisk = sinon.stub().callsArg(3); {
this.ResourceWriter._removeExtraneousFiles = sinon.stub().callsArg(2); project_id: this.project_id,
this.ResourceStateManager.saveProjectState = sinon.stub().callsArg(3); syncState: (this.syncState = '0123456789abcdef'),
return this.ResourceWriter.syncResourcesToDisk({ resources: this.resources
project_id: this.project_id, },
syncState: (this.syncState = "0123456789abcdef"), this.basePath,
resources: this.resources this.callback
}, this.basePath, this.callback); )
}); })
it("should remove old files", function() { it('should remove old files', function() {
return this.ResourceWriter._removeExtraneousFiles return this.ResourceWriter._removeExtraneousFiles
.calledWith(this.resources, this.basePath) .calledWith(this.resources, this.basePath)
.should.equal(true); .should.equal(true)
}); })
it("should write each resource to disk", function() { it('should write each resource to disk', function() {
return Array.from(this.resources).map((resource) => return Array.from(this.resources).map(resource =>
this.ResourceWriter._writeResourceToDisk this.ResourceWriter._writeResourceToDisk
.calledWith(this.project_id, resource, this.basePath) .calledWith(this.project_id, resource, this.basePath)
.should.equal(true)); .should.equal(true)
}); )
})
it("should store the sync state and resource list", function() { it('should store the sync state and resource list', function() {
return this.ResourceStateManager.saveProjectState return this.ResourceStateManager.saveProjectState
.calledWith(this.syncState, this.resources, this.basePath) .calledWith(this.syncState, this.resources, this.basePath)
.should.equal(true); .should.equal(true)
}); })
return it("should call the callback", function() { return it('should call the callback', function() {
return this.callback.called.should.equal(true); return this.callback.called.should.equal(true)
}); })
}); })
describe("syncResourcesToDisk on an incremental update", function() { describe('syncResourcesToDisk on an incremental update', function() {
beforeEach(function() { beforeEach(function() {
this.resources = [ this.resources = ['resource-1-mock']
"resource-1-mock" this.ResourceWriter._writeResourceToDisk = sinon.stub().callsArg(3)
]; this.ResourceWriter._removeExtraneousFiles = sinon
this.ResourceWriter._writeResourceToDisk = sinon.stub().callsArg(3); .stub()
this.ResourceWriter._removeExtraneousFiles = sinon.stub().callsArgWith(2, null, (this.outputFiles = []), (this.allFiles = [])); .callsArgWith(2, null, (this.outputFiles = []), (this.allFiles = []))
this.ResourceStateManager.checkProjectStateMatches = sinon.stub().callsArgWith(2, null, this.resources); this.ResourceStateManager.checkProjectStateMatches = sinon
this.ResourceStateManager.saveProjectState = sinon.stub().callsArg(3); .stub()
this.ResourceStateManager.checkResourceFiles = sinon.stub().callsArg(3); .callsArgWith(2, null, this.resources)
return this.ResourceWriter.syncResourcesToDisk({ this.ResourceStateManager.saveProjectState = sinon.stub().callsArg(3)
project_id: this.project_id, this.ResourceStateManager.checkResourceFiles = sinon.stub().callsArg(3)
syncType: "incremental", return this.ResourceWriter.syncResourcesToDisk(
syncState: (this.syncState = "1234567890abcdef"), {
resources: this.resources project_id: this.project_id,
}, this.basePath, this.callback); syncType: 'incremental',
}); syncState: (this.syncState = '1234567890abcdef'),
resources: this.resources
},
this.basePath,
this.callback
)
})
it("should check the sync state matches", function() { it('should check the sync state matches', function() {
return this.ResourceStateManager.checkProjectStateMatches return this.ResourceStateManager.checkProjectStateMatches
.calledWith(this.syncState, this.basePath) .calledWith(this.syncState, this.basePath)
.should.equal(true); .should.equal(true)
}); })
it("should remove old files", function() { it('should remove old files', function() {
return this.ResourceWriter._removeExtraneousFiles return this.ResourceWriter._removeExtraneousFiles
.calledWith(this.resources, this.basePath) .calledWith(this.resources, this.basePath)
.should.equal(true); .should.equal(true)
}); })
it("should check each resource exists", function() { it('should check each resource exists', function() {
return this.ResourceStateManager.checkResourceFiles return this.ResourceStateManager.checkResourceFiles
.calledWith(this.resources, this.allFiles, this.basePath) .calledWith(this.resources, this.allFiles, this.basePath)
.should.equal(true); .should.equal(true)
}); })
it("should write each resource to disk", function() { it('should write each resource to disk', function() {
return Array.from(this.resources).map((resource) => return Array.from(this.resources).map(resource =>
this.ResourceWriter._writeResourceToDisk this.ResourceWriter._writeResourceToDisk
.calledWith(this.project_id, resource, this.basePath) .calledWith(this.project_id, resource, this.basePath)
.should.equal(true)); .should.equal(true)
}); )
})
return it("should call the callback", function() { return it('should call the callback', function() {
return this.callback.called.should.equal(true); return this.callback.called.should.equal(true)
}); })
}); })
describe("syncResourcesToDisk on an incremental update when the state does not match", function() { describe('syncResourcesToDisk on an incremental update when the state does not match', function() {
beforeEach(function() { beforeEach(function() {
this.resources = [ this.resources = ['resource-1-mock']
"resource-1-mock" this.ResourceStateManager.checkProjectStateMatches = sinon
]; .stub()
this.ResourceStateManager.checkProjectStateMatches = sinon.stub().callsArgWith(2, (this.error = new Error())); .callsArgWith(2, (this.error = new Error()))
return this.ResourceWriter.syncResourcesToDisk({ return this.ResourceWriter.syncResourcesToDisk(
project_id: this.project_id, {
syncType: "incremental", project_id: this.project_id,
syncState: (this.syncState = "1234567890abcdef"), syncType: 'incremental',
resources: this.resources syncState: (this.syncState = '1234567890abcdef'),
}, this.basePath, this.callback); resources: this.resources
}); },
this.basePath,
this.callback
)
})
it("should check whether the sync state matches", function() { it('should check whether the sync state matches', function() {
return this.ResourceStateManager.checkProjectStateMatches return this.ResourceStateManager.checkProjectStateMatches
.calledWith(this.syncState, this.basePath) .calledWith(this.syncState, this.basePath)
.should.equal(true); .should.equal(true)
}); })
return it("should call the callback with an error", function() { return it('should call the callback with an error', function() {
return this.callback.calledWith(this.error).should.equal(true); return this.callback.calledWith(this.error).should.equal(true)
}); })
}); })
describe('_removeExtraneousFiles', function() {
beforeEach(function() {
this.output_files = [
{
path: 'output.pdf',
type: 'pdf'
},
{
path: 'extra/file.tex',
type: 'tex'
},
{
path: 'extra.aux',
type: 'aux'
},
{
path: 'cache/_chunk1'
},
{
path: 'figures/image-eps-converted-to.pdf',
type: 'pdf'
},
{
path: 'foo/main-figure0.md5',
type: 'md5'
},
{
path: 'foo/main-figure0.dpth',
type: 'dpth'
},
{
path: 'foo/main-figure0.pdf',
type: 'pdf'
},
{
path: '_minted-main/default-pyg-prefix.pygstyle',
type: 'pygstyle'
},
{
path: '_minted-main/default.pygstyle',
type: 'pygstyle'
},
{
path:
'_minted-main/35E248B60965545BD232AE9F0FE9750D504A7AF0CD3BAA7542030FC560DFCC45.pygtex',
type: 'pygtex'
},
{
path: '_markdown_main/30893013dec5d869a415610079774c2f.md.tex',
type: 'tex'
}
]
this.resources = 'mock-resources'
this.OutputFileFinder.findOutputFiles = sinon
.stub()
.callsArgWith(2, null, this.output_files)
this.ResourceWriter._deleteFileIfNotDirectory = sinon.stub().callsArg(1)
return this.ResourceWriter._removeExtraneousFiles(
this.resources,
this.basePath,
this.callback
)
})
describe("_removeExtraneousFiles", function() { it('should find the existing output files', function() {
beforeEach(function() { return this.OutputFileFinder.findOutputFiles
this.output_files = [{ .calledWith(this.resources, this.basePath)
path: "output.pdf", .should.equal(true)
type: "pdf" })
}, {
path: "extra/file.tex",
type: "tex"
}, {
path: "extra.aux",
type: "aux"
}, {
path: "cache/_chunk1"
},{
path: "figures/image-eps-converted-to.pdf",
type: "pdf"
},{
path: "foo/main-figure0.md5",
type: "md5"
}, {
path: "foo/main-figure0.dpth",
type: "dpth"
}, {
path: "foo/main-figure0.pdf",
type: "pdf"
}, {
path: "_minted-main/default-pyg-prefix.pygstyle",
type: "pygstyle"
}, {
path: "_minted-main/default.pygstyle",
type: "pygstyle"
}, {
path: "_minted-main/35E248B60965545BD232AE9F0FE9750D504A7AF0CD3BAA7542030FC560DFCC45.pygtex",
type: "pygtex"
}, {
path: "_markdown_main/30893013dec5d869a415610079774c2f.md.tex",
type: "tex"
}];
this.resources = "mock-resources";
this.OutputFileFinder.findOutputFiles = sinon.stub().callsArgWith(2, null, this.output_files);
this.ResourceWriter._deleteFileIfNotDirectory = sinon.stub().callsArg(1);
return this.ResourceWriter._removeExtraneousFiles(this.resources, this.basePath, this.callback);
});
it("should find the existing output files", function() { it('should delete the output files', function() {
return this.OutputFileFinder.findOutputFiles return this.ResourceWriter._deleteFileIfNotDirectory
.calledWith(this.resources, this.basePath) .calledWith(path.join(this.basePath, 'output.pdf'))
.should.equal(true); .should.equal(true)
}); })
it("should delete the output files", function() { it('should delete the extra files', function() {
return this.ResourceWriter._deleteFileIfNotDirectory return this.ResourceWriter._deleteFileIfNotDirectory
.calledWith(path.join(this.basePath, "output.pdf")) .calledWith(path.join(this.basePath, 'extra/file.tex'))
.should.equal(true); .should.equal(true)
}); })
it("should delete the extra files", function() { it('should not delete the extra aux files', function() {
return this.ResourceWriter._deleteFileIfNotDirectory return this.ResourceWriter._deleteFileIfNotDirectory
.calledWith(path.join(this.basePath, "extra/file.tex")) .calledWith(path.join(this.basePath, 'extra.aux'))
.should.equal(true); .should.equal(false)
}); })
it("should not delete the extra aux files", function() { it('should not delete the knitr cache file', function() {
return this.ResourceWriter._deleteFileIfNotDirectory return this.ResourceWriter._deleteFileIfNotDirectory
.calledWith(path.join(this.basePath, "extra.aux")) .calledWith(path.join(this.basePath, 'cache/_chunk1'))
.should.equal(false); .should.equal(false)
}); })
it("should not delete the knitr cache file", function() {
return this.ResourceWriter._deleteFileIfNotDirectory
.calledWith(path.join(this.basePath, "cache/_chunk1"))
.should.equal(false);
});
it("should not delete the epstopdf converted files", function() { it('should not delete the epstopdf converted files', function() {
return this.ResourceWriter._deleteFileIfNotDirectory return this.ResourceWriter._deleteFileIfNotDirectory
.calledWith(path.join(this.basePath, "figures/image-eps-converted-to.pdf")) .calledWith(
.should.equal(false); path.join(this.basePath, 'figures/image-eps-converted-to.pdf')
}); )
.should.equal(false)
})
it("should not delete the tikz md5 files", function() { it('should not delete the tikz md5 files', function() {
return this.ResourceWriter._deleteFileIfNotDirectory return this.ResourceWriter._deleteFileIfNotDirectory
.calledWith(path.join(this.basePath, "foo/main-figure0.md5")) .calledWith(path.join(this.basePath, 'foo/main-figure0.md5'))
.should.equal(false); .should.equal(false)
}); })
it("should not delete the tikz dpth files", function() { it('should not delete the tikz dpth files', function() {
return this.ResourceWriter._deleteFileIfNotDirectory return this.ResourceWriter._deleteFileIfNotDirectory
.calledWith(path.join(this.basePath, "foo/main-figure0.dpth")) .calledWith(path.join(this.basePath, 'foo/main-figure0.dpth'))
.should.equal(false); .should.equal(false)
}); })
it("should not delete the tikz pdf files", function() { it('should not delete the tikz pdf files', function() {
return this.ResourceWriter._deleteFileIfNotDirectory return this.ResourceWriter._deleteFileIfNotDirectory
.calledWith(path.join(this.basePath, "foo/main-figure0.pdf")) .calledWith(path.join(this.basePath, 'foo/main-figure0.pdf'))
.should.equal(false); .should.equal(false)
}); })
it("should not delete the minted pygstyle files", function() { it('should not delete the minted pygstyle files', function() {
return this.ResourceWriter._deleteFileIfNotDirectory return this.ResourceWriter._deleteFileIfNotDirectory
.calledWith(path.join(this.basePath, "_minted-main/default-pyg-prefix.pygstyle")) .calledWith(
.should.equal(false); path.join(this.basePath, '_minted-main/default-pyg-prefix.pygstyle')
}); )
.should.equal(false)
})
it("should not delete the minted default pygstyle files", function() { it('should not delete the minted default pygstyle files', function() {
return this.ResourceWriter._deleteFileIfNotDirectory return this.ResourceWriter._deleteFileIfNotDirectory
.calledWith(path.join(this.basePath, "_minted-main/default.pygstyle")) .calledWith(path.join(this.basePath, '_minted-main/default.pygstyle'))
.should.equal(false); .should.equal(false)
}); })
it("should not delete the minted default pygtex files", function() { it('should not delete the minted default pygtex files', function() {
return this.ResourceWriter._deleteFileIfNotDirectory return this.ResourceWriter._deleteFileIfNotDirectory
.calledWith(path.join(this.basePath, "_minted-main/35E248B60965545BD232AE9F0FE9750D504A7AF0CD3BAA7542030FC560DFCC45.pygtex")) .calledWith(
.should.equal(false); path.join(
}); this.basePath,
'_minted-main/35E248B60965545BD232AE9F0FE9750D504A7AF0CD3BAA7542030FC560DFCC45.pygtex'
)
)
.should.equal(false)
})
it("should not delete the markdown md.tex files", function() { it('should not delete the markdown md.tex files', function() {
return this.ResourceWriter._deleteFileIfNotDirectory return this.ResourceWriter._deleteFileIfNotDirectory
.calledWith(path.join(this.basePath, "_markdown_main/30893013dec5d869a415610079774c2f.md.tex")) .calledWith(
.should.equal(false); path.join(
}); this.basePath,
'_markdown_main/30893013dec5d869a415610079774c2f.md.tex'
)
)
.should.equal(false)
})
it("should call the callback", function() { it('should call the callback', function() {
return this.callback.called.should.equal(true); return this.callback.called.should.equal(true)
}); })
return it("should time the request", function() { return it('should time the request', function() {
return this.Metrics.Timer.prototype.done.called.should.equal(true); return this.Metrics.Timer.prototype.done.called.should.equal(true)
}); })
}); })
describe("_writeResourceToDisk", function() { describe('_writeResourceToDisk', function() {
describe("with a url based resource", function() { describe('with a url based resource', function() {
beforeEach(function() { beforeEach(function() {
this.resource = { this.resource = {
path: "main.tex", path: 'main.tex',
url: "http://www.example.com/main.tex", url: 'http://www.example.com/main.tex',
modified: Date.now() modified: Date.now()
}; }
this.UrlCache.downloadUrlToFile = sinon.stub().callsArgWith(4, "fake error downloading file"); this.UrlCache.downloadUrlToFile = sinon
return this.ResourceWriter._writeResourceToDisk(this.project_id, this.resource, this.basePath, this.callback); .stub()
}); .callsArgWith(4, 'fake error downloading file')
return this.ResourceWriter._writeResourceToDisk(
this.project_id,
this.resource,
this.basePath,
this.callback
)
})
it("should ensure the directory exists", function() { it('should ensure the directory exists', function() {
return this.mkdirp return this.mkdirp
.calledWith(path.dirname(path.join(this.basePath, this.resource.path))) .calledWith(
.should.equal(true); path.dirname(path.join(this.basePath, this.resource.path))
}); )
.should.equal(true)
})
it("should write the URL from the cache", function() { it('should write the URL from the cache', function() {
return this.UrlCache.downloadUrlToFile return this.UrlCache.downloadUrlToFile
.calledWith(this.project_id, this.resource.url, path.join(this.basePath, this.resource.path), this.resource.modified) .calledWith(
.should.equal(true); this.project_id,
}); this.resource.url,
path.join(this.basePath, this.resource.path),
it("should call the callback", function() { this.resource.modified
return this.callback.called.should.equal(true); )
}); .should.equal(true)
})
return it("should not return an error if the resource writer errored", function() { it('should call the callback', function() {
return should.not.exist(this.callback.args[0][0]); return this.callback.called.should.equal(true)
}); })
});
describe("with a content based resource", function() { return it('should not return an error if the resource writer errored', function() {
beforeEach(function() { return should.not.exist(this.callback.args[0][0])
this.resource = { })
path: "main.tex", })
content: "Hello world"
};
this.fs.writeFile = sinon.stub().callsArg(2);
return this.ResourceWriter._writeResourceToDisk(this.project_id, this.resource, this.basePath, this.callback);
});
it("should ensure the directory exists", function() { describe('with a content based resource', function() {
return this.mkdirp beforeEach(function() {
.calledWith(path.dirname(path.join(this.basePath, this.resource.path))) this.resource = {
.should.equal(true); path: 'main.tex',
}); content: 'Hello world'
}
this.fs.writeFile = sinon.stub().callsArg(2)
return this.ResourceWriter._writeResourceToDisk(
this.project_id,
this.resource,
this.basePath,
this.callback
)
})
it("should write the contents to disk", function() { it('should ensure the directory exists', function() {
return this.fs.writeFile return this.mkdirp
.calledWith(path.join(this.basePath, this.resource.path), this.resource.content) .calledWith(
.should.equal(true); path.dirname(path.join(this.basePath, this.resource.path))
}); )
.should.equal(true)
return it("should call the callback", function() { })
return this.callback.called.should.equal(true);
});
});
return describe("with a file path that breaks out of the root folder", function() { it('should write the contents to disk', function() {
beforeEach(function() { return this.fs.writeFile
this.resource = { .calledWith(
path: "../../main.tex", path.join(this.basePath, this.resource.path),
content: "Hello world" this.resource.content
}; )
this.fs.writeFile = sinon.stub().callsArg(2); .should.equal(true)
return this.ResourceWriter._writeResourceToDisk(this.project_id, this.resource, this.basePath, this.callback); })
});
it("should not write to disk", function() { return it('should call the callback', function() {
return this.fs.writeFile.called.should.equal(false); return this.callback.called.should.equal(true)
}); })
})
return it("should return an error", function() { return describe('with a file path that breaks out of the root folder', function() {
return this.callback beforeEach(function() {
.calledWith(new Error("resource path is outside root directory")) this.resource = {
.should.equal(true); path: '../../main.tex',
}); content: 'Hello world'
}); }
}); this.fs.writeFile = sinon.stub().callsArg(2)
return this.ResourceWriter._writeResourceToDisk(
return describe("checkPath", function() { this.project_id,
describe("with a valid path", function() { this.resource,
beforeEach(function() { this.basePath,
return this.ResourceWriter.checkPath("foo", "bar", this.callback); this.callback
}); )
})
return it("should return the joined path", function() { it('should not write to disk', function() {
return this.callback.calledWith(null, "foo/bar") return this.fs.writeFile.called.should.equal(false)
.should.equal(true); })
});
});
describe("with an invalid path", function() { return it('should return an error', function() {
beforeEach(function() { return this.callback
return this.ResourceWriter.checkPath("foo", "baz/../../bar", this.callback); .calledWith(new Error('resource path is outside root directory'))
}); .should.equal(true)
})
})
})
return it("should return an error", function() { return describe('checkPath', function() {
return this.callback.calledWith(new Error("resource path is outside root directory")) describe('with a valid path', function() {
.should.equal(true); beforeEach(function() {
}); return this.ResourceWriter.checkPath('foo', 'bar', this.callback)
}); })
return describe("with another invalid path matching on a prefix", function() { return it('should return the joined path', function() {
beforeEach(function() { return this.callback.calledWith(null, 'foo/bar').should.equal(true)
return this.ResourceWriter.checkPath("foo", "../foobar/baz", this.callback); })
}); })
return it("should return an error", function() { describe('with an invalid path', function() {
return this.callback.calledWith(new Error("resource path is outside root directory")) beforeEach(function() {
.should.equal(true); return this.ResourceWriter.checkPath(
}); 'foo',
}); 'baz/../../bar',
}); this.callback
}); )
})
return it('should return an error', function() {
return this.callback
.calledWith(new Error('resource path is outside root directory'))
.should.equal(true)
})
})
return describe('with another invalid path matching on a prefix', function() {
beforeEach(function() {
return this.ResourceWriter.checkPath(
'foo',
'../foobar/baz',
this.callback
)
})
return it('should return an error', function() {
return this.callback
.calledWith(new Error('resource path is outside root directory'))
.should.equal(true)
})
})
})
})

View file

@ -9,217 +9,229 @@
* DS102: Remove unnecessary code created because of implicit returns * DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/ */
const should = require('chai').should(); const should = require('chai').should()
const SandboxedModule = require('sandboxed-module'); const SandboxedModule = require('sandboxed-module')
const assert = require('assert'); const assert = require('assert')
const path = require('path'); const path = require('path')
const sinon = require('sinon'); const sinon = require('sinon')
const modulePath = path.join(__dirname, "../../../app/js/StaticServerForbidSymlinks"); const modulePath = path.join(
const { expect } = require("chai"); __dirname,
'../../../app/js/StaticServerForbidSymlinks'
)
const { expect } = require('chai')
describe("StaticServerForbidSymlinks", function() { describe('StaticServerForbidSymlinks', function() {
beforeEach(function() {
this.settings = {
path: {
compilesDir: '/compiles/here'
}
}
beforeEach(function() { this.fs = {}
this.ForbidSymlinks = SandboxedModule.require(modulePath, {
requires: {
'settings-sharelatex': this.settings,
'logger-sharelatex': {
log() {},
warn() {},
error() {}
},
fs: this.fs
}
})
this.settings = { this.dummyStatic = (rootDir, options) => (req, res, next) =>
path: { // console.log "dummyStatic serving file", rootDir, "called with", req.url
compilesDir: "/compiles/here" // serve it
} next()
};
this.fs = {}; this.StaticServerForbidSymlinks = this.ForbidSymlinks(
this.ForbidSymlinks = SandboxedModule.require(modulePath, { requires: { this.dummyStatic,
"settings-sharelatex":this.settings, this.settings.path.compilesDir
"logger-sharelatex": { )
log() {}, this.req = {
warn() {}, params: {
error() {} project_id: '12345'
}, }
"fs":this.fs }
}
}
);
this.dummyStatic = (rootDir, options) => this.res = {}
(req, res, next) => return (this.req.url = '/12345/output.pdf')
// console.log "dummyStatic serving file", rootDir, "called with", req.url })
// serve it
next()
;
this.StaticServerForbidSymlinks = this.ForbidSymlinks(this.dummyStatic, this.settings.path.compilesDir); describe('sending a normal file through', function() {
this.req = { beforeEach(function() {
params: { return (this.fs.realpath = sinon
project_id:"12345" .stub()
} .callsArgWith(
}; 1,
null,
`${this.settings.path.compilesDir}/${this.req.params.project_id}/output.pdf`
))
})
this.res = {}; return it('should call next', function(done) {
return this.req.url = "/12345/output.pdf"; this.res.sendStatus = function(resCode) {
}); resCode.should.equal(200)
return done()
}
return this.StaticServerForbidSymlinks(this.req, this.res, done)
})
})
describe('with a missing file', function() {
beforeEach(function() {
return (this.fs.realpath = sinon
.stub()
.callsArgWith(
1,
{ code: 'ENOENT' },
`${this.settings.path.compilesDir}/${this.req.params.project_id}/unknown.pdf`
))
})
describe("sending a normal file through", function() { return it('should send a 404', function(done) {
beforeEach(function() { this.res.sendStatus = function(resCode) {
return this.fs.realpath = sinon.stub().callsArgWith(1, null, `${this.settings.path.compilesDir}/${this.req.params.project_id}/output.pdf`); resCode.should.equal(404)
}); return done()
}
return this.StaticServerForbidSymlinks(this.req, this.res)
})
})
return it("should call next", function(done){ describe('with a symlink file', function() {
this.res.sendStatus = function(resCode){ beforeEach(function() {
resCode.should.equal(200); return (this.fs.realpath = sinon
return done(); .stub()
}; .callsArgWith(1, null, `/etc/${this.req.params.project_id}/output.pdf`))
return this.StaticServerForbidSymlinks(this.req, this.res, done); })
});
});
return it('should send a 404', function(done) {
this.res.sendStatus = function(resCode) {
resCode.should.equal(404)
return done()
}
return this.StaticServerForbidSymlinks(this.req, this.res)
})
})
describe("with a missing file", function() { describe('with a relative file', function() {
beforeEach(function() { beforeEach(function() {
return this.fs.realpath = sinon.stub().callsArgWith(1, {code: 'ENOENT'}, `${this.settings.path.compilesDir}/${this.req.params.project_id}/unknown.pdf`); return (this.req.url = '/12345/../67890/output.pdf')
}); })
return it("should send a 404", function(done){ return it('should send a 404', function(done) {
this.res.sendStatus = function(resCode){ this.res.sendStatus = function(resCode) {
resCode.should.equal(404); resCode.should.equal(404)
return done(); return done()
}; }
return this.StaticServerForbidSymlinks(this.req, this.res); return this.StaticServerForbidSymlinks(this.req, this.res)
}); })
}); })
describe('with a unnormalized file containing .', function() {
beforeEach(function() {
return (this.req.url = '/12345/foo/./output.pdf')
})
describe("with a symlink file", function() { return it('should send a 404', function(done) {
beforeEach(function() { this.res.sendStatus = function(resCode) {
return this.fs.realpath = sinon.stub().callsArgWith(1, null, `/etc/${this.req.params.project_id}/output.pdf`); resCode.should.equal(404)
}); return done()
}
return this.StaticServerForbidSymlinks(this.req, this.res)
})
})
return it("should send a 404", function(done){ describe('with a file containing an empty path', function() {
this.res.sendStatus = function(resCode){ beforeEach(function() {
resCode.should.equal(404); return (this.req.url = '/12345/foo//output.pdf')
return done(); })
};
return this.StaticServerForbidSymlinks(this.req, this.res);
});
});
return it('should send a 404', function(done) {
this.res.sendStatus = function(resCode) {
resCode.should.equal(404)
return done()
}
return this.StaticServerForbidSymlinks(this.req, this.res)
})
})
describe("with a relative file", function() { describe('with a non-project file', function() {
beforeEach(function() { beforeEach(function() {
return this.req.url = "/12345/../67890/output.pdf"; return (this.req.url = '/.foo/output.pdf')
}); })
return it("should send a 404", function(done){ return it('should send a 404', function(done) {
this.res.sendStatus = function(resCode){ this.res.sendStatus = function(resCode) {
resCode.should.equal(404); resCode.should.equal(404)
return done(); return done()
}; }
return this.StaticServerForbidSymlinks(this.req, this.res); return this.StaticServerForbidSymlinks(this.req, this.res)
}); })
}); })
describe('with a file outside the compiledir', function() {
beforeEach(function() {
return (this.req.url = '/../bar/output.pdf')
})
describe("with a unnormalized file containing .", function() { return it('should send a 404', function(done) {
beforeEach(function() { this.res.sendStatus = function(resCode) {
return this.req.url = "/12345/foo/./output.pdf"; resCode.should.equal(404)
}); return done()
}
return this.StaticServerForbidSymlinks(this.req, this.res)
})
})
return it("should send a 404", function(done){ describe('with a file with no leading /', function() {
this.res.sendStatus = function(resCode){ beforeEach(function() {
resCode.should.equal(404); return (this.req.url = './../bar/output.pdf')
return done(); })
};
return this.StaticServerForbidSymlinks(this.req, this.res);
});
});
return it('should send a 404', function(done) {
this.res.sendStatus = function(resCode) {
resCode.should.equal(404)
return done()
}
return this.StaticServerForbidSymlinks(this.req, this.res)
})
})
describe("with a file containing an empty path", function() { describe('with a github style path', function() {
beforeEach(function() { beforeEach(function() {
return this.req.url = "/12345/foo//output.pdf"; this.req.url = '/henryoswald-latex_example/output/output.log'
}); return (this.fs.realpath = sinon
.stub()
.callsArgWith(
1,
null,
`${this.settings.path.compilesDir}/henryoswald-latex_example/output/output.log`
))
})
return it("should send a 404", function(done){ return it('should call next', function(done) {
this.res.sendStatus = function(resCode){ this.res.sendStatus = function(resCode) {
resCode.should.equal(404); resCode.should.equal(200)
return done(); return done()
}; }
return this.StaticServerForbidSymlinks(this.req, this.res); return this.StaticServerForbidSymlinks(this.req, this.res, done)
}); })
}); })
describe("with a non-project file", function() { return describe('with an error from fs.realpath', function() {
beforeEach(function() { beforeEach(function() {
return this.req.url = "/.foo/output.pdf"; return (this.fs.realpath = sinon.stub().callsArgWith(1, 'error'))
}); })
return it("should send a 404", function(done){
this.res.sendStatus = function(resCode){
resCode.should.equal(404);
return done();
};
return this.StaticServerForbidSymlinks(this.req, this.res);
});
});
describe("with a file outside the compiledir", function() {
beforeEach(function() {
return this.req.url = "/../bar/output.pdf";
});
return it("should send a 404", function(done){
this.res.sendStatus = function(resCode){
resCode.should.equal(404);
return done();
};
return this.StaticServerForbidSymlinks(this.req, this.res);
});
});
describe("with a file with no leading /", function() {
beforeEach(function() {
return this.req.url = "./../bar/output.pdf";
});
return it("should send a 404", function(done){
this.res.sendStatus = function(resCode){
resCode.should.equal(404);
return done();
};
return this.StaticServerForbidSymlinks(this.req, this.res);
});
});
describe("with a github style path", function() {
beforeEach(function() {
this.req.url = "/henryoswald-latex_example/output/output.log";
return this.fs.realpath = sinon.stub().callsArgWith(1, null, `${this.settings.path.compilesDir}/henryoswald-latex_example/output/output.log`);
});
return it("should call next", function(done){
this.res.sendStatus = function(resCode){
resCode.should.equal(200);
return done();
};
return this.StaticServerForbidSymlinks(this.req, this.res, done);
});
});
return describe("with an error from fs.realpath", function() {
beforeEach(function() {
return this.fs.realpath = sinon.stub().callsArgWith(1, "error");
});
return it("should send a 500", function(done){
this.res.sendStatus = function(resCode){
resCode.should.equal(500);
return done();
};
return this.StaticServerForbidSymlinks(this.req, this.res);
});
});
});
return it('should send a 500', function(done) {
this.res.sendStatus = function(resCode) {
resCode.should.equal(500)
return done()
}
return this.StaticServerForbidSymlinks(this.req, this.res)
})
})
})

View file

@ -8,148 +8,180 @@
* DS102: Remove unnecessary code created because of implicit returns * DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/ */
const SandboxedModule = require('sandboxed-module'); const SandboxedModule = require('sandboxed-module')
const sinon = require('sinon'); const sinon = require('sinon')
require('chai').should(); require('chai').should()
const modulePath = require('path').join(__dirname, '../../../app/js/TikzManager'); const modulePath = require('path').join(
__dirname,
'../../../app/js/TikzManager'
)
describe('TikzManager', function() { describe('TikzManager', function() {
beforeEach(function() { beforeEach(function() {
return this.TikzManager = SandboxedModule.require(modulePath, { requires: { return (this.TikzManager = SandboxedModule.require(modulePath, {
"./ResourceWriter": (this.ResourceWriter = {}), requires: {
"./SafeReader": (this.SafeReader = {}), './ResourceWriter': (this.ResourceWriter = {}),
"fs": (this.fs = {}), './SafeReader': (this.SafeReader = {}),
"logger-sharelatex": (this.logger = {log() {}}) fs: (this.fs = {}),
} 'logger-sharelatex': (this.logger = { log() {} })
});}); }
}))
})
describe("checkMainFile", function() { describe('checkMainFile', function() {
beforeEach(function() { beforeEach(function() {
this.compileDir = "compile-dir"; this.compileDir = 'compile-dir'
this.mainFile = "main.tex"; this.mainFile = 'main.tex'
return this.callback = sinon.stub(); return (this.callback = sinon.stub())
}); })
describe("if there is already an output.tex file in the resources", function() { describe('if there is already an output.tex file in the resources', function() {
beforeEach(function() { beforeEach(function() {
this.resources = [{path:"main.tex"},{path:"output.tex"}]; this.resources = [{ path: 'main.tex' }, { path: 'output.tex' }]
return this.TikzManager.checkMainFile(this.compileDir, this.mainFile, this.resources, this.callback); return this.TikzManager.checkMainFile(
}); this.compileDir,
this.mainFile,
this.resources,
this.callback
)
})
return it("should call the callback with false ", function() { return it('should call the callback with false ', function() {
return this.callback.calledWithExactly(null, false) return this.callback.calledWithExactly(null, false).should.equal(true)
.should.equal(true); })
}); })
});
return describe("if there is no output.tex file in the resources", function() { return describe('if there is no output.tex file in the resources', function() {
beforeEach(function() { beforeEach(function() {
this.resources = [{path:"main.tex"}]; this.resources = [{ path: 'main.tex' }]
return this.ResourceWriter.checkPath = sinon.stub() return (this.ResourceWriter.checkPath = sinon
.withArgs(this.compileDir, this.mainFile) .stub()
.callsArgWith(2, null, `${this.compileDir}/${this.mainFile}`); .withArgs(this.compileDir, this.mainFile)
}); .callsArgWith(2, null, `${this.compileDir}/${this.mainFile}`))
})
describe("and the main file contains tikzexternalize", function() { describe('and the main file contains tikzexternalize', function() {
beforeEach(function() { beforeEach(function() {
this.SafeReader.readFile = sinon.stub() this.SafeReader.readFile = sinon
.withArgs(`${this.compileDir}/${this.mainFile}`) .stub()
.callsArgWith(3, null, "hello \\tikzexternalize"); .withArgs(`${this.compileDir}/${this.mainFile}`)
return this.TikzManager.checkMainFile(this.compileDir, this.mainFile, this.resources, this.callback); .callsArgWith(3, null, 'hello \\tikzexternalize')
}); return this.TikzManager.checkMainFile(
this.compileDir,
this.mainFile,
this.resources,
this.callback
)
})
it("should look at the file on disk", function() { it('should look at the file on disk', function() {
return this.SafeReader.readFile return this.SafeReader.readFile
.calledWith(`${this.compileDir}/${this.mainFile}`) .calledWith(`${this.compileDir}/${this.mainFile}`)
.should.equal(true); .should.equal(true)
}); })
return it("should call the callback with true ", function() { return it('should call the callback with true ', function() {
return this.callback.calledWithExactly(null, true) return this.callback.calledWithExactly(null, true).should.equal(true)
.should.equal(true); })
}); })
});
describe("and the main file does not contain tikzexternalize", function() { describe('and the main file does not contain tikzexternalize', function() {
beforeEach(function() { beforeEach(function() {
this.SafeReader.readFile = sinon.stub() this.SafeReader.readFile = sinon
.withArgs(`${this.compileDir}/${this.mainFile}`) .stub()
.callsArgWith(3, null, "hello"); .withArgs(`${this.compileDir}/${this.mainFile}`)
return this.TikzManager.checkMainFile(this.compileDir, this.mainFile, this.resources, this.callback); .callsArgWith(3, null, 'hello')
}); return this.TikzManager.checkMainFile(
this.compileDir,
this.mainFile,
this.resources,
this.callback
)
})
it("should look at the file on disk", function() { it('should look at the file on disk', function() {
return this.SafeReader.readFile return this.SafeReader.readFile
.calledWith(`${this.compileDir}/${this.mainFile}`) .calledWith(`${this.compileDir}/${this.mainFile}`)
.should.equal(true); .should.equal(true)
}); })
return it("should call the callback with false", function() { return it('should call the callback with false', function() {
return this.callback.calledWithExactly(null, false) return this.callback.calledWithExactly(null, false).should.equal(true)
.should.equal(true); })
}); })
});
return describe("and the main file contains \\usepackage{pstool}", function() { return describe('and the main file contains \\usepackage{pstool}', function() {
beforeEach(function() { beforeEach(function() {
this.SafeReader.readFile = sinon.stub() this.SafeReader.readFile = sinon
.withArgs(`${this.compileDir}/${this.mainFile}`) .stub()
.callsArgWith(3, null, "hello \\usepackage[random-options]{pstool}"); .withArgs(`${this.compileDir}/${this.mainFile}`)
return this.TikzManager.checkMainFile(this.compileDir, this.mainFile, this.resources, this.callback); .callsArgWith(3, null, 'hello \\usepackage[random-options]{pstool}')
}); return this.TikzManager.checkMainFile(
this.compileDir,
this.mainFile,
this.resources,
this.callback
)
})
it("should look at the file on disk", function() { it('should look at the file on disk', function() {
return this.SafeReader.readFile return this.SafeReader.readFile
.calledWith(`${this.compileDir}/${this.mainFile}`) .calledWith(`${this.compileDir}/${this.mainFile}`)
.should.equal(true); .should.equal(true)
}); })
return it("should call the callback with true ", function() { return it('should call the callback with true ', function() {
return this.callback.calledWithExactly(null, true) return this.callback.calledWithExactly(null, true).should.equal(true)
.should.equal(true); })
}); })
}); })
}); })
});
return describe("injectOutputFile", function() { return describe('injectOutputFile', function() {
beforeEach(function() { beforeEach(function() {
this.rootDir = "/mock"; this.rootDir = '/mock'
this.filename = "filename.tex"; this.filename = 'filename.tex'
this.callback = sinon.stub(); this.callback = sinon.stub()
this.content = `\ this.content = `\
\\documentclass{article} \\documentclass{article}
\\usepackage{tikz} \\usepackage{tikz}
\\tikzexternalize \\tikzexternalize
\\begin{document} \\begin{document}
Hello world Hello world
\\end{document}\ \\end{document}\
`; `
this.fs.readFile = sinon.stub().callsArgWith(2, null, this.content); this.fs.readFile = sinon.stub().callsArgWith(2, null, this.content)
this.fs.writeFile = sinon.stub().callsArg(3); this.fs.writeFile = sinon.stub().callsArg(3)
this.ResourceWriter.checkPath = sinon.stub().callsArgWith(2, null, `${this.rootDir}/${this.filename}`); this.ResourceWriter.checkPath = sinon
return this.TikzManager.injectOutputFile(this.rootDir, this.filename, this.callback); .stub()
}); .callsArgWith(2, null, `${this.rootDir}/${this.filename}`)
return this.TikzManager.injectOutputFile(
this.rootDir,
this.filename,
this.callback
)
})
it("sould check the path", function() { it('sould check the path', function() {
return this.ResourceWriter.checkPath.calledWith(this.rootDir, this.filename) return this.ResourceWriter.checkPath
.should.equal(true); .calledWith(this.rootDir, this.filename)
}); .should.equal(true)
})
it("should read the file", function() { it('should read the file', function() {
return this.fs.readFile return this.fs.readFile
.calledWith(`${this.rootDir}/${this.filename}`, "utf8") .calledWith(`${this.rootDir}/${this.filename}`, 'utf8')
.should.equal(true); .should.equal(true)
}); })
it("should write out the same file as output.tex", function() { it('should write out the same file as output.tex', function() {
return this.fs.writeFile return this.fs.writeFile
.calledWith(`${this.rootDir}/output.tex`, this.content, {flag: 'wx'}) .calledWith(`${this.rootDir}/output.tex`, this.content, { flag: 'wx' })
.should.equal(true); .should.equal(true)
}); })
return it("should call the callback", function() { return it('should call the callback', function() {
return this.callback.called.should.equal(true); return this.callback.called.should.equal(true)
}); })
}); })
}); })

View file

@ -10,259 +10,347 @@
* DS102: Remove unnecessary code created because of implicit returns * DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/ */
const SandboxedModule = require('sandboxed-module'); const SandboxedModule = require('sandboxed-module')
const sinon = require('sinon'); const sinon = require('sinon')
require('chai').should(); require('chai').should()
const modulePath = require('path').join(__dirname, '../../../app/js/UrlCache'); const modulePath = require('path').join(__dirname, '../../../app/js/UrlCache')
const { EventEmitter } = require("events"); const { EventEmitter } = require('events')
describe("UrlCache", function() { describe('UrlCache', function() {
beforeEach(function() { beforeEach(function() {
this.callback = sinon.stub(); this.callback = sinon.stub()
this.url = "www.example.com/file"; this.url = 'www.example.com/file'
this.project_id = "project-id-123"; this.project_id = 'project-id-123'
return this.UrlCache = SandboxedModule.require(modulePath, { requires: { return (this.UrlCache = SandboxedModule.require(modulePath, {
"./db" : {}, requires: {
"./UrlFetcher" : (this.UrlFetcher = {}), './db': {},
"logger-sharelatex": (this.logger = {log: sinon.stub()}), './UrlFetcher': (this.UrlFetcher = {}),
"settings-sharelatex": (this.Settings = { path: {clsiCacheDir: "/cache/dir"} }), 'logger-sharelatex': (this.logger = { log: sinon.stub() }),
"fs": (this.fs = {}) 'settings-sharelatex': (this.Settings = {
} path: { clsiCacheDir: '/cache/dir' }
});}); }),
fs: (this.fs = {})
describe("_doesUrlNeedDownloading", function() { }
beforeEach(function() { }))
this.lastModified = new Date(); })
return this.lastModifiedRoundedToSeconds = new Date(Math.floor(this.lastModified.getTime() / 1000) * 1000);
});
describe("when URL does not exist in cache", function() { describe('_doesUrlNeedDownloading', function() {
beforeEach(function() { beforeEach(function() {
this.UrlCache._findUrlDetails = sinon.stub().callsArgWith(2, null, null); this.lastModified = new Date()
return this.UrlCache._doesUrlNeedDownloading(this.project_id, this.url, this.lastModified, this.callback); return (this.lastModifiedRoundedToSeconds = new Date(
}); Math.floor(this.lastModified.getTime() / 1000) * 1000
))
})
return it("should return the callback with true", function() { describe('when URL does not exist in cache', function() {
return this.callback.calledWith(null, true).should.equal(true); beforeEach(function() {
}); this.UrlCache._findUrlDetails = sinon.stub().callsArgWith(2, null, null)
}); return this.UrlCache._doesUrlNeedDownloading(
this.project_id,
this.url,
this.lastModified,
this.callback
)
})
return describe("when URL does exist in cache", function() { return it('should return the callback with true', function() {
beforeEach(function() { return this.callback.calledWith(null, true).should.equal(true)
this.urlDetails = {}; })
return this.UrlCache._findUrlDetails = sinon.stub().callsArgWith(2, null, this.urlDetails); })
});
describe("when the modified date is more recent than the cached modified date", function() { return describe('when URL does exist in cache', function() {
beforeEach(function() { beforeEach(function() {
this.urlDetails.lastModified = new Date(this.lastModified.getTime() - 1000); this.urlDetails = {}
return this.UrlCache._doesUrlNeedDownloading(this.project_id, this.url, this.lastModified, this.callback); return (this.UrlCache._findUrlDetails = sinon
}); .stub()
.callsArgWith(2, null, this.urlDetails))
})
it("should get the url details", function() { describe('when the modified date is more recent than the cached modified date', function() {
return this.UrlCache._findUrlDetails beforeEach(function() {
.calledWith(this.project_id, this.url) this.urlDetails.lastModified = new Date(
.should.equal(true); this.lastModified.getTime() - 1000
}); )
return this.UrlCache._doesUrlNeedDownloading(
this.project_id,
this.url,
this.lastModified,
this.callback
)
})
return it("should return the callback with true", function() { it('should get the url details', function() {
return this.callback.calledWith(null, true).should.equal(true); return this.UrlCache._findUrlDetails
}); .calledWith(this.project_id, this.url)
}); .should.equal(true)
})
describe("when the cached modified date is more recent than the modified date", function() { return it('should return the callback with true', function() {
beforeEach(function() { return this.callback.calledWith(null, true).should.equal(true)
this.urlDetails.lastModified = new Date(this.lastModified.getTime() + 1000); })
return this.UrlCache._doesUrlNeedDownloading(this.project_id, this.url, this.lastModified, this.callback); })
});
return it("should return the callback with false", function() { describe('when the cached modified date is more recent than the modified date', function() {
return this.callback.calledWith(null, false).should.equal(true); beforeEach(function() {
}); this.urlDetails.lastModified = new Date(
}); this.lastModified.getTime() + 1000
)
return this.UrlCache._doesUrlNeedDownloading(
this.project_id,
this.url,
this.lastModified,
this.callback
)
})
describe("when the cached modified date is equal to the modified date", function() { return it('should return the callback with false', function() {
beforeEach(function() { return this.callback.calledWith(null, false).should.equal(true)
this.urlDetails.lastModified = this.lastModified; })
return this.UrlCache._doesUrlNeedDownloading(this.project_id, this.url, this.lastModified, this.callback); })
});
return it("should return the callback with false", function() { describe('when the cached modified date is equal to the modified date', function() {
return this.callback.calledWith(null, false).should.equal(true); beforeEach(function() {
}); this.urlDetails.lastModified = this.lastModified
}); return this.UrlCache._doesUrlNeedDownloading(
this.project_id,
this.url,
this.lastModified,
this.callback
)
})
describe("when the provided modified date does not exist", function() { return it('should return the callback with false', function() {
beforeEach(function() { return this.callback.calledWith(null, false).should.equal(true)
this.lastModified = null; })
return this.UrlCache._doesUrlNeedDownloading(this.project_id, this.url, this.lastModified, this.callback); })
});
return it("should return the callback with true", function() { describe('when the provided modified date does not exist', function() {
return this.callback.calledWith(null, true).should.equal(true); beforeEach(function() {
}); this.lastModified = null
}); return this.UrlCache._doesUrlNeedDownloading(
this.project_id,
this.url,
this.lastModified,
this.callback
)
})
return describe("when the URL does not have a modified date", function() { return it('should return the callback with true', function() {
beforeEach(function() { return this.callback.calledWith(null, true).should.equal(true)
this.urlDetails.lastModified = null; })
return this.UrlCache._doesUrlNeedDownloading(this.project_id, this.url, this.lastModified, this.callback); })
});
return it("should return the callback with true", function() { return describe('when the URL does not have a modified date', function() {
return this.callback.calledWith(null, true).should.equal(true); beforeEach(function() {
}); this.urlDetails.lastModified = null
}); return this.UrlCache._doesUrlNeedDownloading(
}); this.project_id,
}); this.url,
this.lastModified,
this.callback
)
})
describe("_ensureUrlIsInCache", function() { return it('should return the callback with true', function() {
beforeEach(function() { return this.callback.calledWith(null, true).should.equal(true)
this.UrlFetcher.pipeUrlToFile = sinon.stub().callsArg(2); })
return this.UrlCache._updateOrCreateUrlDetails = sinon.stub().callsArg(3); })
}); })
})
describe("when the URL needs updating", function() {
beforeEach(function() {
this.UrlCache._doesUrlNeedDownloading = sinon.stub().callsArgWith(3, null, true);
return this.UrlCache._ensureUrlIsInCache(this.project_id, this.url, this.lastModified, this.callback);
});
it("should check that the url needs downloading", function() { describe('_ensureUrlIsInCache', function() {
return this.UrlCache._doesUrlNeedDownloading beforeEach(function() {
.calledWith(this.project_id, this.url, this.lastModifiedRoundedToSeconds) this.UrlFetcher.pipeUrlToFile = sinon.stub().callsArg(2)
.should.equal(true); return (this.UrlCache._updateOrCreateUrlDetails = sinon
}); .stub()
.callsArg(3))
})
it("should download the URL to the cache file", function() { describe('when the URL needs updating', function() {
return this.UrlFetcher.pipeUrlToFile beforeEach(function() {
.calledWith(this.url, this.UrlCache._cacheFilePathForUrl(this.project_id, this.url)) this.UrlCache._doesUrlNeedDownloading = sinon
.should.equal(true); .stub()
}); .callsArgWith(3, null, true)
return this.UrlCache._ensureUrlIsInCache(
this.project_id,
this.url,
this.lastModified,
this.callback
)
})
it("should update the database entry", function() { it('should check that the url needs downloading', function() {
return this.UrlCache._updateOrCreateUrlDetails return this.UrlCache._doesUrlNeedDownloading
.calledWith(this.project_id, this.url, this.lastModifiedRoundedToSeconds) .calledWith(
.should.equal(true); this.project_id,
}); this.url,
this.lastModifiedRoundedToSeconds
)
.should.equal(true)
})
return it("should return the callback with the cache file path", function() { it('should download the URL to the cache file', function() {
return this.callback return this.UrlFetcher.pipeUrlToFile
.calledWith(null, this.UrlCache._cacheFilePathForUrl(this.project_id, this.url)) .calledWith(
.should.equal(true); this.url,
}); this.UrlCache._cacheFilePathForUrl(this.project_id, this.url)
}); )
.should.equal(true)
})
return describe("when the URL does not need updating", function() { it('should update the database entry', function() {
beforeEach(function() { return this.UrlCache._updateOrCreateUrlDetails
this.UrlCache._doesUrlNeedDownloading = sinon.stub().callsArgWith(3, null, false); .calledWith(
return this.UrlCache._ensureUrlIsInCache(this.project_id, this.url, this.lastModified, this.callback); this.project_id,
}); this.url,
this.lastModifiedRoundedToSeconds
it("should not download the URL to the cache file", function() { )
return this.UrlFetcher.pipeUrlToFile .should.equal(true)
.called.should.equal(false); })
});
return it("should return the callback with the cache file path", function() { return it('should return the callback with the cache file path', function() {
return this.callback return this.callback
.calledWith(null, this.UrlCache._cacheFilePathForUrl(this.project_id, this.url)) .calledWith(
.should.equal(true); null,
}); this.UrlCache._cacheFilePathForUrl(this.project_id, this.url)
}); )
}); .should.equal(true)
})
})
describe("downloadUrlToFile", function() { return describe('when the URL does not need updating', function() {
beforeEach(function() { beforeEach(function() {
this.cachePath = "path/to/cached/url"; this.UrlCache._doesUrlNeedDownloading = sinon
this.destPath = "path/to/destination"; .stub()
this.UrlCache._copyFile = sinon.stub().callsArg(2); .callsArgWith(3, null, false)
this.UrlCache._ensureUrlIsInCache = sinon.stub().callsArgWith(3, null, this.cachePath); return this.UrlCache._ensureUrlIsInCache(
return this.UrlCache.downloadUrlToFile(this.project_id, this.url, this.destPath, this.lastModified, this.callback); this.project_id,
}); this.url,
this.lastModified,
this.callback
)
})
it("should ensure the URL is downloaded and updated in the cache", function() { it('should not download the URL to the cache file', function() {
return this.UrlCache._ensureUrlIsInCache return this.UrlFetcher.pipeUrlToFile.called.should.equal(false)
.calledWith(this.project_id, this.url, this.lastModified) })
.should.equal(true);
});
it("should copy the file to the new location", function() { return it('should return the callback with the cache file path', function() {
return this.UrlCache._copyFile return this.callback
.calledWith(this.cachePath, this.destPath) .calledWith(
.should.equal(true); null,
}); this.UrlCache._cacheFilePathForUrl(this.project_id, this.url)
)
.should.equal(true)
})
})
})
return it("should call the callback", function() { describe('downloadUrlToFile', function() {
return this.callback.called.should.equal(true); beforeEach(function() {
}); this.cachePath = 'path/to/cached/url'
}); this.destPath = 'path/to/destination'
this.UrlCache._copyFile = sinon.stub().callsArg(2)
this.UrlCache._ensureUrlIsInCache = sinon
.stub()
.callsArgWith(3, null, this.cachePath)
return this.UrlCache.downloadUrlToFile(
this.project_id,
this.url,
this.destPath,
this.lastModified,
this.callback
)
})
describe("_deleteUrlCacheFromDisk", function() { it('should ensure the URL is downloaded and updated in the cache', function() {
beforeEach(function() { return this.UrlCache._ensureUrlIsInCache
this.fs.unlink = sinon.stub().callsArg(1); .calledWith(this.project_id, this.url, this.lastModified)
return this.UrlCache._deleteUrlCacheFromDisk(this.project_id, this.url, this.callback); .should.equal(true)
}); })
it("should delete the cache file", function() { it('should copy the file to the new location', function() {
return this.fs.unlink return this.UrlCache._copyFile
.calledWith(this.UrlCache._cacheFilePathForUrl(this.project_id, this.url)) .calledWith(this.cachePath, this.destPath)
.should.equal(true); .should.equal(true)
}); })
return it("should call the callback", function() { return it('should call the callback', function() {
return this.callback.called.should.equal(true); return this.callback.called.should.equal(true)
}); })
}); })
describe("_clearUrlFromCache", function() { describe('_deleteUrlCacheFromDisk', function() {
beforeEach(function() { beforeEach(function() {
this.UrlCache._deleteUrlCacheFromDisk = sinon.stub().callsArg(2); this.fs.unlink = sinon.stub().callsArg(1)
this.UrlCache._clearUrlDetails = sinon.stub().callsArg(2); return this.UrlCache._deleteUrlCacheFromDisk(
return this.UrlCache._clearUrlFromCache(this.project_id, this.url, this.callback); this.project_id,
}); this.url,
this.callback
)
})
it("should delete the file on disk", function() { it('should delete the cache file', function() {
return this.UrlCache._deleteUrlCacheFromDisk return this.fs.unlink
.calledWith(this.project_id, this.url) .calledWith(
.should.equal(true); this.UrlCache._cacheFilePathForUrl(this.project_id, this.url)
}); )
.should.equal(true)
})
it("should clear the entry in the database", function() { return it('should call the callback', function() {
return this.UrlCache._clearUrlDetails return this.callback.called.should.equal(true)
.calledWith(this.project_id, this.url) })
.should.equal(true); })
});
return it("should call the callback", function() { describe('_clearUrlFromCache', function() {
return this.callback.called.should.equal(true); beforeEach(function() {
}); this.UrlCache._deleteUrlCacheFromDisk = sinon.stub().callsArg(2)
}); this.UrlCache._clearUrlDetails = sinon.stub().callsArg(2)
return this.UrlCache._clearUrlFromCache(
this.project_id,
this.url,
this.callback
)
})
return describe("clearProject", function() { it('should delete the file on disk', function() {
beforeEach(function() { return this.UrlCache._deleteUrlCacheFromDisk
this.urls = [ .calledWith(this.project_id, this.url)
"www.example.com/file1", .should.equal(true)
"www.example.com/file2" })
];
this.UrlCache._findAllUrlsInProject = sinon.stub().callsArgWith(1, null, this.urls);
this.UrlCache._clearUrlFromCache = sinon.stub().callsArg(2);
return this.UrlCache.clearProject(this.project_id, this.callback);
});
it("should clear the cache for each url in the project", function() { it('should clear the entry in the database', function() {
return Array.from(this.urls).map((url) => return this.UrlCache._clearUrlDetails
this.UrlCache._clearUrlFromCache .calledWith(this.project_id, this.url)
.calledWith(this.project_id, url) .should.equal(true)
.should.equal(true)); })
});
return it("should call the callback", function() { return it('should call the callback', function() {
return this.callback.called.should.equal(true); return this.callback.called.should.equal(true)
}); })
}); })
});
return describe('clearProject', function() {
beforeEach(function() {
this.urls = ['www.example.com/file1', 'www.example.com/file2']
this.UrlCache._findAllUrlsInProject = sinon
.stub()
.callsArgWith(1, null, this.urls)
this.UrlCache._clearUrlFromCache = sinon.stub().callsArg(2)
return this.UrlCache.clearProject(this.project_id, this.callback)
})
it('should clear the cache for each url in the project', function() {
return Array.from(this.urls).map(url =>
this.UrlCache._clearUrlFromCache
.calledWith(this.project_id, url)
.should.equal(true)
)
})
return it('should call the callback', function() {
return this.callback.called.should.equal(true)
})
})
})

View file

@ -8,152 +8,165 @@
* DS102: Remove unnecessary code created because of implicit returns * DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/ */
const SandboxedModule = require('sandboxed-module'); const SandboxedModule = require('sandboxed-module')
const sinon = require('sinon'); const sinon = require('sinon')
require('chai').should(); require('chai').should()
const modulePath = require('path').join(__dirname, '../../../app/js/UrlFetcher'); const modulePath = require('path').join(__dirname, '../../../app/js/UrlFetcher')
const { EventEmitter } = require("events"); const { EventEmitter } = require('events')
describe("UrlFetcher", function() { describe('UrlFetcher', function() {
beforeEach(function() { beforeEach(function() {
this.callback = sinon.stub(); this.callback = sinon.stub()
this.url = "https://www.example.com/file/here?query=string"; this.url = 'https://www.example.com/file/here?query=string'
return this.UrlFetcher = SandboxedModule.require(modulePath, { requires: { return (this.UrlFetcher = SandboxedModule.require(modulePath, {
request: { defaults: (this.defaults = sinon.stub().returns(this.request = {})) requires: {
}, request: {
fs: (this.fs = {}), defaults: (this.defaults = sinon.stub().returns((this.request = {})))
"logger-sharelatex": (this.logger = { log: sinon.stub(), error: sinon.stub() }), },
"settings-sharelatex": (this.settings = {}) fs: (this.fs = {}),
} 'logger-sharelatex': (this.logger = {
});}); log: sinon.stub(),
error: sinon.stub()
}),
'settings-sharelatex': (this.settings = {})
}
}))
})
it("should turn off the cookie jar in request", function() { it('should turn off the cookie jar in request', function() {
return this.defaults.calledWith({jar: false}) return this.defaults.calledWith({ jar: false }).should.equal(true)
.should.equal(true); })
});
describe("rewrite url domain if filestoreDomainOveride is set", function() { describe('rewrite url domain if filestoreDomainOveride is set', function() {
beforeEach(function() { beforeEach(function() {
this.path = "/path/to/file/on/disk"; this.path = '/path/to/file/on/disk'
this.request.get = sinon.stub().returns(this.urlStream = new EventEmitter); this.request.get = sinon
this.urlStream.pipe = sinon.stub(); .stub()
this.urlStream.pause = sinon.stub(); .returns((this.urlStream = new EventEmitter()))
this.urlStream.resume = sinon.stub(); this.urlStream.pipe = sinon.stub()
this.fs.createWriteStream = sinon.stub().returns(this.fileStream = new EventEmitter); this.urlStream.pause = sinon.stub()
return this.fs.unlink = (file, callback) => callback(); this.urlStream.resume = sinon.stub()
}); this.fs.createWriteStream = sinon
.stub()
.returns((this.fileStream = new EventEmitter()))
return (this.fs.unlink = (file, callback) => callback())
})
it("should use the normal domain when override not set", function(done){ it('should use the normal domain when override not set', function(done) {
this.UrlFetcher.pipeUrlToFile(this.url, this.path, () => { this.UrlFetcher.pipeUrlToFile(this.url, this.path, () => {
this.request.get.args[0][0].url.should.equal(this.url); this.request.get.args[0][0].url.should.equal(this.url)
return done(); return done()
}); })
this.res = {statusCode: 200}; this.res = { statusCode: 200 }
this.urlStream.emit("response", this.res); this.urlStream.emit('response', this.res)
this.urlStream.emit("end"); this.urlStream.emit('end')
return this.fileStream.emit("finish"); return this.fileStream.emit('finish')
}); })
return it('should use override domain when filestoreDomainOveride is set', function(done) {
this.settings.filestoreDomainOveride = '192.11.11.11'
this.UrlFetcher.pipeUrlToFile(this.url, this.path, () => {
this.request.get.args[0][0].url.should.equal(
'192.11.11.11/file/here?query=string'
)
return done()
})
this.res = { statusCode: 200 }
this.urlStream.emit('response', this.res)
this.urlStream.emit('end')
return this.fileStream.emit('finish')
})
})
return it("should use override domain when filestoreDomainOveride is set", function(done){ return describe('pipeUrlToFile', function() {
this.settings.filestoreDomainOveride = "192.11.11.11"; beforeEach(function(done) {
this.UrlFetcher.pipeUrlToFile(this.url, this.path, () => { this.path = '/path/to/file/on/disk'
this.request.get.args[0][0].url.should.equal("192.11.11.11/file/here?query=string"); this.request.get = sinon
return done(); .stub()
}); .returns((this.urlStream = new EventEmitter()))
this.res = {statusCode: 200}; this.urlStream.pipe = sinon.stub()
this.urlStream.emit("response", this.res); this.urlStream.pause = sinon.stub()
this.urlStream.emit("end"); this.urlStream.resume = sinon.stub()
return this.fileStream.emit("finish"); this.fs.createWriteStream = sinon
}); .stub()
}); .returns((this.fileStream = new EventEmitter()))
this.fs.unlink = (file, callback) => callback()
return done()
})
return describe("pipeUrlToFile", function() { describe('successfully', function() {
beforeEach(function(done){ beforeEach(function(done) {
this.path = "/path/to/file/on/disk"; this.UrlFetcher.pipeUrlToFile(this.url, this.path, () => {
this.request.get = sinon.stub().returns(this.urlStream = new EventEmitter); this.callback()
this.urlStream.pipe = sinon.stub(); return done()
this.urlStream.pause = sinon.stub(); })
this.urlStream.resume = sinon.stub(); this.res = { statusCode: 200 }
this.fs.createWriteStream = sinon.stub().returns(this.fileStream = new EventEmitter); this.urlStream.emit('response', this.res)
this.fs.unlink = (file, callback) => callback(); this.urlStream.emit('end')
return done(); return this.fileStream.emit('finish')
}); })
describe("successfully", function() { it('should request the URL', function() {
beforeEach(function(done){ return this.request.get
this.UrlFetcher.pipeUrlToFile(this.url, this.path, () => { .calledWith(sinon.match({ url: this.url }))
this.callback(); .should.equal(true)
return done(); })
});
this.res = {statusCode: 200};
this.urlStream.emit("response", this.res);
this.urlStream.emit("end");
return this.fileStream.emit("finish");
});
it('should open the file for writing', function() {
return this.fs.createWriteStream
.calledWith(this.path)
.should.equal(true)
})
it("should request the URL", function() { it('should pipe the URL to the file', function() {
return this.request.get return this.urlStream.pipe
.calledWith(sinon.match({"url": this.url})) .calledWith(this.fileStream)
.should.equal(true); .should.equal(true)
}); })
it("should open the file for writing", function() { return it('should call the callback', function() {
return this.fs.createWriteStream return this.callback.called.should.equal(true)
.calledWith(this.path) })
.should.equal(true); })
});
it("should pipe the URL to the file", function() { describe('with non success status code', function() {
return this.urlStream.pipe beforeEach(function(done) {
.calledWith(this.fileStream) this.UrlFetcher.pipeUrlToFile(this.url, this.path, err => {
.should.equal(true); this.callback(err)
}); return done()
})
return it("should call the callback", function() { this.res = { statusCode: 404 }
return this.callback.called.should.equal(true); this.urlStream.emit('response', this.res)
}); return this.urlStream.emit('end')
}); })
describe("with non success status code", function() { return it('should call the callback with an error', function() {
beforeEach(function(done){ return this.callback
this.UrlFetcher.pipeUrlToFile(this.url, this.path, err=> { .calledWith(new Error('URL returned non-success status code: 404'))
this.callback(err); .should.equal(true)
return done(); })
}); })
this.res = {statusCode: 404};
this.urlStream.emit("response", this.res);
return this.urlStream.emit("end");
});
return it("should call the callback with an error", function() { return describe('with error', function() {
return this.callback beforeEach(function(done) {
.calledWith(new Error("URL returned non-success status code: 404")) this.UrlFetcher.pipeUrlToFile(this.url, this.path, err => {
.should.equal(true); this.callback(err)
}); return done()
}); })
return this.urlStream.emit(
'error',
(this.error = new Error('something went wrong'))
)
})
return describe("with error", function() { it('should call the callback with the error', function() {
beforeEach(function(done){ return this.callback.calledWith(this.error).should.equal(true)
this.UrlFetcher.pipeUrlToFile(this.url, this.path, err=> { })
this.callback(err);
return done();
});
return this.urlStream.emit("error", (this.error = new Error("something went wrong")));
});
it("should call the callback with the error", function() {
return this.callback
.calledWith(this.error)
.should.equal(true);
});
return it("should only call the callback once, even if end is called", function() {
this.urlStream.emit("end");
return this.callback.calledOnce.should.equal(true);
});
});
});
});
return it('should only call the callback once, even if end is called', function() {
this.urlStream.emit('end')
return this.callback.calledOnce.should.equal(true)
})
})
})
})