diff --git a/services/clsi/test/unit/js/CompileControllerTests.js b/services/clsi/test/unit/js/CompileControllerTests.js index 2a06fbc30e..4480c8804e 100644 --- a/services/clsi/test/unit/js/CompileControllerTests.js +++ b/services/clsi/test/unit/js/CompileControllerTests.js @@ -9,267 +9,299 @@ * 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"); +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": (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('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': (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" - }; - this.req.params = - {project_id: (this.project_id = "project-id-123")}; - this.request = { - compile: "mock-parsed-request" - }; - 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", - build: 1234 - }]; - this.RequestParser.parse = sinon.stub().callsArgWith(1, null, this.request); - this.ProjectPersistenceManager.markProjectAsJustAccessed = sinon.stub().callsArg(1); - this.res.status = sinon.stub().returnsThis(); - return this.res.send = sinon.stub(); - }); + describe('compile', function() { + beforeEach(function() { + this.req.body = { + compile: 'mock-body' + } + this.req.params = { project_id: (this.project_id = 'project-id-123') } + this.request = { + compile: 'mock-parsed-request' + } + 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', + build: 1234 + } + ] + this.RequestParser.parse = sinon + .stub() + .callsArgWith(1, null, this.request) + this.ProjectPersistenceManager.markProjectAsJustAccessed = sinon + .stub() + .callsArg(1) + this.res.status = sinon.stub().returnsThis() + 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); - }); + 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 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 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); - }); + 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", function() { - beforeEach(function() { - this.CompileManager.doCompileWithLock = sinon.stub().callsArgWith(1, new Error(this.message = "error message"), null); - return this.CompileController.compile(this.req, this.res); - }); - - return it("should return the JSON response with the error", function() { - this.res.status.calledWith(500).should.equal(true); - return this.res.send - .calledWith({ - compile: { - status: "error", - error: this.message, - outputFiles: [] - } - }) - .should.equal(true); - }); - }); + 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("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); - }); - - 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); - }); - }); + describe('with an error', function() { + beforeEach(function() { + this.CompileManager.doCompileWithLock = sinon + .stub() + .callsArgWith(1, new Error((this.message = 'error message')), null) + return this.CompileController.compile(this.req, this.res) + }) - return 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); - }); - - 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); - }); - }); - }); + 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("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(); + 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) + }) - this.CompileManager.syncFromCode = sinon.stub().callsArgWith(5, null, (this.pdfPositions = ["mock-positions"])); - return this.CompileController.syncFromCode(this.req, this.res, this.next); - }); + 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) + }) + }) - 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 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) + }) - return it("should return the positions", function() { - return this.res.json - .calledWith({ - pdf: this.pdfPositions - }) - .should.equal(true); - }); - }); + 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("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(); + 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.syncFromPdf = sinon.stub().callsArgWith(5, null, (this.codePositions = ["mock-positions"])); - return this.CompileController.syncFromPdf(this.req, this.res, this.next); - }); + 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 code", function() { - return this.CompileManager.syncFromPdf - .calledWith(this.project_id, undefined, this.page, this.h, this.v) - .should.equal(true); - }); + 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({ - code: this.codePositions - }) - .should.equal(true); - }); - }); + return it('should return the positions', function() { + return this.res.json + .calledWith({ + pdf: this.pdfPositions + }) + .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(); + 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.wordcount = sinon.stub().callsArgWith(4, null, (this.texcount = ["mock-texcount"])); - return this.CompileController.wordcount(this.req, this.res, this.next); - }); + this.CompileManager.syncFromPdf = sinon + .stub() + .callsArgWith(5, null, (this.codePositions = ['mock-positions'])) + return this.CompileController.syncFromPdf(this.req, this.res, this.next) + }) - it("should return the word count of a file", function() { - return this.CompileManager.wordcount - .calledWith(this.project_id, undefined, this.file, this.image) - .should.equal(true); - }); + 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 texcount info", function() { - return this.res.json - .calledWith({ - texcount: this.texcount - }) - .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) + }) + }) +}) diff --git a/services/clsi/test/unit/js/CompileManagerTests.js b/services/clsi/test/unit/js/CompileManagerTests.js index e798aec390..ae50bcc618 100644 --- a/services/clsi/test/unit/js/CompileManagerTests.js +++ b/services/clsi/test/unit/js/CompileManagerTests.js @@ -13,423 +13,539 @@ * 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"); +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() { return "/compile"; }, - clsi: { - docker: { - image: "SOMEIMAGE" - } - } - }), +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() { + return '/compile' + }, + clsi: { + docker: { + image: 'SOMEIMAGE' + } + } + }), - "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))) - ; - }); + '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); - }); + 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 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); - }); + 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 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); - }); + 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 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); - }); + 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); - }); - }); - }); + 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", - type: "pdf" - }]; - this.build_files = [{ - path: "output.log", - type: "log", - build: 1234 - }, { - path: "output.pdf", - type: "pdf", - build: 1234 - }]; - 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('doCompile', function() { + beforeEach(function() { + this.output_files = [ + { + path: 'output.log', + type: 'log' + }, + { + path: 'output.pdf', + type: 'pdf' + } + ] + this.build_files = [ + { + path: 'output.log', + type: 'log', + build: 1234 + }, + { + path: 'output.pdf', + type: 'pdf', + build: 1234 + } + ] + 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); - }); + 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 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 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", function() { - return this.OutputFileFinder.findOutputFiles - .calledWith(this.resources, this.compileDir) - .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); - }); + 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); - }); - }); + 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); - }); + 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); - }); - }); + 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); - }); + 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); - }); - }); + 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) + }) + }) - 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 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); - }); - }); - }); + 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", 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); - }); + 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); - }); + 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 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); - }); + 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); - }); + 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); + return it('should call the callback with an error from the stderr', function() { + this.callback.calledWith(new Error()).should.equal(true) - return this.callback.args[0][0].message.should.equal(`rm -r ${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id} failed: ${this.error}`); - }); - }); - }); + return this.callback.args[0][0].message.should.equal( + `rm -r ${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id} failed: ${this.error}` + ) + }) + }) + }) - describe("syncing", function() { - 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('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); - }); + 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( - `${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); - }); + 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( + `${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) + }) - return it("should call the callback with the parsed output", function() { - return this.callback - .calledWith(null, [{ - page: this.page, - h: this.h, - v: this.v, - height: this.height, - width: this.width - }]) - .should.equal(true); - }); - }); + return it('should call the callback with the parsed output', function() { + return this.callback + .calledWith(null, [ + { + page: this.page, + h: this.h, + v: this.v, + height: this.height, + width: this.width + } + ]) + .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); - }); + 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( - `${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); - }); + 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( + `${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) + }) - return it("should call the callback with the parsed output", function() { - return this.callback - .calledWith(null, [{ - file: this.file_name, - line: this.line, - column: this.column - }]) - .should.equal(true); - }); - }); - }); + return it('should call the callback with the parsed output', function() { + return this.callback + .calledWith(null, [ + { + file: this.file_name, + line: this.line, + column: this.column + } + ]) + .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(); + 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"; + 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); - }); + 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`]; + 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 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, - messages: "" - }) - .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, + messages: '' + }) + .should.equal(true) + }) + }) +}) diff --git a/services/clsi/test/unit/js/ContentTypeMapperTests.js b/services/clsi/test/unit/js/ContentTypeMapperTests.js index bbde292318..41fc37e486 100644 --- a/services/clsi/test/unit/js/ContentTypeMapperTests.js +++ b/services/clsi/test/unit/js/ContentTypeMapperTests.js @@ -10,73 +10,72 @@ * 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'); +const SandboxedModule = require('sandboxed-module') +const sinon = require('sinon') +require('chai').should() +const modulePath = require('path').join( + __dirname, + '../../../app/js/ContentTypeMapper' +) describe('ContentTypeMapper', function() { + beforeEach(function() { + return (this.ContentTypeMapper = SandboxedModule.require(modulePath)) + }) - beforeEach(function() { - return this.ContentTypeMapper = SandboxedModule.require(modulePath); - }); + return describe('map', function() { + it('should map .txt to text/plain', function() { + const content_type = this.ContentTypeMapper.map('example.txt') + return content_type.should.equal('text/plain') + }) - return describe('map', function() { + it('should map .csv to text/csv', function() { + const content_type = this.ContentTypeMapper.map('example.csv') + return content_type.should.equal('text/csv') + }) - it('should map .txt to text/plain', function() { - const content_type = this.ContentTypeMapper.map('example.txt'); - return content_type.should.equal('text/plain'); - }); + 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 map .csv to text/csv', function() { - const content_type = this.ContentTypeMapper.map('example.csv'); - return content_type.should.equal('text/csv'); - }); + it('should fall back to octet-stream', function() { + const content_type = this.ContentTypeMapper.map('example.unknown') + return content_type.should.equal('application/octet-stream') + }) - it('should map .pdf to application/pdf', function() { - const content_type = this.ContentTypeMapper.map('example.pdf'); - return content_type.should.equal('application/pdf'); - }); + describe('coercing web files to plain text', function() { + 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 fall back to octet-stream', function() { - const content_type = this.ContentTypeMapper.map('example.unknown'); - return content_type.should.equal('application/octet-stream'); - }); + it('should map .html to plain text', function() { + const content_type = this.ContentTypeMapper.map('example.html') + return content_type.should.equal('text/plain') + }) - describe('coercing web files to plain text', function() { + return it('should map .css to plain text', function() { + const content_type = this.ContentTypeMapper.map('example.css') + return content_type.should.equal('text/plain') + }) + }) - it('should map .js to plain text', function() { - const content_type = this.ContentTypeMapper.map('example.js'); - return content_type.should.equal('text/plain'); - }); + return describe('image files', function() { + it('should map .png to image/png', function() { + const content_type = this.ContentTypeMapper.map('example.png') + return content_type.should.equal('image/png') + }) - it('should map .html to plain text', function() { - const content_type = this.ContentTypeMapper.map('example.html'); - return content_type.should.equal('text/plain'); - }); + it('should map .jpeg to image/jpeg', function() { + const content_type = this.ContentTypeMapper.map('example.jpeg') + return content_type.should.equal('image/jpeg') + }) - return it('should map .css to plain text', function() { - const content_type = this.ContentTypeMapper.map('example.css'); - return content_type.should.equal('text/plain'); - }); - }); - - return describe('image files', function() { - - it('should map .png to image/png', function() { - const content_type = this.ContentTypeMapper.map('example.png'); - return content_type.should.equal('image/png'); - }); - - it('should map .jpeg to image/jpeg', function() { - const content_type = this.ContentTypeMapper.map('example.jpeg'); - return content_type.should.equal('image/jpeg'); - }); - - return it('should map .svg to text/plain to protect against XSS (SVG can execute JS)', function() { - const content_type = this.ContentTypeMapper.map('example.svg'); - return content_type.should.equal('text/plain'); - }); - }); - }); -}); + 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') + }) + }) + }) +}) diff --git a/services/clsi/test/unit/js/DockerLockManagerTests.js b/services/clsi/test/unit/js/DockerLockManagerTests.js index 155a2464fd..9dcf9dcc14 100644 --- a/services/clsi/test/unit/js/DockerLockManagerTests.js +++ b/services/clsi/test/unit/js/DockerLockManagerTests.js @@ -9,185 +9,244 @@ * 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'); +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", 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('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() + }) + } + })) + }) - 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(); - }); - }); + 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), - return it("should call the callback", function() { - return this.callback.calledWith(null,"hello","world").should.equal(true); - }); - }); + (err, ...args) => { + this.callback(err, ...Array.from(args)) + return 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(); - }); - }); + return it('should call the callback', function() { + return this.callback + .calledWith(null, 'hello', 'world') + .should.equal(true) + }) + }) - it("should call the first callback", function() { - return this.callback1.calledWith(null,"hello","world","one").should.equal(true); - }); + 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), - return it("should call the second callback", function() { - return this.callback2.calledWith(null,"hello","world","two").should.equal(true); - }); - }); + (err, ...args) => { + return this.callback1(err, ...Array.from(args)) + } + ) + return this.LockManager.runWithLock( + 'lock-two', + releaseLock => + setTimeout(() => releaseLock(null, 'hello', 'world', 'two'), 200), - return describe("with lock contention", function() { - 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(); - }); - }); + (err, ...args) => { + this.callback2(err, ...Array.from(args)) + return done() + } + ) + }) - it("should call the first callback", function() { - return this.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) + }) - return it("should call the second callback", function() { - return this.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", 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); - }); - }); + 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), - it("should call the first callback", function() { - return this.callback1.calledWith(null,"hello","world","one").should.equal(true); - }); + (err, ...args) => { + return this.callback1(err, ...Array.from(args)) + } + ) + return this.LockManager.runWithLock( + 'lock', + releaseLock => + setTimeout(() => releaseLock(null, 'hello', 'world', 'two'), 200), - return it("should call the second callback with an error", function() { - const error = sinon.match.instanceOf(Error); - return this.callback2.calledWith(error).should.equal(true); - }); - }); + (err, ...args) => { + this.callback2(err, ...Array.from(args)) + return done() + } + ) + }) - return describe("where the first lock is held longer than the max holding time", function() { - beforeEach(function(done) { - let doneTwo; - this.LockManager.MAX_LOCK_HOLD_TIME = 1000; - this.LockManager.MAX_LOCK_WAIT_TIME = 2000; - this.LockManager.LOCK_TEST_INTERVAL = 100; - this.callback1 = sinon.stub(); - this.callback2 = sinon.stub(); - let doneOne = (doneTwo = false); - const finish = function(key) { - if (key === 1) { doneOne = true; } - if (key === 2) { doneTwo = true; } - if (doneOne && doneTwo) { return done(); } - }; - this.LockManager.runWithLock("lock", releaseLock => - setTimeout(() => releaseLock(null, "hello", "world","one") - , 1500) - - , (err, ...args) => { - this.callback1(err,...Array.from(args)); - return finish(1); - }); - return this.LockManager.runWithLock("lock", releaseLock => - setTimeout(() => releaseLock(null, "hello", "world","two") - , 100) - - , (err, ...args) => { - this.callback2(err,...Array.from(args)); - return finish(2); - }); - }); + it('should call the first callback', function() { + return this.callback1 + .calledWith(null, 'hello', 'world', 'one') + .should.equal(true) + }) - it("should call the first callback", function() { - return this.callback1.calledWith(null,"hello","world","one").should.equal(true); - }); + return it('should call the second callback', function() { + return this.callback2 + .calledWith(null, 'hello', 'world', 'two') + .should.equal(true) + }) + }) - 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', 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', function() { + return this.callback1 + .calledWith(null, 'hello', 'world', 'one') + .should.equal(true) + }) + + return it('should call the second callback with an error', function() { + const error = sinon.match.instanceOf(Error) + return this.callback2.calledWith(error).should.equal(true) + }) + }) + + return describe('where the first lock is held longer than the max holding time', function() { + beforeEach(function(done) { + let doneTwo + this.LockManager.MAX_LOCK_HOLD_TIME = 1000 + this.LockManager.MAX_LOCK_WAIT_TIME = 2000 + this.LockManager.LOCK_TEST_INTERVAL = 100 + this.callback1 = sinon.stub() + this.callback2 = sinon.stub() + let doneOne = (doneTwo = false) + const finish = function(key) { + if (key === 1) { + doneOne = true + } + if (key === 2) { + doneTwo = true + } + if (doneOne && doneTwo) { + return done() + } + } + this.LockManager.runWithLock( + 'lock', + releaseLock => + setTimeout( + () => releaseLock(null, 'hello', 'world', 'one'), + 1500 + ), + + (err, ...args) => { + this.callback1(err, ...Array.from(args)) + return finish(1) + } + ) + return this.LockManager.runWithLock( + 'lock', + releaseLock => + setTimeout(() => releaseLock(null, 'hello', 'world', 'two'), 100), + + (err, ...args) => { + this.callback2(err, ...Array.from(args)) + return finish(2) + } + ) + }) + + it('should call the first callback', function() { + return this.callback1 + .calledWith(null, 'hello', 'world', 'one') + .should.equal(true) + }) + + return it('should call the second callback', function() { + return this.callback2 + .calledWith(null, 'hello', 'world', 'two') + .should.equal(true) + }) + }) + }) + }) +}) diff --git a/services/clsi/test/unit/js/DockerRunnerTests.js b/services/clsi/test/unit/js/DockerRunnerTests.js index 152b8b9637..e43a044c80 100644 --- a/services/clsi/test/unit/js/DockerRunnerTests.js +++ b/services/clsi/test/unit/js/DockerRunnerTests.js @@ -13,651 +13,826 @@ * 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": (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); } - } - } - } - ); - 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' - }); - }); - - 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", 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); - }); - }); - - 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, () => {}); - }); - - 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", function() { - return this.createContainer.called.should.equal(false); - }); - - it("should attach to the container", function() { - return this.DockerRunner.attachToContainer.called.should.equal(true); - }); - - it("should call the callback", function() { - return this.callback.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 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", 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", 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", 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); - }); - - 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); - }); - - 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", 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: "/totally-not-a-project-container", - Id: "some-random-id", - Created: nowInSeconds - (2 * oneHourInSeconds ) - }]; - 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); - return this.DockerRunner.destroyContainer - .calledWith("/project-old-container-name", "old-container-id") - .should.equal(true); - }); - - it("should not destroy new containers", function() { - return this.DockerRunner.destroyContainer - .calledWith("/project-new-container-name", "new-container-id") - .should.equal(false); - }); - - it("should not destroy non-project containers", function() { - return this.DockerRunner.destroyContainer - .calledWith("/totally-not-a-project-container", "some-random-id") - .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 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(); - }); - }); - }); - }); -}); +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': (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) + } + } + } + }) + 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' + }) + }) + + 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', 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) + }) + }) + + 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, + () => {} + ) + }) + + 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', function() { + return this.createContainer.called.should.equal(false) + }) + + it('should attach to the container', function() { + return this.DockerRunner.attachToContainer.called.should.equal(true) + }) + + it('should call the callback', function() { + return this.callback.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 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', 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', 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', 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) + }) + + 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) + }) + + 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', 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: '/totally-not-a-project-container', + Id: 'some-random-id', + Created: nowInSeconds - 2 * oneHourInSeconds + } + ] + 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) + return this.DockerRunner.destroyContainer + .calledWith('/project-old-container-name', 'old-container-id') + .should.equal(true) + }) + + it('should not destroy new containers', function() { + return this.DockerRunner.destroyContainer + .calledWith('/project-new-container-name', 'new-container-id') + .should.equal(false) + }) + + it('should not destroy non-project containers', function() { + return this.DockerRunner.destroyContainer + .calledWith('/totally-not-a-project-container', 'some-random-id') + .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 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() + }) + }) + }) + }) +}) diff --git a/services/clsi/test/unit/js/DraftModeManagerTests.js b/services/clsi/test/unit/js/DraftModeManagerTests.js index f270873a74..2c30b404bf 100644 --- a/services/clsi/test/unit/js/DraftModeManagerTests.js +++ b/services/clsi/test/unit/js/DraftModeManagerTests.js @@ -8,75 +8,79 @@ * 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'); +const SandboxedModule = require('sandboxed-module') +const sinon = require('sinon') +require('chai').should() +const modulePath = require('path').join( + __dirname, + '../../../app/js/DraftModeManager' +) describe('DraftModeManager', function() { - beforeEach(function() { - return this.DraftModeManager = SandboxedModule.require(modulePath, { requires: { - "fs": (this.fs = {}), - "logger-sharelatex": (this.logger = {log() {}}) - } - });}); - - describe("_injectDraftOption", function() { - it("should add draft option into documentclass with existing options", function() { - return this.DraftModeManager - ._injectDraftOption(`\ -\\documentclass[a4paper,foo=bar]{article}\ -`) - .should.equal(`\ -\\documentclass[draft,a4paper,foo=bar]{article}\ -`); - }); + beforeEach(function() { + return (this.DraftModeManager = SandboxedModule.require(modulePath, { + requires: { + fs: (this.fs = {}), + 'logger-sharelatex': (this.logger = { log() {} }) + } + })) + }) - return it("should add draft option into documentclass with no options", function() { - return this.DraftModeManager - ._injectDraftOption(`\ -\\documentclass{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}\ `) - .should.equal(`\ + }) + + return it('should add draft option into documentclass with no options', function() { + return this.DraftModeManager._injectDraftOption(`\ +\\documentclass{article}\ +`).should.equal(`\ \\documentclass[draft]{article}\ -`); - }); - }); - - return describe("injectDraftMode", function() { - beforeEach(function() { - this.filename = "/mock/filename.tex"; - this.callback = sinon.stub(); - const content = `\ +`) + }) + }) + + 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", function() { - return this.fs.readFile - .calledWith(this.filename, "utf8") - .should.equal(true); - }); - - it("should write the modified file", function() { - return this.fs.writeFile - .calledWith(this.filename, `\ +` + 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', function() { + return this.fs.readFile + .calledWith(this.filename, 'utf8') + .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); - }); - - return it("should call the callback", function() { - return this.callback.called.should.equal(true); - }); - }); -}); +` + ) + .should.equal(true) + }) + + return it('should call the callback', function() { + return this.callback.called.should.equal(true) + }) + }) +}) diff --git a/services/clsi/test/unit/js/LatexRunnerTests.js b/services/clsi/test/unit/js/LatexRunnerTests.js index 7fe8bc8164..b468b83183 100644 --- a/services/clsi/test/unit/js/LatexRunnerTests.js +++ b/services/clsi/test/unit/js/LatexRunnerTests.js @@ -9,103 +9,129 @@ * 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"); +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", function() { - beforeEach(function() { - let Timer; - this.LatexRunner = SandboxedModule.require(modulePath, { requires: { - "settings-sharelatex": (this.Settings = { - docker: { - socketPath: "/var/run/docker.sock" - } - }), - "logger-sharelatex": (this.logger = { log: sinon.stub(), error: sinon.stub() }), - "./Metrics": { - Timer: (Timer = class Timer { - done() {} - }) - }, - "./CommandRunner": (this.CommandRunner = {}) - } - }); +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': (this.logger = { + log: sinon.stub(), + error: sinon.stub() + }), + './Metrics': { + Timer: (Timer = class Timer { + done() {} + }) + }, + './CommandRunner': (this.CommandRunner = {}) + } + }) - 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'};}); + 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' }) + }) - return describe("runLatex", function() { - beforeEach(function() { - return this.CommandRunner.run = sinon.stub().callsArg(6); - }); + return describe('runLatex', function() { + beforeEach(function() { + return (this.CommandRunner.run = sinon.stub().callsArg(6)) + }) - 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); - }); + 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 + ) + }) - 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); - }); - }); + 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", 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); - }); + 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 + ) + }) - 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"); - }); - }); + 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') + }) + }) - 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"] - }, - this.callback); - }); + 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'] + }, + this.callback + ) + }) - 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"); - }); - }); - }); -}); + 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') + }) + }) + }) +}) diff --git a/services/clsi/test/unit/js/LockManagerTests.js b/services/clsi/test/unit/js/LockManagerTests.js index 6d1b15629a..ea6c3419f9 100644 --- a/services/clsi/test/unit/js/LockManagerTests.js +++ b/services/clsi/test/unit/js/LockManagerTests.js @@ -9,75 +9,85 @@ * 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"); +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": (this.Lockfile = {}) - } - }); - return this.lockFile = "/local/compile/directory/.project-lock"; - }); +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: (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(); - }); + 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); - }); + 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); - }); + it('should run the compile', function() { + return this.runner.calledWith().should.equal(true) + }) - return it("should call the callback with the response from the compile", function() { - return this.callback - .calledWithExactly(null, "foo", "bar") - .should.equal(true); - }); - }); + return it('should call the callback with the response from the compile', function() { + return this.callback + .calledWithExactly(null, 'foo', 'bar') + .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); - }); + 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); - }); + it('should not run the compile', function() { + return this.runner.called.should.equal(false) + }) - return it("should return an error", function() { - const error = new Errors.AlreadyCompilingError(); - return this.callback - .calledWithExactly(error) - .should.equal(true); - }); - }); - }); -}); + return it('should return an error', function() { + const error = new Errors.AlreadyCompilingError() + return this.callback.calledWithExactly(error).should.equal(true) + }) + }) + }) +}) diff --git a/services/clsi/test/unit/js/OutputFileFinderTests.js b/services/clsi/test/unit/js/OutputFileFinderTests.js index 5c956adb1d..e5f990466e 100644 --- a/services/clsi/test/unit/js/OutputFileFinderTests.js +++ b/services/clsi/test/unit/js/OutputFileFinderTests.js @@ -10,90 +10,96 @@ * 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"); +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", 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() } - } - }); - this.directory = "/test/dir"; - return this.callback = 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() } + } + }) + this.directory = '/test/dir' + return (this.callback = sinon.stub()) + }) - 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; - - }); - }); + 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 + } + ) + }) - 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" - }]); - }); -}); - - return describe("_getAllFiles", function() { - beforeEach(function() { - this.proc = new EventEmitter(); - this.proc.stdout = new EventEmitter(); - this.spawn.returns(this.proc); - this.directory = "/base/dir"; - return this.OutputFileFinder._getAllFiles(this.directory, this.callback); - }); - - describe("successfully", function() { - beforeEach(function() { - this.proc.stdout.emit( - "data", - ["/base/dir/main.tex", "/base/dir/chapters/chapter1.tex"].join("\n") + "\n" - ); - return this.proc.emit("close", 0); - }); - - return it("should call the callback with the relative file paths", function() { - return this.callback.calledWith( - null, - ["main.tex", "chapters/chapter1.tex"] - ).should.equal(true); - }); - }); + return 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' + } + ]) + }) + }) - return describe("when the directory doesn't exist", function() { - beforeEach(function() { - return this.proc.emit("close", 1); - }); - - return it("should call the callback with a blank array", function() { - return this.callback.calledWith( - null, - [] - ).should.equal(true); - }); - }); - }); -}); + return describe('_getAllFiles', function() { + beforeEach(function() { + this.proc = new EventEmitter() + this.proc.stdout = new EventEmitter() + this.spawn.returns(this.proc) + this.directory = '/base/dir' + return this.OutputFileFinder._getAllFiles(this.directory, this.callback) + }) + + describe('successfully', function() { + beforeEach(function() { + this.proc.stdout.emit( + 'data', + ['/base/dir/main.tex', '/base/dir/chapters/chapter1.tex'].join('\n') + + '\n' + ) + return this.proc.emit('close', 0) + }) + + return it('should call the callback with the relative file paths', function() { + return this.callback + .calledWith(null, ['main.tex', 'chapters/chapter1.tex']) + .should.equal(true) + }) + }) + + return describe("when the directory doesn't exist", function() { + beforeEach(function() { + return this.proc.emit('close', 1) + }) + + return it('should call the callback with a blank array', function() { + return this.callback.calledWith(null, []).should.equal(true) + }) + }) + }) +}) diff --git a/services/clsi/test/unit/js/OutputFileOptimiserTests.js b/services/clsi/test/unit/js/OutputFileOptimiserTests.js index 13b8d60c0a..4546f08431 100644 --- a/services/clsi/test/unit/js/OutputFileOptimiserTests.js +++ b/services/clsi/test/unit/js/OutputFileOptimiserTests.js @@ -10,139 +10,187 @@ * 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"); +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" : {} - } - }); - this.directory = "/test/dir"; - return this.callback = sinon.stub(); - }); +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': {} + } + }) + this.directory = '/test/dir' + return (this.callback = sinon.stub()) + }) - describe("optimiseFile", function() { - beforeEach(function() { - this.src = "./output.pdf"; - return this.dst = "./output.pdf"; - }); + 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); - }); + 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); - }); + 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); - }); - }); + 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); - }); + 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); - }); + 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 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); - }); + 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); - }); + 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 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); - }); + 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); - }); + 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 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 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); - }); + 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 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); - }); + 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 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 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); - }); + 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); - }); - }); - }); -}); + return it('should call the callback with a false result', function() { + return this.callback.calledWith(null, false).should.equal(true) + }) + }) + }) +}) diff --git a/services/clsi/test/unit/js/ProjectPersistenceManagerTests.js b/services/clsi/test/unit/js/ProjectPersistenceManagerTests.js index 5f77a80785..0d84fc2455 100644 --- a/services/clsi/test/unit/js/ProjectPersistenceManagerTests.js +++ b/services/clsi/test/unit/js/ProjectPersistenceManagerTests.js @@ -11,79 +11,90 @@ * 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"); +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('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" - ]; - 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); - }); + describe('clearExpiredProjects', function() { + beforeEach(function() { + this.project_ids = ['project-id-1', 'project-id-2'] + 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 clear each expired project', function() { + return Array.from(this.project_ids).map(project_id => + this.ProjectPersistenceManager.clearProjectFromCache + .calledWith(project_id) + .should.equal(true) + ) + }) - return it("should call the callback", function() { - return this.callback.called.should.equal(true); - }); - }); + return it('should call the callback', function() { + return this.callback.called.should.equal(true) + }) + }) - 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); - }); + 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 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 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); - }); + 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); - }); - }); -}); - + return it('should call the callback', function() { + return this.callback.called.should.equal(true) + }) + }) +}) diff --git a/services/clsi/test/unit/js/RequestParserTests.js b/services/clsi/test/unit/js/RequestParserTests.js index 725988f623..e2d8b02610 100644 --- a/services/clsi/test/unit/js/RequestParserTests.js +++ b/services/clsi/test/unit/js/RequestParserTests.js @@ -9,378 +9,412 @@ * 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"); +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" - }; - this.validRequest = { - compile: { - token: "token-123", - options: { - imageName: "basicImageName/here:2017-1", - compiler: "pdflatex", - timeout: 42 - }, - resources: [] - } - }; - return this.RequestParser = SandboxedModule.require(modulePath, { requires: { - "settings-sharelatex": (this.settings = {}) - } - });}); +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' + } + this.validRequest = { + compile: { + token: 'token-123', + options: { + imageName: 'basicImageName/here:2017-1', + compiler: 'pdflatex', + timeout: 42 + }, + resources: [] + } + } + return (this.RequestParser = SandboxedModule.require(modulePath, { + requires: { + 'settings-sharelatex': (this.settings = {}) + } + })) + }) - afterEach(function() { return tk.reset(); }); + afterEach(function() { + return tk.reset() + }) - describe("without a top level object", function() { - beforeEach(function() { - return this.RequestParser.parse([], this.callback); - }); + 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); - }); - }); + 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); - }); + 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); - }); - }); + 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); - }); + 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); - }); - }); + 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; - - }); - }); + 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"); - }); - }); + 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; - - }); - }); + 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"); - }); - }); + 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; - - }); - }); + 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"]); - }); -}); + 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; - - }); - }); + 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([]); - }); -}); + 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; - - }); - }); + 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); - }); - }); + 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; - - }); - }); + 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); - }); - }); + 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; - - }); - }); + 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); - }); - }); + 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); - }); + 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); - }); - }); + 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];}); + 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); - }); - }); + 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); - }); + 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: "+ - this.validResource.modified - ) - .should.equal(true); - }); - }); + return it('should return an error', function() { + return this.callback + .calledWith( + 'resource modified date could not be understood: ' + + this.validResource.modified + ) + .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];}); + 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)); - }); - }); + 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); - }); + 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); - }); - }); + 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); - }); + 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); - }); - }); + 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); - }); + 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); - }); - }); + 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];}); + 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); - }); - }); + 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];}); + 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); - }); - }); + 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];}); + 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"); - }); - }); + 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];}); + 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); - }); - }); + 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); - }); + 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); - }); - }); + 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" - }); - this.RequestParser.parse(this.validRequest, this.callback); - return this.data = this.callback.args[0][1];}); + 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' + }) + 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); - }); + 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); - }); - }); + 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];}); + 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); - }); - }); + 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];}); + 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 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 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); - }); - }); -}); + return it('should return an error', function() { + return this.callback + .calledWith('syncType attribute should be one of: full, incremental') + .should.equal(true) + }) + }) +}) diff --git a/services/clsi/test/unit/js/ResourceStateManagerTests.js b/services/clsi/test/unit/js/ResourceStateManagerTests.js index fe52cc584d..c0e89ef7ad 100644 --- a/services/clsi/test/unit/js/ResourceStateManagerTests.js +++ b/services/clsi/test/unit/js/ResourceStateManagerTests.js @@ -9,145 +9,200 @@ * 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"); +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"} - ]; - 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('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' } + ] + 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('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); - }); + 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); - }); + 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 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); - }); + 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 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); - }); + 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); - }); - }); - }); + return it('should call the callback', function() { + return this.callback.called.should.equal(true) + }) + }) + }) - describe("checkProjectStateMatches", function() { + describe('checkProjectStateMatches', function() { + describe('when the state matches', function() { + beforeEach(function() { + this.SafeReader.readFile = sinon + .stub() + .callsArgWith(3, null, this.resourceFileContents) + return this.ResourceStateManager.checkProjectStateMatches( + this.state, + this.basePath, + this.callback + ) + }) - describe("when the state matches", function() { - 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) + }) - 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 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 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 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 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) + }) + }) - 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 + ) + }) - 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 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); - }); - }); - }); -}); + 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) + }) + }) + }) +}) diff --git a/services/clsi/test/unit/js/ResourceWriterTests.js b/services/clsi/test/unit/js/ResourceWriterTests.js index 830954739f..189908dc6b 100644 --- a/services/clsi/test/unit/js/ResourceWriterTests.js +++ b/services/clsi/test/unit/js/ResourceWriterTests.js @@ -10,405 +10,491 @@ * 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"); +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": (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('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': (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" - ]; - 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); - }); + describe('syncResourcesToDisk on a full request', function() { + beforeEach(function() { + this.resources = ['resource-1-mock', 'resource-2-mock', 'resource-3-mock'] + 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 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 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); - }); + 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); - }); - }); + 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" - ]; - 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: (this.syncState = "1234567890abcdef"), - resources: this.resources - }, this.basePath, this.callback); - }); + describe('syncResourcesToDisk on an incremental update', function() { + beforeEach(function() { + this.resources = ['resource-1-mock'] + 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: (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 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 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 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)); - }); + 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); - }); - }); + 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" - ]; - this.ResourceStateManager.checkProjectStateMatches = sinon.stub().callsArgWith(2, (this.error = new Error())); - return this.ResourceWriter.syncResourcesToDisk({ - project_id: this.project_id, - syncType: "incremental", - syncState: (this.syncState = "1234567890abcdef"), - resources: this.resources - }, this.basePath, this.callback); - }); + describe('syncResourcesToDisk on an incremental update when the state does not match', function() { + beforeEach(function() { + this.resources = ['resource-1-mock'] + this.ResourceStateManager.checkProjectStateMatches = sinon + .stub() + .callsArgWith(2, (this.error = new Error())) + return this.ResourceWriter.syncResourcesToDisk( + { + project_id: this.project_id, + syncType: 'incremental', + 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); - }); + 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); - }); - }); + 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', + type: 'tex' + }, + { + path: 'extra.aux', + type: 'aux' + }, + { + path: 'cache/_chunk1' + }, + { + path: 'figures/image-eps-converted-to.pdf', + type: 'pdf' + }, + { + path: 'foo/main-figure0.md5', + type: 'md5' + }, + { + path: 'foo/main-figure0.dpth', + type: 'dpth' + }, + { + path: 'foo/main-figure0.pdf', + type: 'pdf' + }, + { + path: '_minted-main/default-pyg-prefix.pygstyle', + type: 'pygstyle' + }, + { + path: '_minted-main/default.pygstyle', + type: 'pygstyle' + }, + { + path: + '_minted-main/35E248B60965545BD232AE9F0FE9750D504A7AF0CD3BAA7542030FC560DFCC45.pygtex', + type: 'pygtex' + }, + { + path: '_markdown_main/30893013dec5d869a415610079774c2f.md.tex', + type: 'tex' + } + ] + this.resources = 'mock-resources' + this.OutputFileFinder.findOutputFiles = sinon + .stub() + .callsArgWith(2, null, this.output_files) + this.ResourceWriter._deleteFileIfNotDirectory = sinon.stub().callsArg(1) + return this.ResourceWriter._removeExtraneousFiles( + this.resources, + this.basePath, + this.callback + ) + }) - describe("_removeExtraneousFiles", function() { - beforeEach(function() { - this.output_files = [{ - path: "output.pdf", - type: "pdf" - }, { - path: "extra/file.tex", - type: "tex" - }, { - path: "extra.aux", - type: "aux" - }, { - path: "cache/_chunk1" - },{ - path: "figures/image-eps-converted-to.pdf", - type: "pdf" - },{ - path: "foo/main-figure0.md5", - type: "md5" - }, { - path: "foo/main-figure0.dpth", - type: "dpth" - }, { - path: "foo/main-figure0.pdf", - type: "pdf" - }, { - path: "_minted-main/default-pyg-prefix.pygstyle", - type: "pygstyle" - }, { - path: "_minted-main/default.pygstyle", - type: "pygstyle" - }, { - path: "_minted-main/35E248B60965545BD232AE9F0FE9750D504A7AF0CD3BAA7542030FC560DFCC45.pygtex", - type: "pygtex" - }, { - path: "_markdown_main/30893013dec5d869a415610079774c2f.md.tex", - type: "tex" - }]; - this.resources = "mock-resources"; - this.OutputFileFinder.findOutputFiles = sinon.stub().callsArgWith(2, null, this.output_files); - this.ResourceWriter._deleteFileIfNotDirectory = sinon.stub().callsArg(1); - return this.ResourceWriter._removeExtraneousFiles(this.resources, this.basePath, this.callback); - }); + it('should find the existing output files', function() { + return this.OutputFileFinder.findOutputFiles + .calledWith(this.resources, this.basePath) + .should.equal(true) + }) - 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 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 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 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", function() { - return this.ResourceWriter._deleteFileIfNotDirectory - .calledWith(path.join(this.basePath, "cache/_chunk1")) - .should.equal(false); - }); + it('should not delete the knitr cache file', function() { + return this.ResourceWriter._deleteFileIfNotDirectory + .calledWith(path.join(this.basePath, 'cache/_chunk1')) + .should.equal(false) + }) - it("should not delete the epstopdf converted files", function() { - return this.ResourceWriter._deleteFileIfNotDirectory - .calledWith(path.join(this.basePath, "figures/image-eps-converted-to.pdf")) - .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 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 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 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 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 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 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 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); - }); + 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); - }); - }); + 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() - }; - this.UrlCache.downloadUrlToFile = sinon.stub().callsArgWith(4, "fake error downloading file"); - return this.ResourceWriter._writeResourceToDisk(this.project_id, this.resource, this.basePath, this.callback); - }); + 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() + } + 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 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", function() { - return this.callback.called.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) + }) - return it("should not return an error if the resource writer errored", function() { - return should.not.exist(this.callback.args[0][0]); - }); - }); + it('should call the callback', function() { + return this.callback.called.should.equal(true) + }) - describe("with a content based resource", function() { - beforeEach(function() { - this.resource = { - path: "main.tex", - content: "Hello world" - }; - this.fs.writeFile = sinon.stub().callsArg(2); - return this.ResourceWriter._writeResourceToDisk(this.project_id, this.resource, this.basePath, this.callback); - }); + return it('should not return an error if the resource writer errored', function() { + return should.not.exist(this.callback.args[0][0]) + }) + }) - it("should ensure the directory exists", function() { - return this.mkdirp - .calledWith(path.dirname(path.join(this.basePath, this.resource.path))) - .should.equal(true); - }); + describe('with a content based resource', function() { + beforeEach(function() { + this.resource = { + path: 'main.tex', + content: 'Hello world' + } + this.fs.writeFile = sinon.stub().callsArg(2) + return this.ResourceWriter._writeResourceToDisk( + this.project_id, + this.resource, + this.basePath, + this.callback + ) + }) - it("should write the contents to disk", function() { - return this.fs.writeFile - .calledWith(path.join(this.basePath, this.resource.path), this.resource.content) - .should.equal(true); - }); - - return it("should call the callback", function() { - return this.callback.called.should.equal(true); - }); - }); + it('should ensure the directory exists', function() { + return this.mkdirp + .calledWith( + path.dirname(path.join(this.basePath, this.resource.path)) + ) + .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" - }; - this.fs.writeFile = sinon.stub().callsArg(2); - return this.ResourceWriter._writeResourceToDisk(this.project_id, this.resource, this.basePath, this.callback); - }); + it('should write the contents to disk', function() { + return this.fs.writeFile + .calledWith( + path.join(this.basePath, this.resource.path), + this.resource.content + ) + .should.equal(true) + }) - it("should not write 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) + }) + }) - return it("should return an error", function() { - return this.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 describe('with a file path that breaks out of the root folder', function() { + beforeEach(function() { + this.resource = { + path: '../../main.tex', + content: 'Hello world' + } + this.fs.writeFile = sinon.stub().callsArg(2) + return this.ResourceWriter._writeResourceToDisk( + this.project_id, + this.resource, + this.basePath, + this.callback + ) + }) - return it("should return the joined path", function() { - return this.callback.calledWith(null, "foo/bar") - .should.equal(true); - }); - }); + it('should not write to disk', function() { + return this.fs.writeFile.called.should.equal(false) + }) - 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 it("should return an error", function() { - return this.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 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 the joined path', function() { + return this.callback.calledWith(null, 'foo/bar').should.equal(true) + }) + }) - return it("should return an error", function() { - return this.callback.calledWith(new Error("resource path is outside root directory")) - .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) + }) + }) + }) +}) diff --git a/services/clsi/test/unit/js/StaticServerForbidSymlinksTests.js b/services/clsi/test/unit/js/StaticServerForbidSymlinksTests.js index e754ea7547..b9545a4ce1 100644 --- a/services/clsi/test/unit/js/StaticServerForbidSymlinksTests.js +++ b/services/clsi/test/unit/js/StaticServerForbidSymlinksTests.js @@ -9,217 +9,229 @@ * 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"); +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() { +describe('StaticServerForbidSymlinks', function() { + beforeEach(function() { + this.settings = { + path: { + compilesDir: '/compiles/here' + } + } - beforeEach(function() { + this.fs = {} + this.ForbidSymlinks = SandboxedModule.require(modulePath, { + requires: { + 'settings-sharelatex': this.settings, + 'logger-sharelatex': { + log() {}, + warn() {}, + error() {} + }, + fs: this.fs + } + }) - this.settings = { - path: { - compilesDir: "/compiles/here" - } - }; + this.dummyStatic = (rootDir, options) => (req, res, next) => + // console.log "dummyStatic serving file", rootDir, "called with", req.url + // serve it + next() - this.fs = {}; - this.ForbidSymlinks = SandboxedModule.require(modulePath, { requires: { - "settings-sharelatex":this.settings, - "logger-sharelatex": { - log() {}, - warn() {}, - error() {} - }, - "fs":this.fs - } - } - ); + this.StaticServerForbidSymlinks = this.ForbidSymlinks( + this.dummyStatic, + this.settings.path.compilesDir + ) + this.req = { + params: { + project_id: '12345' + } + } - this.dummyStatic = (rootDir, options) => - (req, res, next) => - // console.log "dummyStatic serving file", rootDir, "called with", req.url - // serve it - next() - - ; + this.res = {} + return (this.req.url = '/12345/output.pdf') + }) - this.StaticServerForbidSymlinks = this.ForbidSymlinks(this.dummyStatic, this.settings.path.compilesDir); - this.req = { - params: { - project_id:"12345" - } - }; + 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` + )) + }) - this.res = {}; - return this.req.url = "/12345/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` + )) + }) - 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 send a 404', function(done) { + this.res.sendStatus = function(resCode) { + resCode.should.equal(404) + return done() + } + return this.StaticServerForbidSymlinks(this.req, this.res) + }) + }) - 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 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 missing file", function() { - beforeEach(function() { - return this.fs.realpath = sinon.stub().callsArgWith(1, {code: 'ENOENT'}, `${this.settings.path.compilesDir}/${this.req.params.project_id}/unknown.pdf`); - }); + describe('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); - }); - }); + 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') + }) - 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) + }) + }) - 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 relative file", function() { - beforeEach(function() { - return this.req.url = "/12345/../67890/output.pdf"; - }); + 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); - }); - }); + 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') + }) - 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) + }) + }) - 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 file containing an empty path", function() { - beforeEach(function() { - return this.req.url = "/12345/foo//output.pdf"; - }); + 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 send a 404", function(done){ - this.res.sendStatus = function(resCode){ - resCode.should.equal(404); - return done(); - }; - return this.StaticServerForbidSymlinks(this.req, this.res); - }); - }); + 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 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); - }); - }); -}); + 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) + }) + }) +}) diff --git a/services/clsi/test/unit/js/TikzManager.js b/services/clsi/test/unit/js/TikzManager.js index f35d261988..1a9874cbe0 100644 --- a/services/clsi/test/unit/js/TikzManager.js +++ b/services/clsi/test/unit/js/TikzManager.js @@ -8,148 +8,180 @@ * 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'); +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() {}}) - } - });}); + 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('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); - }); + 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 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}`); - }); + 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); - }); + 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); - }); + 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 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); - }); + 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); - }); + 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 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); - }); + 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); - }); + 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 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 = `\ + 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); - }); +` + 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('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 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); - }); + 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); - }); - }); -}); + return it('should call the callback', function() { + return this.callback.called.should.equal(true) + }) + }) +}) diff --git a/services/clsi/test/unit/js/UrlCacheTests.js b/services/clsi/test/unit/js/UrlCacheTests.js index 7f024507f2..f056a6eb22 100644 --- a/services/clsi/test/unit/js/UrlCacheTests.js +++ b/services/clsi/test/unit/js/UrlCacheTests.js @@ -10,259 +10,347 @@ * 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"); +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", function() { - beforeEach(function() { - this.lastModified = new Date(); - return this.lastModifiedRoundedToSeconds = new Date(Math.floor(this.lastModified.getTime() / 1000) * 1000); - }); +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("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); - }); + describe('_doesUrlNeedDownloading', function() { + beforeEach(function() { + this.lastModified = new Date() + return (this.lastModifiedRoundedToSeconds = new Date( + Math.floor(this.lastModified.getTime() / 1000) * 1000 + )) + }) - return it("should return the callback with true", function() { - return this.callback.calledWith(null, true).should.equal(true); - }); - }); + 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 describe("when URL does exist in cache", function() { - beforeEach(function() { - this.urlDetails = {}; - return this.UrlCache._findUrlDetails = sinon.stub().callsArgWith(2, null, this.urlDetails); - }); + return it('should return the callback with true', function() { + return this.callback.calledWith(null, true).should.equal(true) + }) + }) - 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); - }); + return describe('when URL does exist in cache', function() { + beforeEach(function() { + this.urlDetails = {} + return (this.UrlCache._findUrlDetails = sinon + .stub() + .callsArgWith(2, null, this.urlDetails)) + }) - it("should get the url details", function() { - return this.UrlCache._findUrlDetails - .calledWith(this.project_id, this.url) - .should.equal(true); - }); + 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 + ) + }) - return it("should return the callback with true", function() { - return this.callback.calledWith(null, true).should.equal(true); - }); - }); + it('should get the url details', function() { + return this.UrlCache._findUrlDetails + .calledWith(this.project_id, this.url) + .should.equal(true) + }) - describe("when the cached modified date is more recent than the modified date", function() { - 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 true', function() { + return this.callback.calledWith(null, true).should.equal(true) + }) + }) - 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 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 + ) + }) - 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) + }) + }) - 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 + ) + }) - 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 false', function() { + return this.callback.calledWith(null, false).should.equal(true) + }) + }) - return it("should return the callback with true", function() { - return this.callback.calledWith(null, true).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 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) + }) + }) - 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 + ) + }) - 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", 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); - }); + return it('should return the callback with true', function() { + return this.callback.calledWith(null, true).should.equal(true) + }) + }) + }) + }) - it("should check that the url needs downloading", function() { - return this.UrlCache._doesUrlNeedDownloading - .calledWith(this.project_id, this.url, this.lastModifiedRoundedToSeconds) - .should.equal(true); - }); + describe('_ensureUrlIsInCache', function() { + beforeEach(function() { + this.UrlFetcher.pipeUrlToFile = sinon.stub().callsArg(2) + return (this.UrlCache._updateOrCreateUrlDetails = sinon + .stub() + .callsArg(3)) + }) - 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); - }); - + 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 update the database entry", function() { - return this.UrlCache._updateOrCreateUrlDetails - .calledWith(this.project_id, this.url, this.lastModifiedRoundedToSeconds) - .should.equal(true); - }); + it('should check that the url needs downloading', function() { + return this.UrlCache._doesUrlNeedDownloading + .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); - }); - }); + 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) + }) - 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", function() { - return this.UrlFetcher.pipeUrlToFile - .called.should.equal(false); - }); + 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 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); - }); + 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 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 not download the URL to the cache file', function() { + return this.UrlFetcher.pipeUrlToFile.called.should.equal(false) + }) - 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 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 it("should call the callback", function() { - return this.callback.called.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 + ) + }) - 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 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 delete the cache file", function() { - return this.fs.unlink - .calledWith(this.UrlCache._cacheFilePathForUrl(this.project_id, this.url)) - .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); - }); - }); + 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); - }); + 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 file on disk", function() { - return this.UrlCache._deleteUrlCacheFromDisk - .calledWith(this.project_id, this.url) - .should.equal(true); - }); + it('should delete the cache file', function() { + return this.fs.unlink + .calledWith( + this.UrlCache._cacheFilePathForUrl(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 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 + ) + }) - return describe("clearProject", function() { - beforeEach(function() { - this.urls = [ - "www.example.com/file1", - "www.example.com/file2" - ]; - this.UrlCache._findAllUrlsInProject = sinon.stub().callsArgWith(1, null, this.urls); - this.UrlCache._clearUrlFromCache = sinon.stub().callsArg(2); - return this.UrlCache.clearProject(this.project_id, this.callback); - }); + it('should delete the file on disk', function() { + return this.UrlCache._deleteUrlCacheFromDisk + .calledWith(this.project_id, this.url) + .should.equal(true) + }) - 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)); - }); + 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 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'] + 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) + }) + }) +}) diff --git a/services/clsi/test/unit/js/UrlFetcherTests.js b/services/clsi/test/unit/js/UrlFetcherTests.js index 453a386755..e5ce52b95a 100644 --- a/services/clsi/test/unit/js/UrlFetcherTests.js +++ b/services/clsi/test/unit/js/UrlFetcherTests.js @@ -8,152 +8,165 @@ * 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"); +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 = {}) - } - });}); +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); - }); + 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(); - }); + 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"); - }); + 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 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() + }) - 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') + }) - 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 request the URL", function() { - return this.request.get - .calledWith(sinon.match({"url": this.url})) - .should.equal(true); - }); + it('should pipe the URL to the file', function() { + return this.urlStream.pipe + .calledWith(this.fileStream) + .should.equal(true) + }) - it("should open the file for writing", function() { - return this.fs.createWriteStream - .calledWith(this.path) - .should.equal(true); - }); + return it('should call the callback', function() { + return this.callback.called.should.equal(true) + }) + }) - it("should pipe the URL to the file", function() { - return this.urlStream.pipe - .calledWith(this.fileStream) - .should.equal(true); - }); - - 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') + }) - 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) + }) + }) - 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); - }); - }); + 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')) + ) + }) - 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); - }); - }); - }); -}); + 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) + }) + }) + }) +})