overleaf/services/web/test/UnitTests/coffee/Compile/ClsiManagerTests.coffee

451 lines
16 KiB
CoffeeScript
Raw Normal View History

2014-02-12 05:23:40 -05:00
sinon = require('sinon')
chai = require('chai')
should = chai.should()
expect = chai.expect
modulePath = "../../../../app/js/Features/Compile/ClsiManager.js"
SandboxedModule = require('sandboxed-module')
describe "ClsiManager", ->
beforeEach ->
2016-04-19 11:48:51 -04:00
@jar = {cookie:"stuff"}
@ClsiCookieManager =
2016-04-19 11:48:51 -04:00
getCookieJar: sinon.stub().callsArgWith(1, null, @jar)
setServerId: sinon.stub().callsArgWith(2)
_getServerId:sinon.stub()
2017-08-08 06:38:31 -04:00
@ClsiStateManager =
computeHash: sinon.stub().callsArgWith(2, null, "01234567890abcdef")
2016-06-02 08:09:11 -04:00
@ClsiFormatChecker =
checkRecoursesForProblems:sinon.stub().callsArgWith(1)
2014-02-12 05:23:40 -05:00
@ClsiManager = SandboxedModule.require modulePath, requires:
"settings-sharelatex": @settings =
apis:
filestore:
url: "filestore.example.com"
secret: "secret"
clsi:
url: "http://clsi.example.com"
clsi_priority:
url: "https://clsipremium.example.com"
2014-02-12 05:23:40 -05:00
"../../models/Project": Project: @Project = {}
2014-05-06 07:54:26 -04:00
"../Project/ProjectEntityHandler": @ProjectEntityHandler = {}
2017-08-08 06:38:31 -04:00
"../Project/ProjectGetter": @ProjectGetter = {}
"../DocumentUpdater/DocumentUpdaterHandler": @DocumentUpdaterHandler =
getProjectDocsIfMatch: sinon.stub().callsArgWith(2,null,null)
"./ClsiCookieManager": @ClsiCookieManager
2017-08-08 06:38:31 -04:00
"./ClsiStateManager": @ClsiStateManager
"logger-sharelatex": @logger = { log: sinon.stub(), error: sinon.stub(), err: sinon.stub(), warn: sinon.stub() }
2016-04-19 11:48:51 -04:00
"request": @request = sinon.stub()
2016-06-02 08:09:11 -04:00
"./ClsiFormatChecker": @ClsiFormatChecker
2017-08-09 06:34:16 -04:00
"metrics-sharelatex": @Metrics =
Timer: class Timer
done: sinon.stub()
inc: sinon.stub()
2014-02-12 05:23:40 -05:00
@project_id = "project-id"
2016-05-31 11:54:28 -04:00
@user_id = "user-id"
2014-02-12 05:23:40 -05:00
@callback = sinon.stub()
describe "sendRequest", ->
beforeEach ->
2014-06-01 12:16:05 -04:00
@ClsiManager._buildRequest = sinon.stub().callsArgWith(2, null, @request = "mock-request")
@ClsiCookieManager._getServerId.callsArgWith(1, null, "clsi3")
2014-02-12 05:23:40 -05:00
describe "with a successful compile", ->
beforeEach ->
2016-05-31 11:54:28 -04:00
@ClsiManager._postToClsi = sinon.stub().callsArgWith(4, null, {
2014-02-12 05:23:40 -05:00
compile:
status: @status = "success"
2014-02-12 05:23:40 -05:00
outputFiles: [{
2016-05-31 11:54:28 -04:00
url: "#{@settings.apis.clsi.url}/project/#{@project_id}/user/#{@user_id}/build/1234/output/output.pdf"
2016-05-26 11:26:58 -04:00
path: "output.pdf"
2014-02-12 05:23:40 -05:00
type: "pdf"
build: 1234
2014-02-12 05:23:40 -05:00
},{
2016-05-31 11:54:28 -04:00
url: "#{@settings.apis.clsi.url}/project/#{@project_id}/user/#{@user_id}/build/1234/output/output.log"
2016-05-26 11:26:58 -04:00
path: "output.log"
2014-02-12 05:23:40 -05:00
type: "log"
build: 1234
2014-02-12 05:23:40 -05:00
}]
})
2016-05-31 11:54:28 -04:00
@ClsiManager.sendRequest @project_id, @user_id, {compileGroup:"standard"}, @callback
2014-02-12 05:23:40 -05:00
2014-05-06 07:54:26 -04:00
it "should build the request", ->
2014-02-12 05:23:40 -05:00
@ClsiManager._buildRequest
2014-05-06 07:54:26 -04:00
.calledWith(@project_id)
2014-02-12 05:23:40 -05:00
.should.equal true
it "should send the request to the CLSI", ->
@ClsiManager._postToClsi
2016-05-31 11:54:28 -04:00
.calledWith(@project_id, @user_id, @request, "standard")
2014-02-12 05:23:40 -05:00
.should.equal true
it "should call the callback with the status and output files", ->
outputFiles = [{
2016-05-31 11:54:28 -04:00
url: "/project/#{@project_id}/user/#{@user_id}/build/1234/output/output.pdf"
2014-02-12 05:23:40 -05:00
path: "output.pdf"
type: "pdf"
build: 1234
2014-02-12 05:23:40 -05:00
},{
2016-05-31 11:54:28 -04:00
url: "/project/#{@project_id}/user/#{@user_id}/build/1234/output/output.log"
2014-02-12 05:23:40 -05:00
path: "output.log"
type: "log"
build: 1234
2014-02-12 05:23:40 -05:00
}]
@callback.calledWith(null, @status, outputFiles).should.equal true
2014-02-12 05:23:40 -05:00
describe "with a failed compile", ->
beforeEach ->
2016-05-31 11:54:28 -04:00
@ClsiManager._postToClsi = sinon.stub().callsArgWith(4, null, {
2014-02-12 05:23:40 -05:00
compile:
status: @status = "failure"
2014-02-12 05:23:40 -05:00
})
2016-05-31 11:54:28 -04:00
@ClsiManager.sendRequest @project_id, @user_id, {}, @callback
2014-02-12 05:23:40 -05:00
it "should call the callback with a failure statue", ->
@callback.calledWith(null, @status).should.equal true
2014-02-12 05:23:40 -05:00
2017-08-15 09:35:02 -04:00
describe "with a sync conflict", ->
beforeEach ->
@ClsiManager.sendRequestOnce = sinon.stub()
@ClsiManager.sendRequestOnce.withArgs(@project_id, @user_id, {syncType:"full"}).callsArgWith(3, null, @status = "success")
@ClsiManager.sendRequestOnce.withArgs(@project_id, @user_id, {}).callsArgWith(3, null, "conflict")
@ClsiManager.sendRequest @project_id, @user_id, {}, @callback
it "should call the sendRequestOnce method twice", ->
@ClsiManager.sendRequestOnce.calledTwice.should.equal true
2017-08-17 10:36:52 -04:00
it "should call the sendRequestOnce method with syncType:full", ->
@ClsiManager.sendRequestOnce.calledWith(@project_id, @user_id, {syncType:"full"}).should.equal true
2017-08-15 09:35:02 -04:00
2017-08-17 10:36:52 -04:00
it "should call the sendRequestOnce method without syncType:full", ->
@ClsiManager.sendRequestOnce.calledWith(@project_id, @user_id, {}).should.equal true
2017-08-15 09:35:02 -04:00
it "should call the callback with a success status", ->
@callback.calledWith(null, @status, ).should.equal true
describe "when the resources fail the precompile check", ->
beforeEach ->
@ClsiFormatChecker.checkRecoursesForProblems = sinon.stub().callsArgWith(1, new Error("failed"))
@ClsiManager._postToClsi = sinon.stub().callsArgWith(4, null, {
compile:
status: @status = "failure"
})
@ClsiManager.sendRequest @project_id, @user_id, {}, @callback
it "should call the callback only once", ->
@callback.calledOnce.should.equal true
it "should call the callback with an error", ->
@callback.calledWithExactly(new Error("failed")).should.equal true
describe "deleteAuxFiles", ->
beforeEach ->
2016-04-19 11:48:51 -04:00
@ClsiManager._makeRequest = sinon.stub().callsArg(2)
@DocumentUpdaterHandler.clearProjectState = sinon.stub().callsArg(1)
describe "with the standard compileGroup", ->
beforeEach ->
2016-05-31 11:54:28 -04:00
@ClsiManager.deleteAuxFiles @project_id, @user_id, {compileGroup: "standard"}, @callback
it "should call the delete method in the standard CLSI", ->
2016-04-19 11:48:51 -04:00
@ClsiManager._makeRequest
2016-05-31 11:54:28 -04:00
.calledWith(@project_id, { method:"DELETE", url:"#{@settings.apis.clsi.url}/project/#{@project_id}/user/#{@user_id}"})
.should.equal true
it "should clear the project state from the docupdater", ->
@DocumentUpdaterHandler.clearProjectState
.calledWith(@project_id)
.should.equal true
it "should call the callback", ->
@callback.called.should.equal true
2014-02-12 05:23:40 -05:00
describe "_buildRequest", ->
beforeEach ->
@project =
_id: @project_id
compiler: @compiler = "latex"
rootDoc_id: "mock-doc-id-1"
2016-01-14 11:35:42 -05:00
imageName: @image = "mock-image-name"
2014-05-06 07:54:26 -04:00
@docs = {
"/main.tex": @doc_1 = {
name: "main.tex"
_id: "mock-doc-id-1"
lines: ["Hello", "world"]
},
"/chapters/chapter1.tex": @doc_2 = {
name: "chapter1.tex"
_id: "mock-doc-id-2"
lines: [
"Chapter 1"
2014-02-12 05:23:40 -05:00
]
2014-05-06 07:54:26 -04:00
}
}
2014-02-12 05:23:40 -05:00
2014-05-06 07:54:26 -04:00
@files = {
"/images/image.png": @file_1 = {
name: "image.png"
_id: "mock-file-id-1"
created: new Date()
}
}
@Project.findById = sinon.stub().callsArgWith(2, null, @project)
@ProjectEntityHandler.getAllDocs = sinon.stub().callsArgWith(1, null, @docs)
@ProjectEntityHandler.getAllFiles = sinon.stub().callsArgWith(1, null, @files)
2017-08-08 06:38:31 -04:00
@ProjectGetter.getProject = sinon.stub().callsArgWith(2, null, @project)
2017-07-28 10:01:05 -04:00
@DocumentUpdaterHandler.flushProjectToMongo = sinon.stub().callsArgWith(1, null)
2014-02-12 05:23:40 -05:00
describe "with a valid project", ->
beforeEach (done) ->
2014-10-16 13:43:48 -04:00
@ClsiManager._buildRequest @project_id, {timeout:100}, (error, request) =>
2014-02-12 05:23:40 -05:00
@request = request
done()
2014-05-06 07:54:26 -04:00
it "should get the project with the required fields", ->
2017-08-08 06:38:31 -04:00
@ProjectGetter.getProject
.calledWith(@project_id, {compiler:1, rootDoc_id: 1, imageName: 1, rootFolder: 1})
2014-05-06 07:54:26 -04:00
.should.equal true
2017-07-28 10:01:05 -04:00
it "should flush the project to the database", ->
@DocumentUpdaterHandler.flushProjectToMongo
.calledWith(@project_id)
.should.equal true
2014-05-06 07:54:26 -04:00
it "should get all the docs", ->
@ProjectEntityHandler.getAllDocs
.calledWith(@project_id)
.should.equal true
it "should get all the files", ->
@ProjectEntityHandler.getAllFiles
.calledWith(@project_id)
.should.equal true
2014-02-12 05:23:40 -05:00
it "should build up the CLSI request", ->
expect(@request).to.deep.equal(
compile:
options:
compiler: @compiler
2014-10-16 13:43:48 -04:00
timeout : 100
2016-01-14 11:35:42 -05:00
imageName: @image
2016-02-02 09:50:48 -05:00
draft: false
2016-07-27 05:55:24 -04:00
check: undefined
2017-08-09 06:31:10 -04:00
syncType: undefined # "full"
syncState: undefined # "01234567890abcdef"
2014-02-12 05:23:40 -05:00
rootResourcePath: "main.tex"
resources: [{
2014-05-06 07:54:26 -04:00
path: "main.tex"
2014-02-12 05:23:40 -05:00
content: @doc_1.lines.join("\n")
}, {
2014-05-06 07:54:26 -04:00
path: "chapters/chapter1.tex"
2014-02-12 05:23:40 -05:00
content: @doc_2.lines.join("\n")
}, {
2014-05-06 07:54:26 -04:00
path: "images/image.png"
2014-02-12 05:23:40 -05:00
url: "#{@settings.apis.filestore.url}/project/#{@project_id}/file/#{@file_1._id}"
modified: @file_1.created.getTime()
}]
)
2017-08-15 11:00:40 -04:00
describe "with the incremental compile option", ->
beforeEach (done) ->
@ClsiStateManager.computeHash = sinon.stub().callsArgWith(2, null, @project_state_hash = "01234567890abcdef")
2017-08-15 11:00:40 -04:00
@DocumentUpdaterHandler.getProjectDocsIfMatch = sinon.stub().callsArgWith(2, null, [{_id:@doc_1._id, lines: @doc_1.lines, v: 123}])
@ProjectEntityHandler.getAllDocPathsFromProject = sinon.stub().callsArgWith(1, null, {"mock-doc-id-1":"main.tex"})
@ClsiManager._buildRequest @project_id, {timeout:100, incrementalCompilesEnabled:true}, (error, request) =>
@request = request
done()
it "should get the project with the required fields", ->
@ProjectGetter.getProject
.calledWith(@project_id, {compiler:1, rootDoc_id: 1, imageName: 1, rootFolder: 1})
.should.equal true
2017-10-12 10:01:11 -04:00
it "should not explicitly flush the project to the database", ->
2017-08-15 11:00:40 -04:00
@DocumentUpdaterHandler.flushProjectToMongo
.calledWith(@project_id)
2017-10-12 10:01:11 -04:00
.should.equal false
2017-08-15 11:00:40 -04:00
2017-10-12 10:01:11 -04:00
it "should get only the live docs from the docupdater with a background flush in docupdater", ->
2017-08-15 11:00:40 -04:00
@DocumentUpdaterHandler.getProjectDocsIfMatch
.calledWith(@project_id)
.should.equal true
it "should not get any of the files", ->
@ProjectEntityHandler.getAllFiles
.called.should.equal false
it "should build up the CLSI request", ->
expect(@request).to.deep.equal(
compile:
options:
compiler: @compiler
timeout : 100
imageName: @image
draft: false
check: undefined
syncType: "incremental"
syncState: "01234567890abcdef"
rootResourcePath: "main.tex"
resources: [{
path: "main.tex"
content: @doc_1.lines.join("\n")
}]
)
2014-06-01 16:28:19 -04:00
describe "when the root doc is set and not in the docupdater", ->
beforeEach (done) ->
@ClsiStateManager.computeHash = sinon.stub().callsArgWith(2, null, @project_state_hash = "01234567890abcdef")
@DocumentUpdaterHandler.getProjectDocsIfMatch = sinon.stub().callsArgWith(2, null, [{_id:@doc_1._id, lines: @doc_1.lines, v: 123}])
@ProjectEntityHandler.getAllDocPathsFromProject = sinon.stub().callsArgWith(1, null, {"mock-doc-id-1":"main.tex", "mock-doc-id-2":"/chapters/chapter1.tex"})
@ClsiManager._buildRequest @project_id, {timeout:100, incrementalCompilesEnabled:true, rootDoc_id:"mock-doc-id-2"}, (error, request) =>
@request = request
done()
it "should still change the root path", ->
@request.compile.rootResourcePath.should.equal "chapters/chapter1.tex"
2014-06-01 16:28:19 -04:00
describe "when root doc override is valid", ->
beforeEach (done) ->
@ClsiManager._buildRequest @project_id, {rootDoc_id:"mock-doc-id-2"}, (error, request) =>
@request = request
done()
it "should change root path", ->
@request.compile.rootResourcePath.should.equal "chapters/chapter1.tex"
describe "when root doc override is invalid", ->
beforeEach (done) ->
@ClsiManager._buildRequest @project_id, {rootDoc_id:"invalid-id"}, (error, request) =>
@request = request
done()
it "should fallback to default root doc", ->
@request.compile.rootResourcePath.should.equal "main.tex"
2014-02-12 05:23:40 -05:00
describe "when the project has an invalid compiler", ->
beforeEach (done) ->
@project.compiler = "context"
2014-06-01 12:16:05 -04:00
@ClsiManager._buildRequest @project, null, (error, request) =>
2014-02-12 05:23:40 -05:00
@request = request
done()
it "should set the compiler to pdflatex", ->
@request.compile.options.compiler.should.equal "pdflatex"
describe "when there is no valid root document", ->
beforeEach (done) ->
@project.rootDoc_id = "not-valid"
2014-06-01 12:16:05 -04:00
@ClsiManager._buildRequest @project, null, (@error, @request) =>
2014-02-12 05:23:40 -05:00
done()
it "should set to main.tex", ->
@request.compile.rootResourcePath.should.equal "main.tex"
2016-02-02 09:50:48 -05:00
describe "with the draft option", ->
it "should add the draft option into the request", (done) ->
@ClsiManager._buildRequest @project_id, {timeout:100, draft: true}, (error, request) =>
request.compile.options.draft.should.equal true
done()
2014-02-12 05:23:40 -05:00
describe '_postToClsi', ->
beforeEach ->
@req = { mock: "req" }
describe "successfully", ->
beforeEach ->
2016-04-19 11:48:51 -04:00
@ClsiManager._makeRequest = sinon.stub().callsArgWith(2, null, {statusCode: 204}, @body = { mock: "foo" })
2016-05-31 11:54:28 -04:00
@ClsiManager._postToClsi @project_id, @user_id, @req, "standard", @callback
it 'should send the request to the CLSI', ->
2016-05-31 11:54:28 -04:00
url = "#{@settings.apis.clsi.url}/project/#{@project_id}/user/#{@user_id}/compile"
2016-04-19 11:48:51 -04:00
@ClsiManager._makeRequest.calledWith(@project_id, {
method: "POST",
url: url
json: @req
}).should.equal true
it "should call the callback with the body and no error", ->
@callback.calledWith(null, @body).should.equal true
describe "when the CLSI returns an error", ->
beforeEach ->
2016-04-19 11:48:51 -04:00
@ClsiManager._makeRequest = sinon.stub().callsArgWith(2, null, {statusCode: 500}, @body = { mock: "foo" })
2016-05-31 11:54:28 -04:00
@ClsiManager._postToClsi @project_id, @user_id, @req, "standard", @callback
it "should call the callback with the body and the error", ->
@callback.calledWith(new Error("CLSI returned non-success code: 500"), @body).should.equal true
2015-09-11 09:21:05 -04:00
describe "wordCount", ->
beforeEach ->
2016-04-19 11:48:51 -04:00
@ClsiManager._makeRequest = sinon.stub().callsArgWith(2, null, {statusCode: 200}, @body = { mock: "foo" })
@ClsiManager._buildRequest = sinon.stub().callsArgWith(2, null, @req = { compile: { rootResourcePath: "rootfile.text", options: {} } })
2015-09-11 09:21:05 -04:00
describe "with root file", ->
beforeEach ->
2016-05-31 11:54:28 -04:00
@ClsiManager.wordCount @project_id, @user_id, false, {}, @callback
2015-09-11 09:21:05 -04:00
it "should call wordCount with root file", ->
2016-04-19 11:48:51 -04:00
@ClsiManager._makeRequest
2016-05-31 11:54:28 -04:00
.calledWith(@project_id, {method: "GET", url: "http://clsi.example.com/project/#{@project_id}/user/#{@user_id}/wordcount", qs: {file: "rootfile.text",image:undefined}})
.should.equal true
2015-09-11 09:21:05 -04:00
it "should call the callback", ->
@callback.called.should.equal true
describe "with param file", ->
beforeEach ->
2016-05-31 11:54:28 -04:00
@ClsiManager.wordCount @project_id, @user_id, "main.tex", {}, @callback
2015-09-11 09:21:05 -04:00
it "should call wordCount with param file", ->
2016-04-19 11:48:51 -04:00
@ClsiManager._makeRequest
2016-05-31 11:54:28 -04:00
.calledWith(@project_id, { method: "GET", url: "http://clsi.example.com/project/#{@project_id}/user/#{@user_id}/wordcount", qs:{file:"main.tex",image:undefined}})
2015-09-11 09:21:05 -04:00
.should.equal true
describe "with image", ->
beforeEach ->
@req.compile.options.imageName = @image = "example.com/mock/image"
2016-05-31 11:54:28 -04:00
@ClsiManager.wordCount @project_id, @user_id, "main.tex", {}, @callback
it "should call wordCount with file and image", ->
2016-04-19 11:48:51 -04:00
@ClsiManager._makeRequest
2016-05-31 11:54:28 -04:00
.calledWith(@project_id, { method: "GET", url: "http://clsi.example.com/project/#{@project_id}/user/#{@user_id}/wordcount", qs:{file:"main.tex",image:@image}})
.should.equal true
describe "_makeRequest", ->
beforeEach ->
@response = {there:"something"}
@request.callsArgWith(1, null, @response)
@opts =
method: "SOMETHIGN"
url: "http://a place on the web"
it "should process a request with a cookie jar", (done)->
@ClsiManager._makeRequest @project_id, @opts, =>
args = @request.args[0]
args[0].method.should.equal @opts.method
args[0].url.should.equal @opts.url
args[0].jar.should.equal @jar
done()
it "should set the cookie again on response as it might have changed", (done)->
@ClsiManager._makeRequest @project_id, @opts, =>
@ClsiCookieManager.setServerId.calledWith(@project_id, @response).should.equal true
done()