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/CompileManager.js"
|
|
|
|
assert = require("chai").assert
|
|
|
|
SandboxedModule = require('sandboxed-module')
|
|
|
|
|
|
|
|
describe "CompileManager", ->
|
|
|
|
beforeEach ->
|
|
|
|
@rateLimitGetStub = sinon.stub()
|
|
|
|
rateLimitGetStub = @rateLimitGetStub
|
2014-02-28 12:59:54 -05:00
|
|
|
@ratelimiter =
|
|
|
|
addCount: sinon.stub()
|
2014-02-12 05:23:40 -05:00
|
|
|
@CompileManager = SandboxedModule.require modulePath, requires:
|
|
|
|
"settings-sharelatex": @settings =
|
|
|
|
redis: web: {host: "localhost", port: 42}
|
2014-09-26 12:49:31 -04:00
|
|
|
"redis-sharelatex":
|
2014-02-12 05:23:40 -05:00
|
|
|
createClient: () => @rclient = { auth: () -> }
|
|
|
|
"../DocumentUpdater/DocumentUpdaterHandler": @DocumentUpdaterHandler = {}
|
|
|
|
"../Project/ProjectRootDocManager": @ProjectRootDocManager = {}
|
|
|
|
"../../models/Project": Project: @Project = {}
|
2014-11-28 09:26:21 -05:00
|
|
|
"../User/UserGetter": @UserGetter = {}
|
2014-02-12 05:23:40 -05:00
|
|
|
"./ClsiManager": @ClsiManager = {}
|
2014-02-28 12:59:54 -05:00
|
|
|
"../../infrastructure/RateLimiter": @ratelimiter
|
2014-02-12 05:23:40 -05:00
|
|
|
"../../infrastructure/Metrics": @Metrics =
|
|
|
|
Timer: class Timer
|
|
|
|
done: sinon.stub()
|
|
|
|
inc: sinon.stub()
|
2016-03-21 09:54:45 -04:00
|
|
|
"logger-sharelatex": @logger = { log: sinon.stub(), warn: sinon.stub() }
|
2014-02-12 05:23:40 -05:00
|
|
|
@project_id = "mock-project-id-123"
|
|
|
|
@user_id = "mock-user-id-123"
|
|
|
|
@callback = sinon.stub()
|
2014-11-28 09:26:21 -05:00
|
|
|
@limits = {
|
|
|
|
timeout: 42
|
|
|
|
}
|
2014-02-12 05:23:40 -05:00
|
|
|
|
2014-11-28 09:26:21 -05:00
|
|
|
|
2014-02-12 05:23:40 -05:00
|
|
|
describe "compile", ->
|
|
|
|
beforeEach ->
|
|
|
|
@CompileManager._checkIfRecentlyCompiled = sinon.stub().callsArgWith(2, null, false)
|
|
|
|
@CompileManager._ensureRootDocumentIsSet = sinon.stub().callsArgWith(1, null)
|
|
|
|
@DocumentUpdaterHandler.flushProjectToMongo = sinon.stub().callsArgWith(1, null)
|
2014-11-28 09:26:21 -05:00
|
|
|
@CompileManager.getProjectCompileLimits = sinon.stub().callsArgWith(1, null, @limits)
|
|
|
|
@ClsiManager.sendRequest = sinon.stub().callsArgWith(2, null, @status = "mock-status", @outputFiles = "mock output files", @output = "mock output")
|
2014-02-12 05:23:40 -05:00
|
|
|
|
|
|
|
describe "succesfully", ->
|
|
|
|
beforeEach ->
|
|
|
|
@CompileManager._checkIfAutoCompileLimitHasBeenHit = (_, cb)-> cb(null, true)
|
|
|
|
@CompileManager.compile @project_id, @user_id, {}, @callback
|
|
|
|
|
|
|
|
it "should check the project has not been recently compiled", ->
|
|
|
|
@CompileManager._checkIfRecentlyCompiled
|
|
|
|
.calledWith(@project_id, @user_id)
|
|
|
|
.should.equal true
|
|
|
|
|
|
|
|
it "should flush the project to the database", ->
|
|
|
|
@DocumentUpdaterHandler.flushProjectToMongo
|
|
|
|
.calledWith(@project_id)
|
|
|
|
.should.equal true
|
|
|
|
|
|
|
|
it "should ensure that the root document is set", ->
|
|
|
|
@CompileManager._ensureRootDocumentIsSet
|
|
|
|
.calledWith(@project_id)
|
|
|
|
.should.equal true
|
|
|
|
|
2014-11-28 09:26:21 -05:00
|
|
|
it "should get the project compile limits", ->
|
|
|
|
@CompileManager.getProjectCompileLimits
|
2014-02-12 05:23:40 -05:00
|
|
|
.calledWith(@project_id)
|
|
|
|
.should.equal true
|
|
|
|
|
2014-11-28 09:26:21 -05:00
|
|
|
it "should run the compile with the compile limits", ->
|
|
|
|
@ClsiManager.sendRequest
|
|
|
|
.calledWith(@project_id, {
|
|
|
|
timeout: @limits.timeout
|
|
|
|
})
|
|
|
|
.should.equal true
|
|
|
|
|
|
|
|
it "should call the callback with the output", ->
|
2014-02-12 05:23:40 -05:00
|
|
|
@callback
|
2014-11-28 09:26:21 -05:00
|
|
|
.calledWith(null, @status, @outputFiles, @output)
|
2014-02-12 05:23:40 -05:00
|
|
|
.should.equal true
|
|
|
|
|
|
|
|
it "should time the compile", ->
|
|
|
|
@Metrics.Timer::done.called.should.equal true
|
|
|
|
|
|
|
|
it "should log out the compile", ->
|
|
|
|
@logger.log
|
|
|
|
.calledWith(project_id: @project_id, user_id: @user_id, "compiling project")
|
|
|
|
.should.equal true
|
|
|
|
|
|
|
|
describe "when the project has been recently compiled", ->
|
2016-03-21 09:54:45 -04:00
|
|
|
it "should return", (done)->
|
2014-02-12 05:23:40 -05:00
|
|
|
@CompileManager._checkIfAutoCompileLimitHasBeenHit = (_, cb)-> cb(null, true)
|
|
|
|
@CompileManager._checkIfRecentlyCompiled = sinon.stub().callsArgWith(2, null, true)
|
2016-03-21 09:54:45 -04:00
|
|
|
@CompileManager.compile @project_id, @user_id, {}, (err, status)->
|
|
|
|
status.should.equal "too-recently-compiled"
|
|
|
|
done()
|
2014-02-12 05:23:40 -05:00
|
|
|
|
|
|
|
describe "should check the rate limit", ->
|
|
|
|
it "should return", (done)->
|
|
|
|
@CompileManager._checkIfAutoCompileLimitHasBeenHit = sinon.stub().callsArgWith(1, null, false)
|
2014-05-19 10:28:35 -04:00
|
|
|
@CompileManager.compile @project_id, @user_id, {}, (err, status)->
|
|
|
|
status.should.equal "autocompile-backoff"
|
2014-02-12 05:23:40 -05:00
|
|
|
done()
|
2014-11-28 09:26:21 -05:00
|
|
|
|
|
|
|
describe "getProjectCompileLimits", ->
|
|
|
|
beforeEach ->
|
|
|
|
@features = {
|
|
|
|
compileTimeout: @timeout = 42
|
|
|
|
compileGroup: @group = "priority"
|
|
|
|
}
|
|
|
|
@Project.findById = sinon.stub().callsArgWith(2, null, @project = { owner_ref: @owner_id = "owner-id-123" })
|
|
|
|
@UserGetter.getUser = sinon.stub().callsArgWith(2, null, @user = { features: @features })
|
|
|
|
@CompileManager.getProjectCompileLimits @project_id, @callback
|
|
|
|
|
|
|
|
it "should look up the owner of the project", ->
|
|
|
|
@Project.findById
|
|
|
|
.calledWith(@project_id, { owner_ref: 1 })
|
|
|
|
.should.equal true
|
|
|
|
|
|
|
|
it "should look up the owner's features", ->
|
|
|
|
@UserGetter.getUser
|
|
|
|
.calledWith(@project.owner_ref, { features: 1 })
|
|
|
|
.should.equal true
|
|
|
|
|
|
|
|
it "should return the limits", ->
|
|
|
|
@callback
|
|
|
|
.calledWith(null, {
|
|
|
|
timeout: @timeout
|
|
|
|
compileGroup: @group
|
|
|
|
})
|
|
|
|
.should.equal true
|
2014-12-01 07:19:01 -05:00
|
|
|
|
|
|
|
describe "deleteAuxFiles", ->
|
2014-02-12 05:23:40 -05:00
|
|
|
beforeEach ->
|
2014-12-01 07:19:01 -05:00
|
|
|
@CompileManager.getProjectCompileLimits = sinon.stub().callsArgWith 1, null, @limits = { compileGroup: "mock-compile-group" }
|
|
|
|
@ClsiManager.deleteAuxFiles = sinon.stub().callsArg(2)
|
|
|
|
@CompileManager.deleteAuxFiles @project_id, @callback
|
|
|
|
|
|
|
|
it "should look up the compile group to use", ->
|
|
|
|
@CompileManager.getProjectCompileLimits
|
2014-02-12 05:23:40 -05:00
|
|
|
.calledWith(@project_id)
|
|
|
|
.should.equal true
|
2014-12-01 07:19:01 -05:00
|
|
|
|
|
|
|
it "should delete the aux files", ->
|
|
|
|
@ClsiManager.deleteAuxFiles
|
|
|
|
.calledWith(@project_id, @limits)
|
2014-02-12 05:23:40 -05:00
|
|
|
.should.equal true
|
2014-12-01 07:19:01 -05:00
|
|
|
|
|
|
|
it "should call the callback", ->
|
|
|
|
@callback.called.should.equal true
|
2014-02-12 05:23:40 -05:00
|
|
|
|
|
|
|
describe "_checkIfRecentlyCompiled", ->
|
|
|
|
describe "when the key exists in redis", ->
|
|
|
|
beforeEach ->
|
|
|
|
@rclient.set = sinon.stub().callsArgWith(5, null, null)
|
|
|
|
@CompileManager._checkIfRecentlyCompiled(@project_id, @user_id, @callback)
|
|
|
|
|
|
|
|
it "should try to set the key", ->
|
|
|
|
@rclient.set
|
|
|
|
.calledWith("compile:#{@project_id}:#{@user_id}", true, "EX", @CompileManager.COMPILE_DELAY, "NX")
|
|
|
|
.should.equal true
|
|
|
|
|
|
|
|
it "should call the callback with true", ->
|
|
|
|
@callback.calledWith(null, true).should.equal true
|
|
|
|
|
|
|
|
describe "when the key does not exist in redis", ->
|
|
|
|
beforeEach ->
|
|
|
|
@rclient.set = sinon.stub().callsArgWith(5, null, "OK")
|
|
|
|
@CompileManager._checkIfRecentlyCompiled(@project_id, @user_id, @callback)
|
|
|
|
|
|
|
|
it "should try to set the key", ->
|
|
|
|
@rclient.set
|
|
|
|
.calledWith("compile:#{@project_id}:#{@user_id}", true, "EX", @CompileManager.COMPILE_DELAY, "NX")
|
|
|
|
.should.equal true
|
|
|
|
|
|
|
|
it "should call the callback with false", ->
|
|
|
|
@callback.calledWith(null, false).should.equal true
|
|
|
|
|
|
|
|
describe "_ensureRootDocumentIsSet", ->
|
|
|
|
beforeEach ->
|
|
|
|
@project = {}
|
|
|
|
@Project.findById = sinon.stub().callsArgWith(2, null, @project)
|
|
|
|
@ProjectRootDocManager.setRootDocAutomatically = sinon.stub().callsArgWith(1, null)
|
|
|
|
|
|
|
|
describe "when the root doc is set", ->
|
|
|
|
beforeEach ->
|
|
|
|
@project.rootDoc_id = "root-doc-id"
|
|
|
|
@CompileManager._ensureRootDocumentIsSet(@project_id, @callback)
|
|
|
|
|
|
|
|
it "should find the project with only the rootDoc_id fiel", ->
|
|
|
|
@Project.findById
|
|
|
|
.calledWith(@project_id, "rootDoc_id")
|
|
|
|
.should.equal true
|
|
|
|
|
|
|
|
it "should not try to update the project rootDoc_id", ->
|
|
|
|
@ProjectRootDocManager.setRootDocAutomatically
|
|
|
|
.called.should.equal false
|
|
|
|
|
|
|
|
it "should call the callback", ->
|
|
|
|
@callback.called.should.equal true
|
|
|
|
|
|
|
|
describe "when the root doc is not set", ->
|
|
|
|
beforeEach ->
|
|
|
|
@CompileManager._ensureRootDocumentIsSet(@project_id, @callback)
|
|
|
|
|
|
|
|
it "should find the project with only the rootDoc_id fiel", ->
|
|
|
|
@Project.findById
|
|
|
|
.calledWith(@project_id, "rootDoc_id")
|
|
|
|
.should.equal true
|
|
|
|
|
|
|
|
it "should update the project rootDoc_id", ->
|
|
|
|
@ProjectRootDocManager.setRootDocAutomatically
|
|
|
|
.calledWith(@project_id)
|
|
|
|
.should.equal true
|
|
|
|
|
|
|
|
it "should call the callback", ->
|
|
|
|
@callback.called.should.equal true
|
|
|
|
|
|
|
|
describe "when the project does not exist", ->
|
|
|
|
beforeEach ->
|
|
|
|
@Project.findById = sinon.stub().callsArgWith(2, null, null)
|
|
|
|
@CompileManager._ensureRootDocumentIsSet(@project_id, @callback)
|
|
|
|
|
|
|
|
it "should call the callback with an error", ->
|
|
|
|
@callback.calledWith(new Error("project not found")).should.equal true
|
|
|
|
|
|
|
|
describe "_checkIfAutoCompileLimitHasBeenHit", ->
|
|
|
|
|
|
|
|
it "should be able to compile if it is not an autocompile", (done)->
|
2014-02-28 12:59:54 -05:00
|
|
|
@ratelimiter.addCount.callsArgWith(1, null, true)
|
2014-02-12 05:23:40 -05:00
|
|
|
@CompileManager._checkIfAutoCompileLimitHasBeenHit false, (err, canCompile)=>
|
|
|
|
canCompile.should.equal true
|
|
|
|
done()
|
|
|
|
|
|
|
|
it "should be able to compile if rate limit has remianing", (done)->
|
2014-02-28 12:59:54 -05:00
|
|
|
@ratelimiter.addCount.callsArgWith(1, null, true)
|
2014-02-12 05:23:40 -05:00
|
|
|
@CompileManager._checkIfAutoCompileLimitHasBeenHit true, (err, canCompile)=>
|
2014-02-28 12:59:54 -05:00
|
|
|
args = @ratelimiter.addCount.args[0][0]
|
2015-02-18 16:27:59 -05:00
|
|
|
args.throttle.should.equal 25
|
2014-02-28 12:59:54 -05:00
|
|
|
args.subjectName.should.equal "everyone"
|
2015-02-18 16:27:59 -05:00
|
|
|
args.timeInterval.should.equal 20
|
2014-02-28 12:59:54 -05:00
|
|
|
args.endpointName.should.equal "auto_compile"
|
2014-02-12 05:23:40 -05:00
|
|
|
canCompile.should.equal true
|
|
|
|
done()
|
|
|
|
|
|
|
|
it "should be not able to compile if rate limit has no remianing", (done)->
|
2014-02-28 12:59:54 -05:00
|
|
|
@ratelimiter.addCount.callsArgWith(1, null, false)
|
2014-02-12 05:23:40 -05:00
|
|
|
@CompileManager._checkIfAutoCompileLimitHasBeenHit true, (err, canCompile)=>
|
|
|
|
canCompile.should.equal false
|
|
|
|
done()
|
|
|
|
|
|
|
|
it "should return false if there is an error in the rate limit", (done)->
|
2014-02-28 12:59:54 -05:00
|
|
|
@ratelimiter.addCount.callsArgWith(1, "error")
|
2014-02-12 05:23:40 -05:00
|
|
|
@CompileManager._checkIfAutoCompileLimitHasBeenHit true, (err, canCompile)=>
|
|
|
|
canCompile.should.equal false
|
|
|
|
done()
|
2015-09-11 09:21:05 -04:00
|
|
|
|
|
|
|
describe "wordCount", ->
|
|
|
|
beforeEach ->
|
|
|
|
@CompileManager.getProjectCompileLimits = sinon.stub().callsArgWith 1, null, @limits = { compileGroup: "mock-compile-group" }
|
|
|
|
@ClsiManager.wordCount = sinon.stub().callsArg(3)
|
|
|
|
@CompileManager.wordCount @project_id, false, @callback
|
|
|
|
|
|
|
|
it "should look up the compile group to use", ->
|
|
|
|
@CompileManager.getProjectCompileLimits
|
|
|
|
.calledWith(@project_id)
|
|
|
|
.should.equal true
|
|
|
|
|
|
|
|
it "should call wordCount for project", ->
|
|
|
|
@ClsiManager.wordCount
|
|
|
|
.calledWith(@project_id, false, @limits)
|
|
|
|
.should.equal true
|
|
|
|
|
|
|
|
it "should call the callback", ->
|
|
|
|
@callback.called.should.equal true
|