فهرست منبع

decaffeinate: Convert CompileControllerTests.coffee and 17 other files to JS

decaffeinate 6 سال پیش
والد
کامیت
37b4e96de4

+ 255 - 203
services/clsi/test/unit/coffee/CompileControllerTests.js

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

+ 399 - 329
services/clsi/test/unit/coffee/CompileManagerTests.js

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

+ 60 - 40
services/clsi/test/unit/coffee/ContentTypeMapperTests.js

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

+ 173 - 130
services/clsi/test/unit/coffee/DockerLockManagerTests.js

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

+ 624 - 477
services/clsi/test/unit/coffee/DockerRunnerTests.js

@@ -1,509 +1,656 @@
-SandboxedModule = require('sandboxed-module')
-sinon = require('sinon')
-require('chai').should()
-expect = require('chai').expect
-require "coffee-script"
-modulePath = require('path').join __dirname, '../../../app/coffee/DockerRunner'
-Path = require "path"
-
-describe "DockerRunner", ->
-	beforeEach ->
-		@container = container = {}
-		@DockerRunner = SandboxedModule.require modulePath, requires:
-			"settings-sharelatex": @Settings =
-				clsi: docker: {}
+/*
+ * decaffeinate suggestions:
+ * DS101: Remove unnecessary use of Array.from
+ * DS102: Remove unnecessary code created because of implicit returns
+ * DS206: Consider reworking classes to avoid initClass
+ * DS207: Consider shorter variations of null checks
+ * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
+ */
+const SandboxedModule = require('sandboxed-module');
+const sinon = require('sinon');
+require('chai').should();
+const { expect } = require('chai');
+require("coffee-script");
+const modulePath = require('path').join(__dirname, '../../../app/coffee/DockerRunner');
+const Path = require("path");
+
+describe("DockerRunner", function() {
+	beforeEach(function() {
+		let container, Docker, Timer;
+		this.container = (container = {});
+		this.DockerRunner = SandboxedModule.require(modulePath, { requires: {
+			"settings-sharelatex": (this.Settings = {
+				clsi: { docker: {}
+			},
 				path: {}
-			"logger-sharelatex": @logger = {
+			}),
+			"logger-sharelatex": (this.logger = {
 				log: sinon.stub(),
 				error: sinon.stub(),
 				info: sinon.stub(),
 				warn: sinon.stub()
+			}),
+			"dockerode": (Docker = (function() {
+				Docker = class Docker {
+					static initClass() {
+						this.prototype.getContainer = sinon.stub().returns(container);
+						this.prototype.createContainer = sinon.stub().yields(null, container);
+						this.prototype.listContainers = sinon.stub();
+					}
+				};
+				Docker.initClass();
+				return Docker;
+			})()),
+			"fs": (this.fs = { stat: sinon.stub().yields(null,{isDirectory(){ return true; }}) }),
+			"./Metrics": {
+				Timer: (Timer = class Timer {
+					done() {}
+				})
+			},
+			"./LockManager": {
+				runWithLock(key, runner, callback) { return runner(callback); }
 			}
-			"dockerode": class Docker
-				getContainer: sinon.stub().returns(container)
-				createContainer: sinon.stub().yields(null, container)
-				listContainers: sinon.stub()
-			"fs": @fs = { stat: sinon.stub().yields(null,{isDirectory:()->true}) }
-			"./Metrics":
-				Timer: class Timer
-					done: () ->
-			"./LockManager":
-				runWithLock: (key, runner, callback) -> runner(callback)
-		@Docker = Docker
-		@getContainer = Docker::getContainer
-		@createContainer = Docker::createContainer
-		@listContainers = Docker::listContainers
-
-		@directory = "/local/compile/directory"
-		@mainFile  = "main-file.tex"
-		@compiler  = "pdflatex"
-		@image     = "example.com/sharelatex/image:2016.2"
-		@env       = {}
-		@callback  = sinon.stub()
-		@project_id = "project-id-123"
-		@volumes =
-			"/local/compile/directory": "/compile"
-		@Settings.clsi.docker.image = @defaultImage = "default-image"
-		@Settings.clsi.docker.env   = PATH: "mock-path"
-
-	describe "run", ->
-		beforeEach (done)->
-			@DockerRunner._getContainerOptions = sinon.stub().returns(@options = {mockoptions: "foo"})
-			@DockerRunner._fingerprintContainer = sinon.stub().returns(@fingerprint = "fingerprint")
-
-			@name = "project-#{@project_id}-#{@fingerprint}"
-
-			@command = ["mock", "command", "--outdir=$COMPILE_DIR"]
-			@command_with_dir = ["mock", "command", "--outdir=/compile"]
-			@timeout = 42000
-			done()
-
-		describe "successfully", ->
-			beforeEach (done)->
-				@DockerRunner._runAndWaitForContainer = sinon.stub().callsArgWith(3, null, @output = "mock-output")
-				@DockerRunner.run @project_id, @command, @directory, @image, @timeout, @env, (err, output)=>
-					@callback(err, output)
-					done()
-
-			it "should generate the options for the container", ->
-				@DockerRunner._getContainerOptions
-					.calledWith(@command_with_dir, @image, @volumes, @timeout)
-					.should.equal true
-
-			it "should generate the fingerprint from the returned options", ->
-				@DockerRunner._fingerprintContainer
-					.calledWith(@options)
-					.should.equal true
-
-			it "should do the run", ->
-				@DockerRunner._runAndWaitForContainer	
-					.calledWith(@options, @volumes, @timeout)		
-					.should.equal true
-
-			it "should call the callback", ->
-				@callback.calledWith(null, @output).should.equal true
-
-		describe 'when path.sandboxedCompilesHostDir is set', ->
-
-			beforeEach ->
-				@Settings.path.sandboxedCompilesHostDir = '/some/host/dir/compiles'
-				@directory = '/var/lib/sharelatex/data/compiles/xyz'
-				@DockerRunner._runAndWaitForContainer = sinon.stub().callsArgWith(3, null, @output = "mock-output")
-				@DockerRunner.run @project_id, @command, @directory, @image, @timeout, @env, @callback
-
-			it 'should re-write the bind directory', ->
-				volumes = @DockerRunner._runAndWaitForContainer.lastCall.args[1]
-				expect(volumes).to.deep.equal {
+		}
+	}
+		);
+		this.Docker = Docker;
+		this.getContainer = Docker.prototype.getContainer;
+		this.createContainer = Docker.prototype.createContainer;
+		this.listContainers = Docker.prototype.listContainers;
+
+		this.directory = "/local/compile/directory";
+		this.mainFile  = "main-file.tex";
+		this.compiler  = "pdflatex";
+		this.image     = "example.com/sharelatex/image:2016.2";
+		this.env       = {};
+		this.callback  = sinon.stub();
+		this.project_id = "project-id-123";
+		this.volumes =
+			{"/local/compile/directory": "/compile"};
+		this.Settings.clsi.docker.image = (this.defaultImage = "default-image");
+		return this.Settings.clsi.docker.env   = {PATH: "mock-path"};
+	});
+
+	describe("run", function() {
+		beforeEach(function(done){
+			this.DockerRunner._getContainerOptions = sinon.stub().returns(this.options = {mockoptions: "foo"});
+			this.DockerRunner._fingerprintContainer = sinon.stub().returns(this.fingerprint = "fingerprint");
+
+			this.name = `project-${this.project_id}-${this.fingerprint}`;
+
+			this.command = ["mock", "command", "--outdir=$COMPILE_DIR"];
+			this.command_with_dir = ["mock", "command", "--outdir=/compile"];
+			this.timeout = 42000;
+			return done();
+		});
+
+		describe("successfully", function() {
+			beforeEach(function(done){
+				this.DockerRunner._runAndWaitForContainer = sinon.stub().callsArgWith(3, null, (this.output = "mock-output"));
+				return this.DockerRunner.run(this.project_id, this.command, this.directory, this.image, this.timeout, this.env, (err, output)=> {
+					this.callback(err, output);
+					return done();
+				});
+			});
+
+			it("should generate the options for the container", function() {
+				return this.DockerRunner._getContainerOptions
+					.calledWith(this.command_with_dir, this.image, this.volumes, this.timeout)
+					.should.equal(true);
+			});
+
+			it("should generate the fingerprint from the returned options", function() {
+				return this.DockerRunner._fingerprintContainer
+					.calledWith(this.options)
+					.should.equal(true);
+			});
+
+			it("should do the run", function() {
+				return this.DockerRunner._runAndWaitForContainer	
+					.calledWith(this.options, this.volumes, this.timeout)		
+					.should.equal(true);
+			});
+
+			return it("should call the callback", function() {
+				return this.callback.calledWith(null, this.output).should.equal(true);
+			});
+		});
+
+		describe('when path.sandboxedCompilesHostDir is set', function() {
+
+			beforeEach(function() {
+				this.Settings.path.sandboxedCompilesHostDir = '/some/host/dir/compiles';
+				this.directory = '/var/lib/sharelatex/data/compiles/xyz';
+				this.DockerRunner._runAndWaitForContainer = sinon.stub().callsArgWith(3, null, (this.output = "mock-output"));
+				return this.DockerRunner.run(this.project_id, this.command, this.directory, this.image, this.timeout, this.env, this.callback);
+			});
+
+			it('should re-write the bind directory', function() {
+				const volumes = this.DockerRunner._runAndWaitForContainer.lastCall.args[1];
+				return expect(volumes).to.deep.equal({
 					'/some/host/dir/compiles/xyz': '/compile'
-				}
-
-			it "should call the callback", ->
-				@callback.calledWith(null, @output).should.equal true
-
-		describe "when the run throws an error", ->
-			beforeEach ->
-				firstTime = true
-				@output = "mock-output"
-				@DockerRunner._runAndWaitForContainer = (options, volumes, timeout, callback = (error, output)->) =>
-					if firstTime
-						firstTime = false
-						callback new Error("HTTP code is 500 which indicates error: server error")
-					else
-						callback(null, @output)
-				sinon.spy @DockerRunner, "_runAndWaitForContainer"
-				@DockerRunner.destroyContainer = sinon.stub().callsArg(3)
-				@DockerRunner.run @project_id, @command, @directory, @image, @timeout, @env, @callback
-
-			it "should do the run twice", ->
-				@DockerRunner._runAndWaitForContainer	
-					.calledTwice.should.equal true
-
-			it "should destroy the container in between", ->
-				@DockerRunner.destroyContainer
-					.calledWith(@name, null)
-					.should.equal true
-
-			it "should call the callback", ->
-				@callback.calledWith(null, @output).should.equal true
+				});
+		});
+
+			return it("should call the callback", function() {
+				return this.callback.calledWith(null, this.output).should.equal(true);
+			});
+		});
+
+		describe("when the run throws an error", function() {
+			beforeEach(function() {
+				let firstTime = true;
+				this.output = "mock-output";
+				this.DockerRunner._runAndWaitForContainer = (options, volumes, timeout, callback) => {
+					if (callback == null) { callback = function(error, output){}; }
+					if (firstTime) {
+						firstTime = false;
+						return callback(new Error("HTTP code is 500 which indicates error: server error"));
+					} else {
+						return callback(null, this.output);
+					}
+				};
+				sinon.spy(this.DockerRunner, "_runAndWaitForContainer");
+				this.DockerRunner.destroyContainer = sinon.stub().callsArg(3);
+				return this.DockerRunner.run(this.project_id, this.command, this.directory, this.image, this.timeout, this.env, this.callback);
+			});
+
+			it("should do the run twice", function() {
+				return this.DockerRunner._runAndWaitForContainer	
+					.calledTwice.should.equal(true);
+			});
+
+			it("should destroy the container in between", function() {
+				return this.DockerRunner.destroyContainer
+					.calledWith(this.name, null)
+					.should.equal(true);
+			});
+
+			return it("should call the callback", function() {
+				return this.callback.calledWith(null, this.output).should.equal(true);
+			});
+		});
 		
-		describe "with no image", ->
-			beforeEach ->
-				@DockerRunner._runAndWaitForContainer = sinon.stub().callsArgWith(3, null, @output = "mock-output")
-				@DockerRunner.run @project_id, @command, @directory, null, @timeout, @env, @callback
-
-			it "should use the default image", ->
-				@DockerRunner._getContainerOptions
-					.calledWith(@command_with_dir, @defaultImage, @volumes, @timeout)
-					.should.equal true
+		describe("with no image", function() {
+			beforeEach(function() {
+				this.DockerRunner._runAndWaitForContainer = sinon.stub().callsArgWith(3, null, (this.output = "mock-output"));
+				return this.DockerRunner.run(this.project_id, this.command, this.directory, null, this.timeout, this.env, this.callback);
+			});
+
+			return it("should use the default image", function() {
+				return this.DockerRunner._getContainerOptions
+					.calledWith(this.command_with_dir, this.defaultImage, this.volumes, this.timeout)
+					.should.equal(true);
+			});
+		});
 		
-		describe "with image override", ->
-			beforeEach ->
-				@Settings.texliveImageNameOveride = "overrideimage.com/something"
-				@DockerRunner._runAndWaitForContainer = sinon.stub().callsArgWith(3, null, @output = "mock-output")
-				@DockerRunner.run @project_id, @command, @directory, @image, @timeout, @env, @callback
-
-			it "should use the override and keep the tag", ->
-				image = @DockerRunner._getContainerOptions.args[0][1]
-				image.should.equal "overrideimage.com/something/image:2016.2"
-
-	describe "_runAndWaitForContainer", ->
-		beforeEach ->
-			@options = {mockoptions: "foo", name: @name = "mock-name"}
-			@DockerRunner.startContainer = (options, volumes, attachStreamHandler, callback) =>
-				attachStreamHandler(null, @output = "mock-output")
-				callback(null, @containerId = "container-id")
-			sinon.spy @DockerRunner, "startContainer"
-			@DockerRunner.waitForContainer = sinon.stub().callsArgWith(2, null, @exitCode = 42)
-			@DockerRunner._runAndWaitForContainer @options, @volumes, @timeout, @callback
-
-		it "should create/start the container", ->
-			@DockerRunner.startContainer
-				.calledWith(@options, @volumes)
-				.should.equal true
-
-		it "should wait for the container to finish", ->
-			@DockerRunner.waitForContainer
-				.calledWith(@name, @timeout)
-				.should.equal true
-
-		it "should call the callback with the output", ->
-			@callback.calledWith(null, @output).should.equal true
-
-	describe "startContainer", ->
-		beforeEach ->
-			@attachStreamHandler = sinon.stub()
-			@attachStreamHandler.cock = true
-			@options = {mockoptions: "foo", name: "mock-name"}
-			@container.inspect = sinon.stub().callsArgWith(0)
-			@DockerRunner.attachToContainer = (containerId, attachStreamHandler, cb)=> 
-				attachStreamHandler()
-				cb()
-			sinon.spy @DockerRunner, "attachToContainer"
-
-
-
-		describe "when the container exists", ->
-			beforeEach ->
-				@container.inspect = sinon.stub().callsArgWith(0)
-				@container.start = sinon.stub().yields()
-
-				@DockerRunner.startContainer @options, @volumes, @callback, ->
-
-			it "should start the container with the given name", ->
-				@getContainer
-					.calledWith(@options.name)
-					.should.equal true
-				@container.start
+		return describe("with image override", function() {
+			beforeEach(function() {
+				this.Settings.texliveImageNameOveride = "overrideimage.com/something";
+				this.DockerRunner._runAndWaitForContainer = sinon.stub().callsArgWith(3, null, (this.output = "mock-output"));
+				return this.DockerRunner.run(this.project_id, this.command, this.directory, this.image, this.timeout, this.env, this.callback);
+			});
+
+			return it("should use the override and keep the tag", function() {
+				const image = this.DockerRunner._getContainerOptions.args[0][1];
+				return image.should.equal("overrideimage.com/something/image:2016.2");
+			});
+		});
+	});
+
+	describe("_runAndWaitForContainer", function() {
+		beforeEach(function() {
+			this.options = {mockoptions: "foo", name: (this.name = "mock-name")};
+			this.DockerRunner.startContainer = (options, volumes, attachStreamHandler, callback) => {
+				attachStreamHandler(null, (this.output = "mock-output"));
+				return callback(null, (this.containerId = "container-id"));
+			};
+			sinon.spy(this.DockerRunner, "startContainer");
+			this.DockerRunner.waitForContainer = sinon.stub().callsArgWith(2, null, (this.exitCode = 42));
+			return this.DockerRunner._runAndWaitForContainer(this.options, this.volumes, this.timeout, this.callback);
+		});
+
+		it("should create/start the container", function() {
+			return this.DockerRunner.startContainer
+				.calledWith(this.options, this.volumes)
+				.should.equal(true);
+		});
+
+		it("should wait for the container to finish", function() {
+			return this.DockerRunner.waitForContainer
+				.calledWith(this.name, this.timeout)
+				.should.equal(true);
+		});
+
+		return it("should call the callback with the output", function() {
+			return this.callback.calledWith(null, this.output).should.equal(true);
+		});
+	});
+
+	describe("startContainer", function() {
+		beforeEach(function() {
+			this.attachStreamHandler = sinon.stub();
+			this.attachStreamHandler.cock = true;
+			this.options = {mockoptions: "foo", name: "mock-name"};
+			this.container.inspect = sinon.stub().callsArgWith(0);
+			this.DockerRunner.attachToContainer = (containerId, attachStreamHandler, cb)=> { 
+				attachStreamHandler();
+				return cb();
+			};
+			return sinon.spy(this.DockerRunner, "attachToContainer");
+		});
+
+
+
+		describe("when the container exists", function() {
+			beforeEach(function() {
+				this.container.inspect = sinon.stub().callsArgWith(0);
+				this.container.start = sinon.stub().yields();
+
+				return this.DockerRunner.startContainer(this.options, this.volumes, this.callback, function() {});
+			});
+
+			it("should start the container with the given name", function() {
+				this.getContainer
+					.calledWith(this.options.name)
+					.should.equal(true);
+				return this.container.start
 					.called
-					.should.equal true
+					.should.equal(true);
+			});
 
-			it "should not try to create the container", ->
-				@createContainer.called.should.equal false
+			it("should not try to create the container", function() {
+				return this.createContainer.called.should.equal(false);
+			});
 			
-			it "should attach to the container", ->
-				@DockerRunner.attachToContainer.called.should.equal true
+			it("should attach to the container", function() {
+				return this.DockerRunner.attachToContainer.called.should.equal(true);
+			});
 
-			it "should call the callback", ->
-				@callback.called.should.equal true
+			it("should call the callback", function() {
+				return this.callback.called.should.equal(true);
+			});
 	
-			it "should attach before the container starts", ->
-				sinon.assert.callOrder(@DockerRunner.attachToContainer, @container.start)
+			return it("should attach before the container starts", function() {
+				return sinon.assert.callOrder(this.DockerRunner.attachToContainer, this.container.start);
+			});
+		});
 						
-		describe "when the container does not exist", ->
-			beforeEach ()->
-				exists = false
-				@container.start = sinon.stub().yields()
-				@container.inspect = sinon.stub().callsArgWith(0, {statusCode:404})
-				@DockerRunner.startContainer @options, @volumes, @attachStreamHandler, @callback
-
-			it "should create the container", ->
-				@createContainer
-					.calledWith(@options)
-					.should.equal true
-
-			it "should call the callback and stream handler", ->
-				@attachStreamHandler.called.should.equal true
-				@callback.called.should.equal true
+		describe("when the container does not exist", function() {
+			beforeEach(function(){
+				const exists = false;
+				this.container.start = sinon.stub().yields();
+				this.container.inspect = sinon.stub().callsArgWith(0, {statusCode:404});
+				return this.DockerRunner.startContainer(this.options, this.volumes, this.attachStreamHandler, this.callback);
+			});
+
+			it("should create the container", function() {
+				return this.createContainer
+					.calledWith(this.options)
+					.should.equal(true);
+			});
+
+			it("should call the callback and stream handler", function() {
+				this.attachStreamHandler.called.should.equal(true);
+				return this.callback.called.should.equal(true);
+			});
 			
-			it "should attach to the container", ->
-				@DockerRunner.attachToContainer.called.should.equal true
-
-			it "should attach before the container starts", ->
-				sinon.assert.callOrder(@DockerRunner.attachToContainer, @container.start)
-
-
-		describe "when the container is already running", ->
-			beforeEach ->
-				error = new Error("HTTP code is 304 which indicates error: server error - start: Cannot start container #{@name}: The container MOCKID is already running.")
-				error.statusCode = 304
-				@container.start = sinon.stub().yields(error)
-				@container.inspect = sinon.stub().callsArgWith(0)
-				@DockerRunner.startContainer @options, @volumes, @attachStreamHandler, @callback
-
-			it "should not try to create the container", ->
-				@createContainer.called.should.equal false
-
-			it "should call the callback  and stream handler without an error", ->
-				@attachStreamHandler.called.should.equal true
-				@callback.called.should.equal true
-
-		describe "when a volume does not exist", ->
-			beforeEach ()->
-				@fs.stat = sinon.stub().yields(new Error("no such path"))
-				@DockerRunner.startContainer @options, @volumes, @attachStreamHandler, @callback
-
-			it "should not try to create the container", ->
-				@createContainer.called.should.equal false
-
-			it "should call the callback with an error", ->
-				@callback.calledWith(new Error()).should.equal true
-
-		describe "when a volume exists but is not a directory", ->
-			beforeEach ->
-				@fs.stat = sinon.stub().yields(null, {isDirectory: () -> return false})
-				@DockerRunner.startContainer @options, @volumes, @attachStreamHandler, @callback
-
-			it "should not try to create the container", ->
-				@createContainer.called.should.equal false
-
-			it "should call the callback with an error", ->
-				@callback.calledWith(new Error()).should.equal true
-
-		describe "when a volume does not exist, but sibling-containers are used", ->
-			beforeEach ->
-				@fs.stat = sinon.stub().yields(new Error("no such path"))
-				@Settings.path.sandboxedCompilesHostDir = '/some/path'
-				@container.start = sinon.stub().yields()
-				@DockerRunner.startContainer @options, @volumes, @callback
-
-			afterEach ->
-				delete @Settings.path.sandboxedCompilesHostDir
-
-			it "should start the container with the given name", ->
-				@getContainer
-					.calledWith(@options.name)
-					.should.equal true
-				@container.start
+			it("should attach to the container", function() {
+				return this.DockerRunner.attachToContainer.called.should.equal(true);
+			});
+
+			return it("should attach before the container starts", function() {
+				return sinon.assert.callOrder(this.DockerRunner.attachToContainer, this.container.start);
+			});
+		});
+
+
+		describe("when the container is already running", function() {
+			beforeEach(function() {
+				const error = new Error(`HTTP code is 304 which indicates error: server error - start: Cannot start container ${this.name}: The container MOCKID is already running.`);
+				error.statusCode = 304;
+				this.container.start = sinon.stub().yields(error);
+				this.container.inspect = sinon.stub().callsArgWith(0);
+				return this.DockerRunner.startContainer(this.options, this.volumes, this.attachStreamHandler, this.callback);
+			});
+
+			it("should not try to create the container", function() {
+				return this.createContainer.called.should.equal(false);
+			});
+
+			return it("should call the callback  and stream handler without an error", function() {
+				this.attachStreamHandler.called.should.equal(true);
+				return this.callback.called.should.equal(true);
+			});
+		});
+
+		describe("when a volume does not exist", function() {
+			beforeEach(function(){
+				this.fs.stat = sinon.stub().yields(new Error("no such path"));
+				return this.DockerRunner.startContainer(this.options, this.volumes, this.attachStreamHandler, this.callback);
+			});
+
+			it("should not try to create the container", function() {
+				return this.createContainer.called.should.equal(false);
+			});
+
+			return it("should call the callback with an error", function() {
+				return this.callback.calledWith(new Error()).should.equal(true);
+			});
+		});
+
+		describe("when a volume exists but is not a directory", function() {
+			beforeEach(function() {
+				this.fs.stat = sinon.stub().yields(null, {isDirectory() { return false; }});
+				return this.DockerRunner.startContainer(this.options, this.volumes, this.attachStreamHandler, this.callback);
+			});
+
+			it("should not try to create the container", function() {
+				return this.createContainer.called.should.equal(false);
+			});
+
+			return it("should call the callback with an error", function() {
+				return this.callback.calledWith(new Error()).should.equal(true);
+			});
+		});
+
+		describe("when a volume does not exist, but sibling-containers are used", function() {
+			beforeEach(function() {
+				this.fs.stat = sinon.stub().yields(new Error("no such path"));
+				this.Settings.path.sandboxedCompilesHostDir = '/some/path';
+				this.container.start = sinon.stub().yields();
+				return this.DockerRunner.startContainer(this.options, this.volumes, this.callback);
+			});
+
+			afterEach(function() {
+				return delete this.Settings.path.sandboxedCompilesHostDir;
+			});
+
+			it("should start the container with the given name", function() {
+				this.getContainer
+					.calledWith(this.options.name)
+					.should.equal(true);
+				return this.container.start
 					.called
-					.should.equal true
-
-			it "should not try to create the container", ->
-				@createContainer.called.should.equal false
-
-			it "should call the callback", ->
-				@callback.called.should.equal true
-				@callback.calledWith(new Error()).should.equal false
-
-		describe "when the container tries to be created, but already has been (race condition)", ->
-
-	describe "waitForContainer", ->
-		beforeEach ->
-			@containerId = "container-id"
-			@timeout = 5000
-			@container.wait = sinon.stub().yields(null, StatusCode: @statusCode = 42)
-			@container.kill = sinon.stub().yields()
+					.should.equal(true);
+			});
+
+			it("should not try to create the container", function() {
+				return this.createContainer.called.should.equal(false);
+			});
+
+			return it("should call the callback", function() {
+				this.callback.called.should.equal(true);
+				return this.callback.calledWith(new Error()).should.equal(false);
+			});
+		});
+
+		return describe("when the container tries to be created, but already has been (race condition)", function() {});
+	});
+
+	describe("waitForContainer", function() {
+		beforeEach(function() {
+			this.containerId = "container-id";
+			this.timeout = 5000;
+			this.container.wait = sinon.stub().yields(null, {StatusCode: (this.statusCode = 42)});
+			return this.container.kill = sinon.stub().yields();
+		});
 			
-		describe "when the container returns in time", ->
-			beforeEach ->
-				@DockerRunner.waitForContainer @containerId, @timeout, @callback
-
-			it "should wait for the container", ->
-				@getContainer
-					.calledWith(@containerId)
-					.should.equal true
-				@container.wait
+		describe("when the container returns in time", function() {
+			beforeEach(function() {
+				return this.DockerRunner.waitForContainer(this.containerId, this.timeout, this.callback);
+			});
+
+			it("should wait for the container", function() {
+				this.getContainer
+					.calledWith(this.containerId)
+					.should.equal(true);
+				return this.container.wait
 					.called
-					.should.equal true
-
-			it "should call the callback with the exit", ->
-				@callback
-					.calledWith(null, @statusCode)
-					.should.equal true
-
-		describe "when the container does not return before the timeout", ->
-			beforeEach (done) ->
-				@container.wait = (callback = (error, exitCode) ->) ->
-					setTimeout () ->
-						callback(null, StatusCode: 42)
-					, 100
-				@timeout = 5
-				@DockerRunner.waitForContainer @containerId, @timeout, (args...) =>
-					@callback(args...)
-					done()
-
-			it "should call kill on the container", ->
-				@getContainer
-					.calledWith(@containerId)
-					.should.equal true
-				@container.kill
+					.should.equal(true);
+			});
+
+			return it("should call the callback with the exit", function() {
+				return this.callback
+					.calledWith(null, this.statusCode)
+					.should.equal(true);
+			});
+		});
+
+		return describe("when the container does not return before the timeout", function() {
+			beforeEach(function(done) {
+				this.container.wait = function(callback) {
+					if (callback == null) { callback = function(error, exitCode) {}; }
+					return setTimeout(() => callback(null, {StatusCode: 42})
+					, 100);
+				};
+				this.timeout = 5;
+				return this.DockerRunner.waitForContainer(this.containerId, this.timeout, (...args) => {
+					this.callback(...Array.from(args || []));
+					return done();
+				});
+			});
+
+			it("should call kill on the container", function() {
+				this.getContainer
+					.calledWith(this.containerId)
+					.should.equal(true);
+				return this.container.kill
 					.called
-					.should.equal true
+					.should.equal(true);
+			});
 
-			it "should call the callback with an error", ->
-				error = new Error("container timed out")
-				error.timedout = true
-				@callback
+			return it("should call the callback with an error", function() {
+				const error = new Error("container timed out");
+				error.timedout = true;
+				return this.callback
 					.calledWith(error)
-					.should.equal true
-
-	describe "destroyOldContainers", ->
-		beforeEach (done) ->
-			oneHourInSeconds = 60 * 60
-			oneHourInMilliseconds = oneHourInSeconds * 1000
-			nowInSeconds = Date.now()/1000
-			@containers = [{
-				Name: "/project-old-container-name"
-				Id: "old-container-id"
+					.should.equal(true);
+			});
+		});
+	});
+
+	describe("destroyOldContainers", function() {
+		beforeEach(function(done) {
+			const oneHourInSeconds = 60 * 60;
+			const oneHourInMilliseconds = oneHourInSeconds * 1000;
+			const nowInSeconds = Date.now()/1000;
+			this.containers = [{
+				Name: "/project-old-container-name",
+				Id: "old-container-id",
 				Created: nowInSeconds - oneHourInSeconds - 100
 			}, {
-				Name: "/project-new-container-name"
-				Id: "new-container-id"
-				Created: nowInSeconds - oneHourInSeconds + 100
+				Name: "/project-new-container-name",
+				Id: "new-container-id",
+				Created: (nowInSeconds - oneHourInSeconds) + 100
 			}, {
-				Name: "/totally-not-a-project-container"
-				Id: "some-random-id"
+				Name: "/totally-not-a-project-container",
+				Id: "some-random-id",
 				Created: nowInSeconds - (2 * oneHourInSeconds )
-			}]
-			@DockerRunner.MAX_CONTAINER_AGE = oneHourInMilliseconds
-			@listContainers.callsArgWith(1, null, @containers)
-			@DockerRunner.destroyContainer = sinon.stub().callsArg(3)
-			@DockerRunner.destroyOldContainers (error) =>
-				@callback(error)
-				done()
-
-		it "should list all containers", ->
-			@listContainers
-				.calledWith(all: true)
-				.should.equal true
-
-		it "should destroy old containers", ->
-			@DockerRunner.destroyContainer
+			}];
+			this.DockerRunner.MAX_CONTAINER_AGE = oneHourInMilliseconds;
+			this.listContainers.callsArgWith(1, null, this.containers);
+			this.DockerRunner.destroyContainer = sinon.stub().callsArg(3);
+			return this.DockerRunner.destroyOldContainers(error => {
+				this.callback(error);
+				return done();
+			});
+		});
+
+		it("should list all containers", function() {
+			return this.listContainers
+				.calledWith({all: true})
+				.should.equal(true);
+		});
+
+		it("should destroy old containers", function() {
+			this.DockerRunner.destroyContainer
 				.callCount
-				.should.equal 1
-			@DockerRunner.destroyContainer
+				.should.equal(1);
+			return this.DockerRunner.destroyContainer
 				.calledWith("/project-old-container-name", "old-container-id")
-				.should.equal true
+				.should.equal(true);
+		});
 
-		it "should not destroy new containers", ->
-			@DockerRunner.destroyContainer
+		it("should not destroy new containers", function() {
+			return this.DockerRunner.destroyContainer
 				.calledWith("/project-new-container-name", "new-container-id")
-				.should.equal false
+				.should.equal(false);
+		});
 
-		it "should not destroy non-project containers", ->
-			@DockerRunner.destroyContainer
+		it("should not destroy non-project containers", function() {
+			return this.DockerRunner.destroyContainer
 				.calledWith("/totally-not-a-project-container", "some-random-id")
-				.should.equal false
-
-		it "should callback the callback", ->
-			@callback.called.should.equal true
-
-
-	describe '_destroyContainer', ->
-		beforeEach ->
-			@containerId = 'some_id'
-			@fakeContainer =
-				remove: sinon.stub().callsArgWith(1, null)
-			@Docker::getContainer = sinon.stub().returns(@fakeContainer)
-
-		it 'should get the container', (done) ->
-			@DockerRunner._destroyContainer @containerId, false, (err) =>
-				@Docker::getContainer.callCount.should.equal 1
-				@Docker::getContainer.calledWith(@containerId).should.equal true
-				done()
-
-		it 'should try to force-destroy the container when shouldForce=true', (done) ->
-			@DockerRunner._destroyContainer @containerId, true, (err) =>
-				@fakeContainer.remove.callCount.should.equal 1
-				@fakeContainer.remove.calledWith({force: true}).should.equal true
-				done()
-
-		it 'should not try to force-destroy the container when shouldForce=false', (done) ->
-			@DockerRunner._destroyContainer @containerId, false, (err) =>
-				@fakeContainer.remove.callCount.should.equal 1
-				@fakeContainer.remove.calledWith({force: false}).should.equal true
-				done()
-
-		it 'should not produce an error', (done) ->
-			@DockerRunner._destroyContainer @containerId, false, (err) =>
-				expect(err).to.equal null
-				done()
-
-		describe 'when the container is already gone', ->
-			beforeEach ->
-				@fakeError = new Error('woops')
-				@fakeError.statusCode = 404
-				@fakeContainer =
-					remove: sinon.stub().callsArgWith(1, @fakeError)
-				@Docker::getContainer = sinon.stub().returns(@fakeContainer)
-
-			it 'should not produce an error', (done) ->
-				@DockerRunner._destroyContainer @containerId, false, (err) =>
-					expect(err).to.equal null
-					done()
-
-		describe 'when container.destroy produces an error', (done) ->
-			beforeEach ->
-				@fakeError = new Error('woops')
-				@fakeError.statusCode = 500
-				@fakeContainer =
-					remove: sinon.stub().callsArgWith(1, @fakeError)
-				@Docker::getContainer = sinon.stub().returns(@fakeContainer)
-
-			it 'should produce an error', (done) ->
-				@DockerRunner._destroyContainer @containerId, false, (err) =>
-					expect(err).to.not.equal null
-					expect(err).to.equal @fakeError
-					done()
-
-
-	describe 'kill', ->
-		beforeEach ->
-			@containerId = 'some_id'
-			@fakeContainer =
-				kill: sinon.stub().callsArgWith(0, null)
-			@Docker::getContainer = sinon.stub().returns(@fakeContainer)
-
-		it 'should get the container', (done) ->
-			@DockerRunner.kill @containerId, (err) =>
-				@Docker::getContainer.callCount.should.equal 1
-				@Docker::getContainer.calledWith(@containerId).should.equal true
-				done()
-
-		it 'should try to force-destroy the container', (done) ->
-			@DockerRunner.kill @containerId, (err) =>
-				@fakeContainer.kill.callCount.should.equal 1
-				done()
-
-		it 'should not produce an error', (done) ->
-			@DockerRunner.kill @containerId, (err) =>
-				expect(err).to.equal undefined
-				done()
-
-		describe 'when the container is not actually running', ->
-			beforeEach ->
-				@fakeError = new Error('woops')
-				@fakeError.statusCode = 500
-				@fakeError.message = 'Cannot kill container <whatever> is not running'
-				@fakeContainer =
-					kill: sinon.stub().callsArgWith(0, @fakeError)
-				@Docker::getContainer = sinon.stub().returns(@fakeContainer)
-
-			it 'should not produce an error', (done) ->
-				@DockerRunner.kill @containerId, (err) =>
-					expect(err).to.equal undefined
-					done()
-
-		describe 'when container.kill produces a legitimate error', (done) ->
-			beforeEach ->
-				@fakeError = new Error('woops')
-				@fakeError.statusCode = 500
-				@fakeError.message = 'Totally legitimate reason to throw an error'
-				@fakeContainer =
-					kill: sinon.stub().callsArgWith(0, @fakeError)
-				@Docker::getContainer = sinon.stub().returns(@fakeContainer)
-
-			it 'should produce an error', (done) ->
-				@DockerRunner.kill @containerId, (err) =>
-					expect(err).to.not.equal undefined
-					expect(err).to.equal @fakeError
-					done()
+				.should.equal(false);
+		});
+
+		return it("should callback the callback", function() {
+			return this.callback.called.should.equal(true);
+		});
+	});
+
+
+	describe('_destroyContainer', function() {
+		beforeEach(function() {
+			this.containerId = 'some_id';
+			this.fakeContainer =
+				{remove: sinon.stub().callsArgWith(1, null)};
+			return this.Docker.prototype.getContainer = sinon.stub().returns(this.fakeContainer);
+		});
+
+		it('should get the container', function(done) {
+			return this.DockerRunner._destroyContainer(this.containerId, false, err => {
+				this.Docker.prototype.getContainer.callCount.should.equal(1);
+				this.Docker.prototype.getContainer.calledWith(this.containerId).should.equal(true);
+				return done();
+			});
+		});
+
+		it('should try to force-destroy the container when shouldForce=true', function(done) {
+			return this.DockerRunner._destroyContainer(this.containerId, true, err => {
+				this.fakeContainer.remove.callCount.should.equal(1);
+				this.fakeContainer.remove.calledWith({force: true}).should.equal(true);
+				return done();
+			});
+		});
+
+		it('should not try to force-destroy the container when shouldForce=false', function(done) {
+			return this.DockerRunner._destroyContainer(this.containerId, false, err => {
+				this.fakeContainer.remove.callCount.should.equal(1);
+				this.fakeContainer.remove.calledWith({force: false}).should.equal(true);
+				return done();
+			});
+		});
+
+		it('should not produce an error', function(done) {
+			return this.DockerRunner._destroyContainer(this.containerId, false, err => {
+				expect(err).to.equal(null);
+				return done();
+			});
+		});
+
+		describe('when the container is already gone', function() {
+			beforeEach(function() {
+				this.fakeError = new Error('woops');
+				this.fakeError.statusCode = 404;
+				this.fakeContainer =
+					{remove: sinon.stub().callsArgWith(1, this.fakeError)};
+				return this.Docker.prototype.getContainer = sinon.stub().returns(this.fakeContainer);
+			});
+
+			return it('should not produce an error', function(done) {
+				return this.DockerRunner._destroyContainer(this.containerId, false, err => {
+					expect(err).to.equal(null);
+					return done();
+				});
+			});
+		});
+
+		return describe('when container.destroy produces an error', function(done) {
+			beforeEach(function() {
+				this.fakeError = new Error('woops');
+				this.fakeError.statusCode = 500;
+				this.fakeContainer =
+					{remove: sinon.stub().callsArgWith(1, this.fakeError)};
+				return this.Docker.prototype.getContainer = sinon.stub().returns(this.fakeContainer);
+			});
+
+			return it('should produce an error', function(done) {
+				return this.DockerRunner._destroyContainer(this.containerId, false, err => {
+					expect(err).to.not.equal(null);
+					expect(err).to.equal(this.fakeError);
+					return done();
+				});
+			});
+		});
+	});
+
+
+	return describe('kill', function() {
+		beforeEach(function() {
+			this.containerId = 'some_id';
+			this.fakeContainer =
+				{kill: sinon.stub().callsArgWith(0, null)};
+			return this.Docker.prototype.getContainer = sinon.stub().returns(this.fakeContainer);
+		});
+
+		it('should get the container', function(done) {
+			return this.DockerRunner.kill(this.containerId, err => {
+				this.Docker.prototype.getContainer.callCount.should.equal(1);
+				this.Docker.prototype.getContainer.calledWith(this.containerId).should.equal(true);
+				return done();
+			});
+		});
+
+		it('should try to force-destroy the container', function(done) {
+			return this.DockerRunner.kill(this.containerId, err => {
+				this.fakeContainer.kill.callCount.should.equal(1);
+				return done();
+			});
+		});
+
+		it('should not produce an error', function(done) {
+			return this.DockerRunner.kill(this.containerId, err => {
+				expect(err).to.equal(undefined);
+				return done();
+			});
+		});
+
+		describe('when the container is not actually running', function() {
+			beforeEach(function() {
+				this.fakeError = new Error('woops');
+				this.fakeError.statusCode = 500;
+				this.fakeError.message = 'Cannot kill container <whatever> is not running';
+				this.fakeContainer =
+					{kill: sinon.stub().callsArgWith(0, this.fakeError)};
+				return this.Docker.prototype.getContainer = sinon.stub().returns(this.fakeContainer);
+			});
+
+			return it('should not produce an error', function(done) {
+				return this.DockerRunner.kill(this.containerId, err => {
+					expect(err).to.equal(undefined);
+					return done();
+				});
+			});
+		});
+
+		return describe('when container.kill produces a legitimate error', function(done) {
+			beforeEach(function() {
+				this.fakeError = new Error('woops');
+				this.fakeError.statusCode = 500;
+				this.fakeError.message = 'Totally legitimate reason to throw an error';
+				this.fakeContainer =
+					{kill: sinon.stub().callsArgWith(0, this.fakeError)};
+				return this.Docker.prototype.getContainer = sinon.stub().returns(this.fakeContainer);
+			});
+
+			return it('should produce an error', function(done) {
+				return this.DockerRunner.kill(this.containerId, err => {
+					expect(err).to.not.equal(undefined);
+					expect(err).to.equal(this.fakeError);
+					return done();
+				});
+			});
+		});
+	});
+});

+ 70 - 54
services/clsi/test/unit/coffee/DraftModeManagerTests.js

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

+ 94 - 68
services/clsi/test/unit/coffee/LatexRunnerTests.js

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

+ 70 - 50
services/clsi/test/unit/coffee/LockManagerTests.js

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

+ 73 - 49
services/clsi/test/unit/coffee/OutputFileFinderTests.js

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

+ 140 - 102
services/clsi/test/unit/coffee/OutputFileOptimiserTests.js

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

+ 79 - 59
services/clsi/test/unit/coffee/ProjectPersistenceManagerTests.js

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

+ 373 - 272
services/clsi/test/unit/coffee/RequestParserTests.js

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

+ 145 - 107
services/clsi/test/unit/coffee/ResourceStateManagerTests.js

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

+ 370 - 285
services/clsi/test/unit/coffee/ResourceWriterTests.js

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

+ 214 - 153
services/clsi/test/unit/coffee/StaticServerForbidSymlinksTests.js

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

+ 150 - 117
services/clsi/test/unit/coffee/TikzManager.js

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

+ 253 - 191
services/clsi/test/unit/coffee/UrlCacheTests.js

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

+ 151 - 117
services/clsi/test/unit/coffee/UrlFetcherTests.js

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