Merge branch 'project_url_query' into clsi-dynamic-load

This commit is contained in:
Henry Oswald 2016-05-19 13:02:23 +01:00
commit 2abebd850c
14 changed files with 411 additions and 76 deletions

View file

@ -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)

View file

@ -6,24 +6,51 @@ Project = require("../../models/Project").Project
ProjectEntityHandler = require("../Project/ProjectEntityHandler") ProjectEntityHandler = require("../Project/ProjectEntityHandler")
logger = require "logger-sharelatex" logger = require "logger-sharelatex"
url = require("url") url = require("url")
ClsiCookieManager = require("./ClsiCookieManager")
module.exports = ClsiManager = module.exports = ClsiManager =
sendRequest: (project_id, options = {}, callback = (error, success) ->) -> sendRequest: (project_id, options = {}, callback = (error, success) ->) ->
ClsiManager._buildRequest project_id, options, (error, req) -> ClsiManager._buildRequest project_id, options, (error, req) ->
return callback(error) if error? return callback(error) if error?
logger.log project_id: project_id, "sending compile to CLSI" logger.log project_id: project_id, "sending compile to CLSI"
ClsiManager._postToClsi project_id, req, options.compileGroup, (error, response) -> 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" logger.log project_id: project_id, response: response, "received compile response from CLSI"
callback( ClsiCookieManager._getServerId project_id, (err, clsiServerId)->
null if err?
response?.compile?.status logger.err err:err, project_id:project_id, "error getting server id"
ClsiManager._parseOutputFiles(project_id, response?.compile?.outputFiles) 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) ->) -> deleteAuxFiles: (project_id, options, callback = (error) ->) ->
compilerUrl = @_getCompilerUrl(options?.compileGroup) 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) -> _getCompilerUrl: (compileGroup) ->
if compileGroup == "priority" if compileGroup == "priority"
@ -33,11 +60,11 @@ module.exports = ClsiManager =
_postToClsi: (project_id, req, compileGroup, callback = (error, response) ->) -> _postToClsi: (project_id, req, compileGroup, callback = (error, response) ->) ->
compilerUrl = @_getCompilerUrl(compileGroup) compilerUrl = @_getCompilerUrl(compileGroup)
request.post { opts =
url: "#{compilerUrl}/project/#{project_id}/compile" url: "#{compilerUrl}/project/#{project_id}/compile"
json: req json: req
jar: false method: "POST"
}, (error, response, body) -> ClsiManager._makeRequest project_id, opts, (error, response, body) ->
return callback(error) if error? return callback(error) if error?
if 200 <= response.statusCode < 300 if 200 <= response.statusCode < 300
callback null, body callback null, body
@ -48,11 +75,15 @@ module.exports = ClsiManager =
logger.error err: error, project_id: project_id, "CLSI returned failure code" logger.error err: error, project_id: project_id, "CLSI returned failure code"
callback error, body callback error, body
_parseOutputFiles: (project_id, rawOutputFiles = []) -> _parseOutputFiles: (project_id, rawOutputFiles = [], clsiServer) ->
# console.log rawOutputFiles
outputFiles = [] outputFiles = []
for file in rawOutputFiles for file in rawOutputFiles
console.log path
path = url.parse(file.url).path
path = path.replace("/project/#{project_id}/output/", "")
outputFiles.push outputFiles.push
path: url.parse(file.url).path.replace("/project/#{project_id}/output/", "") path: path
type: file.type type: file.type
build: file.build build: file.build
return outputFiles return outputFiles
@ -115,9 +146,10 @@ module.exports = ClsiManager =
wordcount_url = "#{compilerUrl}/project/#{project_id}/wordcount?file=#{encodeURIComponent(filename)}" wordcount_url = "#{compilerUrl}/project/#{project_id}/wordcount?file=#{encodeURIComponent(filename)}"
if req.compile.options.imageName? if req.compile.options.imageName?
wordcount_url += "&image=#{encodeURIComponent(req.compile.options.imageName)}" wordcount_url += "&image=#{encodeURIComponent(req.compile.options.imageName)}"
request.get { opts =
url: wordcount_url url: wordcount_url
}, (error, response, body) -> method: "GET"
ClsiManager._makeRequest project_id, opts, (error, response, body) ->
return callback(error) if error? return callback(error) if error?
if 200 <= response.statusCode < 300 if 200 <= response.statusCode < 300
callback null, body callback null, body

View file

@ -8,6 +8,8 @@ Settings = require "settings-sharelatex"
AuthenticationController = require "../Authentication/AuthenticationController" AuthenticationController = require "../Authentication/AuthenticationController"
UserGetter = require "../User/UserGetter" UserGetter = require "../User/UserGetter"
RateLimiter = require("../../infrastructure/RateLimiter") RateLimiter = require("../../infrastructure/RateLimiter")
ClsiCookieManager = require("./ClsiCookieManager")
module.exports = CompileController = module.exports = CompileController =
compile: (req, res, next = (error) ->) -> compile: (req, res, next = (error) ->) ->
@ -28,13 +30,14 @@ module.exports = CompileController =
if req.body?.draft if req.body?.draft
options.draft = req.body.draft options.draft = req.body.draft
logger.log {options, project_id}, "got compile request" 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? return next(error) if error?
res.contentType("application/json") res.contentType("application/json")
res.send 200, JSON.stringify { res.status(200).send JSON.stringify {
status: status status: status
outputFiles: outputFiles outputFiles: outputFiles
compileGroup: limits?.compileGroup compileGroup: limits?.compileGroup
clsiServerId:clsiServerId
} }
downloadPdf: (req, res, next = (error) ->)-> downloadPdf: (req, res, next = (error) ->)->
@ -112,30 +115,34 @@ module.exports = CompileController =
CompileController.proxyToClsiWithLimits(project_id, url, limits, req, res, next) CompileController.proxyToClsiWithLimits(project_id, url, limits, req, res, next)
proxyToClsiWithLimits: (project_id, url, limits, req, res, next = (error) ->) -> proxyToClsiWithLimits: (project_id, url, limits, req, res, next = (error) ->) ->
if limits.compileGroup == "priority" ClsiCookieManager.getCookieJar project_id, (err, jar)->
compilerUrl = Settings.apis.clsi_priority.url if err?
else logger.err err:err, "error getting cookie jar for clsi request"
compilerUrl = Settings.apis.clsi.url return callback(err)
url = "#{compilerUrl}#{url}" if limits.compileGroup == "priority"
logger.log url: url, "proxying to CLSI" compilerUrl = Settings.apis.clsi_priority.url
oneMinute = 60 * 1000 else
# the base request compilerUrl = Settings.apis.clsi.url
options = { url: url, method: req.method, timeout: oneMinute } url = "#{compilerUrl}#{url}"
# if we have a build parameter, pass it through to the clsi logger.log url: url, "proxying to CLSI"
if req.query?.pdfng && req.query?.build? # only for new pdf viewer oneMinute = 60 * 1000
options.qs = {} # the base request
options.qs.build = req.query.build options = { url: url, method: req.method, timeout: oneMinute, jar : jar }
# if we are byte serving pdfs, pass through If-* and Range headers # if we have a build parameter, pass it through to the clsi
# do not send any others, there's a proxying loop if Host: is passed! if req.query?.pdfng && req.query?.build? # only for new pdf viewer
if req.query?.pdfng options.qs = {}
newHeaders = {} options.qs.build = req.query.build
for h, v of req.headers # if we are byte serving pdfs, pass through If-* and Range headers
newHeaders[h] = req.headers[h] if h.match /^(If-|Range)/i # do not send any others, there's a proxying loop if Host: is passed!
options.headers = newHeaders if req.query?.pdfng
proxy = request(options) newHeaders = {}
proxy.pipe(res) for h, v of req.headers
proxy.on "error", (error) -> newHeaders[h] = req.headers[h] if h.match /^(If-|Range)/i
logger.warn err: error, url: url, "CLSI proxy error" 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) -> wordCount: (req, res, next) ->
project_id = req.params.Project_id project_id = req.params.Project_id

View file

@ -1,8 +1,6 @@
Settings = require('settings-sharelatex') Settings = require('settings-sharelatex')
redis = require("redis-sharelatex") redis = require("redis-sharelatex")
rclient = redis.createClient(Settings.redis.web) rclient = redis.createClient(Settings.redis.web)
DocumentUpdaterHandler = require "../DocumentUpdater/DocumentUpdaterHandler" DocumentUpdaterHandler = require "../DocumentUpdater/DocumentUpdaterHandler"
Project = require("../../models/Project").Project Project = require("../../models/Project").Project
ProjectRootDocManager = require "../Project/ProjectRootDocManager" ProjectRootDocManager = require "../Project/ProjectRootDocManager"
@ -13,6 +11,8 @@ logger = require("logger-sharelatex")
rateLimiter = require("../../infrastructure/RateLimiter") rateLimiter = require("../../infrastructure/RateLimiter")
module.exports = CompileManager = module.exports = CompileManager =
compile: (project_id, user_id, options = {}, _callback = (error) ->) -> compile: (project_id, user_id, options = {}, _callback = (error) ->) ->
timer = new Metrics.Timer("editor.compile") timer = new Metrics.Timer("editor.compile")
callback = (args...) -> callback = (args...) ->
@ -37,10 +37,10 @@ module.exports = CompileManager =
return callback(error) if error? return callback(error) if error?
for key, value of limits for key, value of limits
options[key] = value 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? return callback(error) if error?
logger.log files: outputFiles, "output files" logger.log files: outputFiles, "output files"
callback(null, status, outputFiles, output, limits) callback(null, status, outputFiles, clsiServerId, limits)
deleteAuxFiles: (project_id, callback = (error) ->) -> deleteAuxFiles: (project_id, callback = (error) ->) ->
CompileManager.getProjectCompileLimits project_id, (error, limits) -> CompileManager.getProjectCompileLimits project_id, (error, limits) ->

View file

@ -125,7 +125,7 @@ apiRouter.get "/profile", (req, res) ->
, time , time
app.get "/heapdump", (req, res)-> 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 res.send filename
logger.info ("creating HTTP server").yellow logger.info ("creating HTTP server").yellow

View file

@ -121,7 +121,7 @@ div.full-size.pdf(ng-controller="PdfController")
ul.dropdown-menu.dropdown-menu-right ul.dropdown-menu.dropdown-menu-right
li(ng-repeat="file in pdf.outputFiles") li(ng-repeat="file in pdf.outputFiles")
a( a(
href="/project/{{project_id}}/output/{{file.path}}" href="{{file.url}}"
target="_blank" target="_blank"
) {{ file.name }} ) {{ file.name }}
a.btn.btn-info.btn-sm(href, ng-click="toggleRawLog()") a.btn.btn-info.btn-sm(href, ng-click="toggleRawLog()")

View file

@ -126,6 +126,9 @@ module.exports =
# cookieDomain: ".sharelatex.dev" # cookieDomain: ".sharelatex.dev"
cookieName:"sharelatex.sid" cookieName:"sharelatex.sid"
# this is only used if cookies are used for clsi backend
#clsiCookieKey: "clsiserver"
# Same, but with http auth credentials. # Same, but with http auth credentials.
httpAuthSiteUrl: 'http://#{httpAuthUser}:#{httpAuthPass}@localhost:3000' httpAuthSiteUrl: 'http://#{httpAuthUser}:#{httpAuthPass}@localhost:3000'

View file

@ -18,6 +18,7 @@
"body-parser": "^1.13.1", "body-parser": "^1.13.1",
"bufferedstream": "1.6.0", "bufferedstream": "1.6.0",
"connect-redis": "2.3.0", "connect-redis": "2.3.0",
"cookie": "^0.2.3",
"cookie-parser": "1.3.5", "cookie-parser": "1.3.5",
"csurf": "^1.8.3", "csurf": "^1.8.3",
"dateformat": "1.0.4-1.2.3", "dateformat": "1.0.4-1.2.3",

View file

@ -37,6 +37,9 @@ define [
} }
parseCompileResponse = (response) -> parseCompileResponse = (response) ->
if response.clsiServerId? and response.clsiServerId != ide.clsiServerId
ide.clsiServerId = response.clsiServerId
# Reset everything # Reset everything
$scope.pdf.error = false $scope.pdf.error = false
$scope.pdf.timedout = false $scope.pdf.timedout = false
@ -69,6 +72,14 @@ define [
else if response.status == "success" else if response.status == "success"
$scope.pdf.view = 'pdf' $scope.pdf.view = 'pdf'
$scope.shouldShowLogs = false $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 # make a cache to look up files by name
fileByPath = {} fileByPath = {}
for file in response.outputFiles for file in response.outputFiles
@ -106,14 +117,23 @@ define [
file.name = "#{file.path.replace(/^output\./, "")} file" file.name = "#{file.path.replace(/^output\./, "")} file"
else else
file.name = file.path 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 $scope.pdf.outputFiles.push file
fetchLogs = (outputFile) -> fetchLogs = (outputFile) ->
opts =
method:"GET"
params:
build:outputFile.build
clsiserverid:ide.clsiServerId
if outputFile?.build? 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 else
logUrl = "/project/#{$scope.project_id}/output/output.log" opts.url = "/project/#{$scope.project_id}/output/output.log"
$http.get logUrl $http opts
.success (log) -> .success (log) ->
#console.log ">>", log #console.log ">>", log
$scope.pdf.rawLog = log $scope.pdf.rawLog = log
@ -137,10 +157,10 @@ define [
} }
# Get the biber log and parse it # Get the biber log and parse it
if outputFile?.build? 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 else
biberLogUrl = "/project/#{$scope.project_id}/output/output.blg" opts.url = "/project/#{$scope.project_id}/output/output.blg"
$http.get biberLogUrl $http opts
.success (log) -> .success (log) ->
window._s = $scope window._s = $scope
biberLogEntries = BibLogParser.parse(log, {}) biberLogEntries = BibLogParser.parse(log, {})
@ -204,6 +224,8 @@ define [
$http { $http {
url: "/project/#{$scope.project_id}/output" url: "/project/#{$scope.project_id}/output"
method: "DELETE" method: "DELETE"
params:
clsiserverid:ide.clsiServerId
headers: headers:
"X-Csrf-Token": window.csrfToken "X-Csrf-Token": window.csrfToken
} }
@ -286,6 +308,7 @@ define [
file: path file: path
line: row + 1 line: row + 1
column: column column: column
clsiserverid:ide.clsiServerId
} }
}) })
.success (data) -> .success (data) ->
@ -313,6 +336,7 @@ define [
page: position.page + 1 page: position.page + 1
h: position.offset.left.toFixed(2) h: position.offset.left.toFixed(2)
v: position.offset.top.toFixed(2) v: position.offset.top.toFixed(2)
clsiserverid:ide.clsiServerId
} }
}) })
.success (data) -> .success (data) ->

View file

@ -5,11 +5,15 @@ define [
$scope.status = $scope.status =
loading:true 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) -> .success (data) ->
$scope.status.loading = false $scope.status.loading = false
$scope.data = data.texcount $scope.data = data.texcount
console.log $scope.data
.error () -> .error () ->
$scope.status.error = true $scope.status.error = true

View file

@ -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()

View file

@ -7,6 +7,11 @@ SandboxedModule = require('sandboxed-module')
describe "ClsiManager", -> describe "ClsiManager", ->
beforeEach -> 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: @ClsiManager = SandboxedModule.require modulePath, requires:
"settings-sharelatex": @settings = "settings-sharelatex": @settings =
apis: apis:
@ -19,14 +24,16 @@ describe "ClsiManager", ->
url: "https://clsipremium.example.com" url: "https://clsipremium.example.com"
"../../models/Project": Project: @Project = {} "../../models/Project": Project: @Project = {}
"../Project/ProjectEntityHandler": @ProjectEntityHandler = {} "../Project/ProjectEntityHandler": @ProjectEntityHandler = {}
"./ClsiCookieManager": @ClsiCookieManager
"logger-sharelatex": @logger = { log: sinon.stub(), error: sinon.stub(), warn: sinon.stub() } "logger-sharelatex": @logger = { log: sinon.stub(), error: sinon.stub(), warn: sinon.stub() }
"request": @request = {} "request": @request = sinon.stub()
@project_id = "project-id" @project_id = "project-id"
@callback = sinon.stub() @callback = sinon.stub()
describe "sendRequest", -> describe "sendRequest", ->
beforeEach -> beforeEach ->
@ClsiManager._buildRequest = sinon.stub().callsArgWith(2, null, @request = "mock-request") @ClsiManager._buildRequest = sinon.stub().callsArgWith(2, null, @request = "mock-request")
@ClsiCookieManager._getServerId.callsArgWith(1, null, "clsi3")
describe "with a successful compile", -> describe "with a successful compile", ->
beforeEach -> beforeEach ->
@ -80,15 +87,15 @@ describe "ClsiManager", ->
describe "deleteAuxFiles", -> describe "deleteAuxFiles", ->
beforeEach -> beforeEach ->
@request.del = sinon.stub().callsArg(1) @ClsiManager._makeRequest = sinon.stub().callsArg(2)
describe "with the standard compileGroup", -> describe "with the standard compileGroup", ->
beforeEach -> beforeEach ->
@ClsiManager.deleteAuxFiles @project_id, {compileGroup: "standard"}, @callback @ClsiManager.deleteAuxFiles @project_id, {compileGroup: "standard"}, @callback
it "should call the delete method in the standard CLSI", -> it "should call the delete method in the standard CLSI", ->
@request.del @ClsiManager._makeRequest
.calledWith("#{@settings.apis.clsi.url}/project/#{@project_id}") .calledWith(@project_id, { method:"DELETE", url:"#{@settings.apis.clsi.url}/project/#{@project_id}"})
.should.equal true .should.equal true
it "should call the callback", -> it "should call the callback", ->
@ -99,8 +106,8 @@ describe "ClsiManager", ->
@ClsiManager.deleteAuxFiles @project_id, {compileGroup: "priority"}, @callback @ClsiManager.deleteAuxFiles @project_id, {compileGroup: "priority"}, @callback
it "should call the delete method in the CLSI", -> it "should call the delete method in the CLSI", ->
@request.del @ClsiManager._makeRequest
.calledWith("#{@settings.apis.clsi_priority.url}/project/#{@project_id}") .calledWith(@project_id, { method:"DELETE", url:"#{@settings.apis.clsi_priority.url}/project/#{@project_id}"})
.should.equal true .should.equal true
describe "_buildRequest", -> describe "_buildRequest", ->
@ -235,15 +242,15 @@ describe "ClsiManager", ->
describe "successfully", -> describe "successfully", ->
beforeEach -> 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 @ClsiManager._postToClsi @project_id, @req, "standard", @callback
it 'should send the request to the CLSI', -> it 'should send the request to the CLSI', ->
url = "#{@settings.apis.clsi.url}/project/#{@project_id}/compile" url = "#{@settings.apis.clsi.url}/project/#{@project_id}/compile"
@request.post.calledWith({ @ClsiManager._makeRequest.calledWith(@project_id, {
method: "POST",
url: url url: url
json: @req json: @req
jar: false
}).should.equal true }).should.equal true
it "should call the callback with the body and no error", -> it "should call the callback with the body and no error", ->
@ -251,7 +258,7 @@ describe "ClsiManager", ->
describe "when the CLSI returns an error", -> describe "when the CLSI returns an error", ->
beforeEach -> 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 @ClsiManager._postToClsi @project_id, @req, "standard", @callback
it "should call the callback with the body and the error", -> it "should call the callback with the body and the error", ->
@ -259,20 +266,20 @@ describe "ClsiManager", ->
describe "when the compiler is priority", -> describe "when the compiler is priority", ->
beforeEach -> beforeEach ->
@request.post = sinon.stub().callsArgWith(1, null, {statusCode: 500}, @body = { mock: "foo" }) @ClsiManager._makeRequest = sinon.stub()
@ClsiManager._postToClsi @project_id, @req, "priority", @callback @ClsiManager._postToClsi @project_id, @req, "priority", @callback
it "should use the clsi_priority url", -> it "should use the clsi_priority url", ->
url = "#{@settings.apis.clsi_priority.url}/project/#{@project_id}/compile" url = "#{@settings.apis.clsi_priority.url}/project/#{@project_id}/compile"
@request.post.calledWith({ @ClsiManager._makeRequest.calledWith(@project_id, {
method: "POST",
url: url url: url
json: @req json: @req
jar: false
}).should.equal true }).should.equal true
describe "wordCount", -> describe "wordCount", ->
beforeEach -> 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._buildRequest = sinon.stub().callsArgWith(2, null, @req = { compile: { rootResourcePath: "rootfile.text", options: {} } })
@ClsiManager._getCompilerUrl = sinon.stub().returns "compiler.url" @ClsiManager._getCompilerUrl = sinon.stub().returns "compiler.url"
@ -281,8 +288,8 @@ describe "ClsiManager", ->
@ClsiManager.wordCount @project_id, false, {}, @callback @ClsiManager.wordCount @project_id, false, {}, @callback
it "should call wordCount with root file", -> it "should call wordCount with root file", ->
@request.get @ClsiManager._makeRequest
.calledWith({ url: "compiler.url/project/#{@project_id}/wordcount?file=rootfile.text" }) .calledWith(@project_id, { method: "GET", url: "compiler.url/project/#{@project_id}/wordcount?file=rootfile.text" })
.should.equal true .should.equal true
it "should call the callback", -> it "should call the callback", ->
@ -293,8 +300,8 @@ describe "ClsiManager", ->
@ClsiManager.wordCount @project_id, "main.tex", {}, @callback @ClsiManager.wordCount @project_id, "main.tex", {}, @callback
it "should call wordCount with param file", -> it "should call wordCount with param file", ->
@request.get @ClsiManager._makeRequest
.calledWith({ url: "compiler.url/project/#{@project_id}/wordcount?file=main.tex" }) .calledWith(@project_id, { method: "GET", url: "compiler.url/project/#{@project_id}/wordcount?file=main.tex" })
.should.equal true .should.equal true
describe "with image", -> describe "with image", ->
@ -303,6 +310,50 @@ describe "ClsiManager", ->
@ClsiManager.wordCount @project_id, "main.tex", {}, @callback @ClsiManager.wordCount @project_id, "main.tex", {}, @callback
it "should call wordCount with file and image", -> it "should call wordCount with file and image", ->
@request.get @ClsiManager._makeRequest
.calledWith({ url: "compiler.url/project/#{@project_id}/wordcount?file=main.tex&image=#{encodeURIComponent(@image)}" }) .calledWith(@project_id, { method: "GET", url: "compiler.url/project/#{@project_id}/wordcount?file=main.tex&image=#{encodeURIComponent(@image)}" })
.should.equal true .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()

View file

@ -22,6 +22,9 @@ describe "CompileController", ->
url: "clsi.example.com" url: "clsi.example.com"
clsi_priority: clsi_priority:
url: "clsi-priority.example.com" url: "clsi-priority.example.com"
@jar = {cookie:"stuff"}
@ClsiCookieManager =
getCookieJar:sinon.stub().callsArgWith(1, null, @jar)
@CompileController = SandboxedModule.require modulePath, requires: @CompileController = SandboxedModule.require modulePath, requires:
"settings-sharelatex": @settings "settings-sharelatex": @settings
"request": @request = sinon.stub() "request": @request = sinon.stub()
@ -33,6 +36,7 @@ describe "CompileController", ->
"./ClsiManager": @ClsiManager "./ClsiManager": @ClsiManager
"../Authentication/AuthenticationController": @AuthenticationController = {} "../Authentication/AuthenticationController": @AuthenticationController = {}
"../../infrastructure/RateLimiter":@RateLimiter "../../infrastructure/RateLimiter":@RateLimiter
"./ClsiCookieManager":@ClsiCookieManager
@project_id = "project-id" @project_id = "project-id"
@user = @user =
features: features:
@ -182,6 +186,7 @@ describe "CompileController", ->
it "should open a request to the CLSI", -> it "should open a request to the CLSI", ->
@request @request
.calledWith( .calledWith(
jar:@jar
method: @req.method method: @req.method
url: "#{@settings.apis.clsi.url}#{@url}", url: "#{@settings.apis.clsi.url}#{@url}",
timeout: 60 * 1000 timeout: 60 * 1000
@ -204,6 +209,7 @@ describe "CompileController", ->
it "should proxy to the priority url if the user has the feature", ()-> it "should proxy to the priority url if the user has the feature", ()->
@request @request
.calledWith( .calledWith(
jar:@jar
method: @req.method method: @req.method
url: "#{@settings.apis.clsi_priority.url}#{@url}", url: "#{@settings.apis.clsi_priority.url}#{@url}",
timeout: 60 * 1000 timeout: 60 * 1000
@ -218,6 +224,7 @@ describe "CompileController", ->
it "should open a request to the CLSI", -> it "should open a request to the CLSI", ->
@request @request
.calledWith( .calledWith(
jar:@jar
method: @req.method method: @req.method
url: "#{@settings.apis.clsi.url}#{@url}", url: "#{@settings.apis.clsi.url}#{@url}",
timeout: 60 * 1000 timeout: 60 * 1000
@ -240,6 +247,7 @@ describe "CompileController", ->
it "should proxy to the priority url if the user has the feature", ()-> it "should proxy to the priority url if the user has the feature", ()->
@request @request
.calledWith( .calledWith(
jar:@jar
method: @req.method method: @req.method
url: "#{@settings.apis.clsi_priority.url}#{@url}", url: "#{@settings.apis.clsi_priority.url}#{@url}",
timeout: 60 * 1000 timeout: 60 * 1000
@ -254,6 +262,7 @@ describe "CompileController", ->
it "should proxy to the standard url", ()-> it "should proxy to the standard url", ()->
@request @request
.calledWith( .calledWith(
jar:@jar
method: @req.method method: @req.method
url: "#{@settings.apis.clsi.url}#{@url}", url: "#{@settings.apis.clsi.url}#{@url}",
timeout: 60 * 1000 timeout: 60 * 1000
@ -269,6 +278,7 @@ describe "CompileController", ->
it "should proxy to the standard url without the build parameter", ()-> it "should proxy to the standard url without the build parameter", ()->
@request @request
.calledWith( .calledWith(
jar:@jar
method: @req.method method: @req.method
url: "#{@settings.apis.clsi.url}#{@url}", url: "#{@settings.apis.clsi.url}#{@url}",
timeout: 60 * 1000 timeout: 60 * 1000
@ -286,6 +296,7 @@ describe "CompileController", ->
it "should open a request to the CLSI", -> it "should open a request to the CLSI", ->
@request @request
.calledWith( .calledWith(
jar:@jar
method: @req.method method: @req.method
url: "#{@settings.apis.clsi.url}#{@url}", url: "#{@settings.apis.clsi.url}#{@url}",
timeout: 60 * 1000 timeout: 60 * 1000
@ -313,6 +324,7 @@ describe "CompileController", ->
it "should proxy to the priority url if the user has the feature", ()-> it "should proxy to the priority url if the user has the feature", ()->
@request @request
.calledWith( .calledWith(
jar:@jar
method: @req.method method: @req.method
url: "#{@settings.apis.clsi_priority.url}#{@url}", url: "#{@settings.apis.clsi_priority.url}#{@url}",
timeout: 60 * 1000 timeout: 60 * 1000
@ -331,8 +343,8 @@ describe "CompileController", ->
@CompileController.proxyToClsi(@project_id, @url = "/test", @req, @res, @next) @CompileController.proxyToClsi(@project_id, @url = "/test", @req, @res, @next)
it "should proxy to the standard url with the build parameter", ()-> it "should proxy to the standard url with the build parameter", ()->
@request @request.calledWith(
.calledWith( jar:@jar
method: @req.method method: @req.method
qs: {build: 1234} qs: {build: 1234}
url: "#{@settings.apis.clsi.url}#{@url}", url: "#{@settings.apis.clsi.url}#{@url}",

View file

@ -63,6 +63,10 @@ class MockResponse
@body = body if body @body = body if body
@callback() if @callback? @callback() if @callback?
status: (@statusCode)->
return @
setHeader: (header, value) -> setHeader: (header, value) ->
@headers[header] = value @headers[header] = value