diff --git a/services/web/app/coffee/Features/Compile/ClsiCookieManager.coffee b/services/web/app/coffee/Features/Compile/ClsiCookieManager.coffee new file mode 100644 index 0000000000..81e2e633e6 --- /dev/null +++ b/services/web/app/coffee/Features/Compile/ClsiCookieManager.coffee @@ -0,0 +1,64 @@ +Settings = require "settings-sharelatex" +request = require('request') +redis = require("redis-sharelatex") +rclient = redis.createClient(Settings.redis.web) +Cookie = require('cookie') +logger = require "logger-sharelatex" + +buildKey = (project_id)-> + return "clsiserver:#{project_id}" + +clsiCookiesEnabled = Settings.clsiCookieKey? and Settings.clsiCookieKey.length != 0 + + +module.exports = ClsiCookieManager = + + _getServerId : (project_id, callback = (err, serverId)->)-> + rclient.get buildKey(project_id), (err, serverId)-> + if err? + return callback(err) + if serverId? + return callback(null, serverId) + else + return ClsiCookieManager._populateServerIdViaRequest project_id, callback + + + _populateServerIdViaRequest :(project_id, callback = (err, serverId)->)-> + url = "#{Settings.apis.clsi.url}/project/#{project_id}/status" + request.get url, (err, res, body)-> + if err? + logger.err err:err, project_id:project_id, "error getting initial server id for project" + return callback(err) + ClsiCookieManager.setServerId project_id, res, (err, serverId)-> + if err? + logger.err err:err, project_id:project_id, "error setting server id via populate request" + callback(err, serverId) + + _parseServerIdFromResponse : (response)-> + cookies = Cookie.parse(response.headers["set-cookie"]?[0] or "") + return cookies?[Settings.clsiCookieKey] + + setServerId: (project_id, response, callback = (err, serverId)->)-> + if !clsiCookiesEnabled + return callback() + serverId = ClsiCookieManager._parseServerIdFromResponse(response) + multi = rclient.multi() + multi.set buildKey(project_id), serverId + multi.expire buildKey(project_id), Settings.clsi_cookie_expire_length_seconds + multi.exec (err)-> + callback(err, serverId) + + + getCookieJar: (project_id, callback = (err, jar)->)-> + if !clsiCookiesEnabled + return callback(null, request.jar()) + ClsiCookieManager._getServerId project_id, (err, serverId)=> + if err? + logger.err err:err, project_id:project_id, "error getting server id" + return callback(err) + serverCookie = request.cookie("clsiserver=#{serverId}") + jar = request.jar() + jar.setCookie serverCookie, Settings.apis.clsi.url + callback(null, jar) + + diff --git a/services/web/app/coffee/Features/Compile/ClsiManager.coffee b/services/web/app/coffee/Features/Compile/ClsiManager.coffee index 174ebe830b..3285026cfa 100755 --- a/services/web/app/coffee/Features/Compile/ClsiManager.coffee +++ b/services/web/app/coffee/Features/Compile/ClsiManager.coffee @@ -6,24 +6,51 @@ Project = require("../../models/Project").Project ProjectEntityHandler = require("../Project/ProjectEntityHandler") logger = require "logger-sharelatex" url = require("url") +ClsiCookieManager = require("./ClsiCookieManager") + module.exports = ClsiManager = + sendRequest: (project_id, options = {}, callback = (error, success) ->) -> ClsiManager._buildRequest project_id, options, (error, req) -> return callback(error) if error? logger.log project_id: project_id, "sending compile to CLSI" ClsiManager._postToClsi project_id, req, options.compileGroup, (error, response) -> - return callback(error) if error? + if error? + logger.err err:error, project_id:project_id, "error sending request to clsi" + return callback(error) logger.log project_id: project_id, response: response, "received compile response from CLSI" - callback( - null - response?.compile?.status - ClsiManager._parseOutputFiles(project_id, response?.compile?.outputFiles) - ) + ClsiCookieManager._getServerId project_id, (err, clsiServerId)-> + if err? + logger.err err:err, project_id:project_id, "error getting server id" + return callback(err) + outputFiles = ClsiManager._parseOutputFiles(project_id, response?.compile?.outputFiles, clsiServerId) + callback(null, response?.compile?.status, outputFiles, clsiServerId) deleteAuxFiles: (project_id, options, callback = (error) ->) -> compilerUrl = @_getCompilerUrl(options?.compileGroup) - request.del "#{compilerUrl}/project/#{project_id}", callback + opts = + url:"#{compilerUrl}/project/#{project_id}" + method:"DELETE" + ClsiManager._makeRequest project_id, opts, callback + + + _makeRequest: (project_id, opts, callback)-> + ClsiCookieManager.getCookieJar project_id, (err, jar)-> + if err? + logger.err err:err, "error getting cookie jar for clsi request" + return callback(err) + opts.jar = jar + request opts, (err, response, body)-> + if err? + logger.err err:err, project_id:project_id, url:opts?.url, "error making request to clsi" + return callback(err) + ClsiCookieManager.setServerId project_id, response, (err)-> + if err? + logger.warn err:err, project_id:project_id, "error setting server id" + + return callback err, response, body + _getCompilerUrl: (compileGroup) -> if compileGroup == "priority" @@ -33,11 +60,11 @@ module.exports = ClsiManager = _postToClsi: (project_id, req, compileGroup, callback = (error, response) ->) -> compilerUrl = @_getCompilerUrl(compileGroup) - request.post { + opts = url: "#{compilerUrl}/project/#{project_id}/compile" json: req - jar: false - }, (error, response, body) -> + method: "POST" + ClsiManager._makeRequest project_id, opts, (error, response, body) -> return callback(error) if error? if 200 <= response.statusCode < 300 callback null, body @@ -48,11 +75,15 @@ module.exports = ClsiManager = logger.error err: error, project_id: project_id, "CLSI returned failure code" callback error, body - _parseOutputFiles: (project_id, rawOutputFiles = []) -> + _parseOutputFiles: (project_id, rawOutputFiles = [], clsiServer) -> + # console.log rawOutputFiles outputFiles = [] for file in rawOutputFiles + console.log path + path = url.parse(file.url).path + path = path.replace("/project/#{project_id}/output/", "") outputFiles.push - path: url.parse(file.url).path.replace("/project/#{project_id}/output/", "") + path: path type: file.type build: file.build return outputFiles @@ -115,9 +146,10 @@ module.exports = ClsiManager = wordcount_url = "#{compilerUrl}/project/#{project_id}/wordcount?file=#{encodeURIComponent(filename)}" if req.compile.options.imageName? wordcount_url += "&image=#{encodeURIComponent(req.compile.options.imageName)}" - request.get { + opts = url: wordcount_url - }, (error, response, body) -> + method: "GET" + ClsiManager._makeRequest project_id, opts, (error, response, body) -> return callback(error) if error? if 200 <= response.statusCode < 300 callback null, body diff --git a/services/web/app/coffee/Features/Compile/CompileController.coffee b/services/web/app/coffee/Features/Compile/CompileController.coffee index a273d38a35..13ce7f47ad 100755 --- a/services/web/app/coffee/Features/Compile/CompileController.coffee +++ b/services/web/app/coffee/Features/Compile/CompileController.coffee @@ -8,6 +8,8 @@ Settings = require "settings-sharelatex" AuthenticationController = require "../Authentication/AuthenticationController" UserGetter = require "../User/UserGetter" RateLimiter = require("../../infrastructure/RateLimiter") +ClsiCookieManager = require("./ClsiCookieManager") + module.exports = CompileController = compile: (req, res, next = (error) ->) -> @@ -28,13 +30,14 @@ module.exports = CompileController = if req.body?.draft options.draft = req.body.draft logger.log {options, project_id}, "got compile request" - CompileManager.compile project_id, user_id, options, (error, status, outputFiles, output, limits) -> + CompileManager.compile project_id, user_id, options, (error, status, outputFiles, clsiServerId, limits) -> return next(error) if error? res.contentType("application/json") - res.send 200, JSON.stringify { + res.status(200).send JSON.stringify { status: status outputFiles: outputFiles compileGroup: limits?.compileGroup + clsiServerId:clsiServerId } downloadPdf: (req, res, next = (error) ->)-> @@ -112,30 +115,34 @@ module.exports = CompileController = CompileController.proxyToClsiWithLimits(project_id, url, limits, req, res, next) proxyToClsiWithLimits: (project_id, url, limits, req, res, next = (error) ->) -> - if limits.compileGroup == "priority" - compilerUrl = Settings.apis.clsi_priority.url - else - compilerUrl = Settings.apis.clsi.url - url = "#{compilerUrl}#{url}" - logger.log url: url, "proxying to CLSI" - oneMinute = 60 * 1000 - # the base request - options = { url: url, method: req.method, timeout: oneMinute } - # if we have a build parameter, pass it through to the clsi - if req.query?.pdfng && req.query?.build? # only for new pdf viewer - options.qs = {} - options.qs.build = req.query.build - # if we are byte serving pdfs, pass through If-* and Range headers - # do not send any others, there's a proxying loop if Host: is passed! - if req.query?.pdfng - newHeaders = {} - for h, v of req.headers - newHeaders[h] = req.headers[h] if h.match /^(If-|Range)/i - options.headers = newHeaders - proxy = request(options) - proxy.pipe(res) - proxy.on "error", (error) -> - logger.warn err: error, url: url, "CLSI proxy error" + ClsiCookieManager.getCookieJar project_id, (err, jar)-> + if err? + logger.err err:err, "error getting cookie jar for clsi request" + return callback(err) + if limits.compileGroup == "priority" + compilerUrl = Settings.apis.clsi_priority.url + else + compilerUrl = Settings.apis.clsi.url + url = "#{compilerUrl}#{url}" + logger.log url: url, "proxying to CLSI" + oneMinute = 60 * 1000 + # the base request + options = { url: url, method: req.method, timeout: oneMinute, jar : jar } + # if we have a build parameter, pass it through to the clsi + if req.query?.pdfng && req.query?.build? # only for new pdf viewer + options.qs = {} + options.qs.build = req.query.build + # if we are byte serving pdfs, pass through If-* and Range headers + # do not send any others, there's a proxying loop if Host: is passed! + if req.query?.pdfng + newHeaders = {} + for h, v of req.headers + newHeaders[h] = req.headers[h] if h.match /^(If-|Range)/i + options.headers = newHeaders + proxy = request(options) + proxy.pipe(res) + proxy.on "error", (error) -> + logger.warn err: error, url: url, "CLSI proxy error" wordCount: (req, res, next) -> project_id = req.params.Project_id diff --git a/services/web/app/coffee/Features/Compile/CompileManager.coffee b/services/web/app/coffee/Features/Compile/CompileManager.coffee index c89e7107dd..0d8d480b7b 100755 --- a/services/web/app/coffee/Features/Compile/CompileManager.coffee +++ b/services/web/app/coffee/Features/Compile/CompileManager.coffee @@ -1,8 +1,6 @@ Settings = require('settings-sharelatex') - redis = require("redis-sharelatex") rclient = redis.createClient(Settings.redis.web) - DocumentUpdaterHandler = require "../DocumentUpdater/DocumentUpdaterHandler" Project = require("../../models/Project").Project ProjectRootDocManager = require "../Project/ProjectRootDocManager" @@ -13,6 +11,8 @@ logger = require("logger-sharelatex") rateLimiter = require("../../infrastructure/RateLimiter") module.exports = CompileManager = + + compile: (project_id, user_id, options = {}, _callback = (error) ->) -> timer = new Metrics.Timer("editor.compile") callback = (args...) -> @@ -37,10 +37,10 @@ module.exports = CompileManager = return callback(error) if error? for key, value of limits options[key] = value - ClsiManager.sendRequest project_id, options, (error, status, outputFiles, output) -> + ClsiManager.sendRequest project_id, options, (error, status, outputFiles, clsiServerId) -> return callback(error) if error? logger.log files: outputFiles, "output files" - callback(null, status, outputFiles, output, limits) + callback(null, status, outputFiles, clsiServerId, limits) deleteAuxFiles: (project_id, callback = (error) ->) -> CompileManager.getProjectCompileLimits project_id, (error, limits) -> diff --git a/services/web/app/coffee/infrastructure/Server.coffee b/services/web/app/coffee/infrastructure/Server.coffee index fea8752bb2..11a3b5237e 100644 --- a/services/web/app/coffee/infrastructure/Server.coffee +++ b/services/web/app/coffee/infrastructure/Server.coffee @@ -125,7 +125,7 @@ apiRouter.get "/profile", (req, res) -> , time app.get "/heapdump", (req, res)-> - require('heapdump').writeSnapshot '/tmp/' + Date.now() + '.clsi.heapsnapshot', (err, filename)-> + require('heapdump').writeSnapshot '/tmp/' + Date.now() + '.web.heapsnapshot', (err, filename)-> res.send filename logger.info ("creating HTTP server").yellow diff --git a/services/web/app/views/project/editor/pdf.jade b/services/web/app/views/project/editor/pdf.jade index f5c64c2d6b..df97ff889e 100644 --- a/services/web/app/views/project/editor/pdf.jade +++ b/services/web/app/views/project/editor/pdf.jade @@ -121,7 +121,7 @@ div.full-size.pdf(ng-controller="PdfController") ul.dropdown-menu.dropdown-menu-right li(ng-repeat="file in pdf.outputFiles") a( - href="/project/{{project_id}}/output/{{file.path}}" + href="{{file.url}}" target="_blank" ) {{ file.name }} a.btn.btn-info.btn-sm(href, ng-click="toggleRawLog()") diff --git a/services/web/config/settings.defaults.coffee b/services/web/config/settings.defaults.coffee index 17482ab7f7..026c451d0c 100644 --- a/services/web/config/settings.defaults.coffee +++ b/services/web/config/settings.defaults.coffee @@ -126,6 +126,9 @@ module.exports = # cookieDomain: ".sharelatex.dev" cookieName:"sharelatex.sid" + # this is only used if cookies are used for clsi backend + #clsiCookieKey: "clsiserver" + # Same, but with http auth credentials. httpAuthSiteUrl: 'http://#{httpAuthUser}:#{httpAuthPass}@localhost:3000' diff --git a/services/web/package.json b/services/web/package.json index 13c513c386..e34a564309 100644 --- a/services/web/package.json +++ b/services/web/package.json @@ -18,6 +18,7 @@ "body-parser": "^1.13.1", "bufferedstream": "1.6.0", "connect-redis": "2.3.0", + "cookie": "^0.2.3", "cookie-parser": "1.3.5", "csurf": "^1.8.3", "dateformat": "1.0.4-1.2.3", diff --git a/services/web/public/coffee/ide/pdf/controllers/PdfController.coffee b/services/web/public/coffee/ide/pdf/controllers/PdfController.coffee index 0eab163e99..478fed3ecc 100644 --- a/services/web/public/coffee/ide/pdf/controllers/PdfController.coffee +++ b/services/web/public/coffee/ide/pdf/controllers/PdfController.coffee @@ -37,6 +37,9 @@ define [ } parseCompileResponse = (response) -> + if response.clsiServerId? and response.clsiServerId != ide.clsiServerId + ide.clsiServerId = response.clsiServerId + # Reset everything $scope.pdf.error = false $scope.pdf.timedout = false @@ -69,6 +72,14 @@ define [ else if response.status == "success" $scope.pdf.view = 'pdf' $scope.shouldShowLogs = false + # define the base url + $scope.pdf.url = "/project/#{$scope.project_id}/output/output.pdf?cache_bust=#{Date.now()}" + # add a query string parameter for the compile group + if response.compileGroup? + $scope.pdf.compileGroup = response.compileGroup + $scope.pdf.url = $scope.pdf.url + "&compileGroup=#{$scope.pdf.compileGroup}" + if response.clsiServerId? + $scope.pdf.url = $scope.pdf.url + "&clsiserverid=#{response.clsiServerId}" # make a cache to look up files by name fileByPath = {} for file in response.outputFiles @@ -106,14 +117,23 @@ define [ file.name = "#{file.path.replace(/^output\./, "")} file" else file.name = file.path + file.url = "/project/#{project_id}/output/#{file.path}" + if response.clsiServerId? + file.url = file.url + "?clsiserverid=#{response.clsiServerId}" $scope.pdf.outputFiles.push file fetchLogs = (outputFile) -> + + opts = + method:"GET" + params: + build:outputFile.build + clsiserverid:ide.clsiServerId if outputFile?.build? - logUrl = "/project/#{$scope.project_id}/build/#{outputFile.build}/output/output.log" + opts.url = "/project/#{$scope.project_id}/build/#{outputFile.build}/output/output.log" else - logUrl = "/project/#{$scope.project_id}/output/output.log" - $http.get logUrl + opts.url = "/project/#{$scope.project_id}/output/output.log" + $http opts .success (log) -> #console.log ">>", log $scope.pdf.rawLog = log @@ -137,10 +157,10 @@ define [ } # Get the biber log and parse it if outputFile?.build? - biberLogUrl = "/project/#{$scope.project_id}/build/#{outputFile.build}/output/output.blg" + opts.url = "/project/#{$scope.project_id}/build/#{outputFile.build}/output/output.blg" else - biberLogUrl = "/project/#{$scope.project_id}/output/output.blg" - $http.get biberLogUrl + opts.url = "/project/#{$scope.project_id}/output/output.blg" + $http opts .success (log) -> window._s = $scope biberLogEntries = BibLogParser.parse(log, {}) @@ -204,6 +224,8 @@ define [ $http { url: "/project/#{$scope.project_id}/output" method: "DELETE" + params: + clsiserverid:ide.clsiServerId headers: "X-Csrf-Token": window.csrfToken } @@ -286,6 +308,7 @@ define [ file: path line: row + 1 column: column + clsiserverid:ide.clsiServerId } }) .success (data) -> @@ -313,6 +336,7 @@ define [ page: position.page + 1 h: position.offset.left.toFixed(2) v: position.offset.top.toFixed(2) + clsiserverid:ide.clsiServerId } }) .success (data) -> diff --git a/services/web/public/coffee/ide/wordcount/controllers/WordCountModalController.coffee b/services/web/public/coffee/ide/wordcount/controllers/WordCountModalController.coffee index 3c69d3e276..e880a25eef 100644 --- a/services/web/public/coffee/ide/wordcount/controllers/WordCountModalController.coffee +++ b/services/web/public/coffee/ide/wordcount/controllers/WordCountModalController.coffee @@ -5,11 +5,15 @@ define [ $scope.status = loading:true - $http.get("/project/#{ide.project_id}/wordcount") + opts = + url:"/project/#{ide.project_id}/wordcount" + method:"GET" + params: + clsiserverid:ide.clsiServerId + $http opts .success (data) -> $scope.status.loading = false $scope.data = data.texcount - console.log $scope.data .error () -> $scope.status.error = true diff --git a/services/web/test/UnitTests/coffee/Compile/ClsiCookieManagerTests.coffee b/services/web/test/UnitTests/coffee/Compile/ClsiCookieManagerTests.coffee new file mode 100644 index 0000000000..1fc8716657 --- /dev/null +++ b/services/web/test/UnitTests/coffee/Compile/ClsiCookieManagerTests.coffee @@ -0,0 +1,133 @@ +sinon = require('sinon') +chai = require('chai') +assert = chai.assert +should = chai.should() +expect = chai.expect +modulePath = "../../../../app/js/Features/Compile/ClsiCookieManager.js" +SandboxedModule = require('sandboxed-module') +realRequst = require("request") + +describe "ClsiCookieManager", -> + beforeEach -> + self = @ + @redisMulti = + set:sinon.stub() + get:sinon.stub() + expire:sinon.stub() + exec:sinon.stub() + @redis = + auth:-> + get:sinon.stub() + multi: -> return self.redisMulti + @project_id = "123423431321" + @request = + get: sinon.stub() + cookie:realRequst.cookie + jar: realRequst.jar + @settings = + redis: + web:"redis.something" + apis: + clsi: + url: "http://clsi.example.com" + clsi_cookie_expire_length_seconds: Math.random() + clsiCookieKey: "coooookie" + @requires = + "redis-sharelatex" : + createClient: => + @redis + "settings-sharelatex": @settings + "request": @request + + "logger-sharelatex": @logger = { log: sinon.stub(), error: sinon.stub(), warn: sinon.stub() } + @ClsiCookieManager = SandboxedModule.require modulePath, requires:@requires + + + + describe "getServerId", -> + + it "should call get for the key", (done)-> + @redis.get.callsArgWith(1, null, "clsi-7") + @ClsiCookieManager._getServerId @project_id, (err, serverId)=> + @redis.get.calledWith("clsiserver:#{@project_id}").should.equal true + serverId.should.equal "clsi-7" + done() + + it "should _populateServerIdViaRequest if no key is found", (done)-> + @ClsiCookieManager._populateServerIdViaRequest = sinon.stub().callsArgWith(1) + @redis.get.callsArgWith(1, null) + @ClsiCookieManager._getServerId @project_id, (err, serverId)=> + @ClsiCookieManager._populateServerIdViaRequest.calledWith(@project_id).should.equal true + done() + + + describe "_populateServerIdViaRequest", -> + + beforeEach -> + @response = "some data" + @request.get.callsArgWith(1, null, @response) + @ClsiCookieManager.setServerId = sinon.stub().callsArgWith(2, null, "clsi-9") + + it "should make a request to the clsi", (done)-> + @ClsiCookieManager._populateServerIdViaRequest @project_id, (err, serverId)=> + args = @ClsiCookieManager.setServerId.args[0] + args[0].should.equal @project_id + args[1].should.deep.equal @response + done() + + it "should return the server id", (done)-> + @ClsiCookieManager._populateServerIdViaRequest @project_id, (err, serverId)=> + serverId.should.equal "clsi-9" + done() + + describe "setServerId", -> + + beforeEach -> + @response = "dsadsakj" + @ClsiCookieManager._parseServerIdFromResponse = sinon.stub().returns("clsi-8") + @redisMulti.exec.callsArgWith(0) + + it "should set the server id with a ttl", (done)-> + @ClsiCookieManager.setServerId @project_id, @response, (err)=> + @redisMulti.set.calledWith("clsiserver:#{@project_id}", "clsi-8").should.equal true + @redisMulti.expire.calledWith("clsiserver:#{@project_id}", @settings.clsi_cookie_expire_length_seconds).should.equal true + done() + + it "should return the server id", (done)-> + @ClsiCookieManager.setServerId @project_id, @response, (err, serverId)=> + serverId.should.equal "clsi-8" + done() + + + it "should not set the server id if clsiCookies are not enabled", (done)-> + delete @settings.clsiCookieKey + @ClsiCookieManager = SandboxedModule.require modulePath, requires:@requires + @ClsiCookieManager.setServerId @project_id, @response, (err, serverId)=> + @redisMulti.exec.called.should.equal false + done() + + describe "getCookieJar", -> + + beforeEach -> + @ClsiCookieManager._getServerId = sinon.stub().callsArgWith(1, null, "clsi-11") + + it "should return a jar with the cookie set populated from redis", (done)-> + @ClsiCookieManager.getCookieJar @project_id, (err, jar)-> + jar._jar.store.idx["clsi.example.com"]["/"].clsiserver.key.should.equal "clsiserver" + jar._jar.store.idx["clsi.example.com"]["/"].clsiserver.value.should.equal "clsi-11" + done() + + + it "should return empty cookie jar if clsiCookies are not enabled", (done)-> + delete @settings.clsiCookieKey + @ClsiCookieManager = SandboxedModule.require modulePath, requires:@requires + @ClsiCookieManager.getCookieJar @project_id, (err, jar)-> + assert.deepEqual jar, realRequst.jar() + done() + + + + + + + diff --git a/services/web/test/UnitTests/coffee/Compile/ClsiManagerTests.coffee b/services/web/test/UnitTests/coffee/Compile/ClsiManagerTests.coffee index 3aa5b80528..89f312441d 100644 --- a/services/web/test/UnitTests/coffee/Compile/ClsiManagerTests.coffee +++ b/services/web/test/UnitTests/coffee/Compile/ClsiManagerTests.coffee @@ -7,6 +7,11 @@ SandboxedModule = require('sandboxed-module') describe "ClsiManager", -> beforeEach -> + @jar = {cookie:"stuff"} + @ClsiCookieManager = + getCookieJar: sinon.stub().callsArgWith(1, null, @jar) + setServerId: sinon.stub().callsArgWith(2) + _getServerId:sinon.stub() @ClsiManager = SandboxedModule.require modulePath, requires: "settings-sharelatex": @settings = apis: @@ -19,14 +24,16 @@ describe "ClsiManager", -> url: "https://clsipremium.example.com" "../../models/Project": Project: @Project = {} "../Project/ProjectEntityHandler": @ProjectEntityHandler = {} + "./ClsiCookieManager": @ClsiCookieManager "logger-sharelatex": @logger = { log: sinon.stub(), error: sinon.stub(), warn: sinon.stub() } - "request": @request = {} + "request": @request = sinon.stub() @project_id = "project-id" @callback = sinon.stub() describe "sendRequest", -> beforeEach -> @ClsiManager._buildRequest = sinon.stub().callsArgWith(2, null, @request = "mock-request") + @ClsiCookieManager._getServerId.callsArgWith(1, null, "clsi3") describe "with a successful compile", -> beforeEach -> @@ -80,15 +87,15 @@ describe "ClsiManager", -> describe "deleteAuxFiles", -> beforeEach -> - @request.del = sinon.stub().callsArg(1) + @ClsiManager._makeRequest = sinon.stub().callsArg(2) describe "with the standard compileGroup", -> beforeEach -> @ClsiManager.deleteAuxFiles @project_id, {compileGroup: "standard"}, @callback it "should call the delete method in the standard CLSI", -> - @request.del - .calledWith("#{@settings.apis.clsi.url}/project/#{@project_id}") + @ClsiManager._makeRequest + .calledWith(@project_id, { method:"DELETE", url:"#{@settings.apis.clsi.url}/project/#{@project_id}"}) .should.equal true it "should call the callback", -> @@ -99,8 +106,8 @@ describe "ClsiManager", -> @ClsiManager.deleteAuxFiles @project_id, {compileGroup: "priority"}, @callback it "should call the delete method in the CLSI", -> - @request.del - .calledWith("#{@settings.apis.clsi_priority.url}/project/#{@project_id}") + @ClsiManager._makeRequest + .calledWith(@project_id, { method:"DELETE", url:"#{@settings.apis.clsi_priority.url}/project/#{@project_id}"}) .should.equal true describe "_buildRequest", -> @@ -235,15 +242,15 @@ describe "ClsiManager", -> describe "successfully", -> beforeEach -> - @request.post = sinon.stub().callsArgWith(1, null, {statusCode: 204}, @body = { mock: "foo" }) + @ClsiManager._makeRequest = sinon.stub().callsArgWith(2, null, {statusCode: 204}, @body = { mock: "foo" }) @ClsiManager._postToClsi @project_id, @req, "standard", @callback it 'should send the request to the CLSI', -> url = "#{@settings.apis.clsi.url}/project/#{@project_id}/compile" - @request.post.calledWith({ + @ClsiManager._makeRequest.calledWith(@project_id, { + method: "POST", url: url json: @req - jar: false }).should.equal true it "should call the callback with the body and no error", -> @@ -251,7 +258,7 @@ describe "ClsiManager", -> describe "when the CLSI returns an error", -> beforeEach -> - @request.post = sinon.stub().callsArgWith(1, null, {statusCode: 500}, @body = { mock: "foo" }) + @ClsiManager._makeRequest = sinon.stub().callsArgWith(2, null, {statusCode: 500}, @body = { mock: "foo" }) @ClsiManager._postToClsi @project_id, @req, "standard", @callback it "should call the callback with the body and the error", -> @@ -259,20 +266,20 @@ describe "ClsiManager", -> describe "when the compiler is priority", -> beforeEach -> - @request.post = sinon.stub().callsArgWith(1, null, {statusCode: 500}, @body = { mock: "foo" }) + @ClsiManager._makeRequest = sinon.stub() @ClsiManager._postToClsi @project_id, @req, "priority", @callback it "should use the clsi_priority url", -> url = "#{@settings.apis.clsi_priority.url}/project/#{@project_id}/compile" - @request.post.calledWith({ + @ClsiManager._makeRequest.calledWith(@project_id, { + method: "POST", url: url json: @req - jar: false }).should.equal true describe "wordCount", -> beforeEach -> - @request.get = sinon.stub().callsArgWith(1, null, {statusCode: 200}, @body = { mock: "foo" }) + @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: {} } }) @ClsiManager._getCompilerUrl = sinon.stub().returns "compiler.url" @@ -281,8 +288,8 @@ describe "ClsiManager", -> @ClsiManager.wordCount @project_id, false, {}, @callback it "should call wordCount with root file", -> - @request.get - .calledWith({ url: "compiler.url/project/#{@project_id}/wordcount?file=rootfile.text" }) + @ClsiManager._makeRequest + .calledWith(@project_id, { method: "GET", url: "compiler.url/project/#{@project_id}/wordcount?file=rootfile.text" }) .should.equal true it "should call the callback", -> @@ -293,8 +300,8 @@ describe "ClsiManager", -> @ClsiManager.wordCount @project_id, "main.tex", {}, @callback it "should call wordCount with param file", -> - @request.get - .calledWith({ url: "compiler.url/project/#{@project_id}/wordcount?file=main.tex" }) + @ClsiManager._makeRequest + .calledWith(@project_id, { method: "GET", url: "compiler.url/project/#{@project_id}/wordcount?file=main.tex" }) .should.equal true describe "with image", -> @@ -303,6 +310,50 @@ describe "ClsiManager", -> @ClsiManager.wordCount @project_id, "main.tex", {}, @callback it "should call wordCount with file and image", -> - @request.get - .calledWith({ url: "compiler.url/project/#{@project_id}/wordcount?file=main.tex&image=#{encodeURIComponent(@image)}" }) + @ClsiManager._makeRequest + .calledWith(@project_id, { method: "GET", url: "compiler.url/project/#{@project_id}/wordcount?file=main.tex&image=#{encodeURIComponent(@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() + + + + + + + + + + + + + + + + + + + + diff --git a/services/web/test/UnitTests/coffee/Compile/CompileControllerTests.coffee b/services/web/test/UnitTests/coffee/Compile/CompileControllerTests.coffee index 26cf0a2e2d..6bae5803c4 100644 --- a/services/web/test/UnitTests/coffee/Compile/CompileControllerTests.coffee +++ b/services/web/test/UnitTests/coffee/Compile/CompileControllerTests.coffee @@ -22,6 +22,9 @@ describe "CompileController", -> url: "clsi.example.com" clsi_priority: url: "clsi-priority.example.com" + @jar = {cookie:"stuff"} + @ClsiCookieManager = + getCookieJar:sinon.stub().callsArgWith(1, null, @jar) @CompileController = SandboxedModule.require modulePath, requires: "settings-sharelatex": @settings "request": @request = sinon.stub() @@ -33,6 +36,7 @@ describe "CompileController", -> "./ClsiManager": @ClsiManager "../Authentication/AuthenticationController": @AuthenticationController = {} "../../infrastructure/RateLimiter":@RateLimiter + "./ClsiCookieManager":@ClsiCookieManager @project_id = "project-id" @user = features: @@ -182,6 +186,7 @@ describe "CompileController", -> it "should open a request to the CLSI", -> @request .calledWith( + jar:@jar method: @req.method url: "#{@settings.apis.clsi.url}#{@url}", timeout: 60 * 1000 @@ -204,6 +209,7 @@ describe "CompileController", -> it "should proxy to the priority url if the user has the feature", ()-> @request .calledWith( + jar:@jar method: @req.method url: "#{@settings.apis.clsi_priority.url}#{@url}", timeout: 60 * 1000 @@ -218,6 +224,7 @@ describe "CompileController", -> it "should open a request to the CLSI", -> @request .calledWith( + jar:@jar method: @req.method url: "#{@settings.apis.clsi.url}#{@url}", timeout: 60 * 1000 @@ -240,6 +247,7 @@ describe "CompileController", -> it "should proxy to the priority url if the user has the feature", ()-> @request .calledWith( + jar:@jar method: @req.method url: "#{@settings.apis.clsi_priority.url}#{@url}", timeout: 60 * 1000 @@ -254,6 +262,7 @@ describe "CompileController", -> it "should proxy to the standard url", ()-> @request .calledWith( + jar:@jar method: @req.method url: "#{@settings.apis.clsi.url}#{@url}", timeout: 60 * 1000 @@ -269,6 +278,7 @@ describe "CompileController", -> it "should proxy to the standard url without the build parameter", ()-> @request .calledWith( + jar:@jar method: @req.method url: "#{@settings.apis.clsi.url}#{@url}", timeout: 60 * 1000 @@ -286,6 +296,7 @@ describe "CompileController", -> it "should open a request to the CLSI", -> @request .calledWith( + jar:@jar method: @req.method url: "#{@settings.apis.clsi.url}#{@url}", timeout: 60 * 1000 @@ -313,6 +324,7 @@ describe "CompileController", -> it "should proxy to the priority url if the user has the feature", ()-> @request .calledWith( + jar:@jar method: @req.method url: "#{@settings.apis.clsi_priority.url}#{@url}", timeout: 60 * 1000 @@ -331,8 +343,8 @@ describe "CompileController", -> @CompileController.proxyToClsi(@project_id, @url = "/test", @req, @res, @next) it "should proxy to the standard url with the build parameter", ()-> - @request - .calledWith( + @request.calledWith( + jar:@jar method: @req.method qs: {build: 1234} url: "#{@settings.apis.clsi.url}#{@url}", diff --git a/services/web/test/UnitTests/coffee/helpers/MockResponse.coffee b/services/web/test/UnitTests/coffee/helpers/MockResponse.coffee index a7182c9df0..5ed7c58522 100644 --- a/services/web/test/UnitTests/coffee/helpers/MockResponse.coffee +++ b/services/web/test/UnitTests/coffee/helpers/MockResponse.coffee @@ -63,6 +63,10 @@ class MockResponse @body = body if body @callback() if @callback? + status: (@statusCode)-> + return @ + + setHeader: (header, value) -> @headers[header] = value