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

This commit is contained in:
decaffeinate
2020-02-19 12:15:08 +01:00
committed by mserranom
parent 18e6b4715d
commit 79a0891fee
18 changed files with 3291 additions and 2401 deletions

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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