CompileManagerTests.coffee 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. sinon = require('sinon')
  2. chai = require('chai')
  3. should = chai.should()
  4. expect = chai.expect
  5. modulePath = "../../../../app/js/Features/Compile/CompileManager.js"
  6. assert = require("chai").assert
  7. SandboxedModule = require('sandboxed-module')
  8. describe "CompileManager", ->
  9. beforeEach ->
  10. @rateLimitGetStub = sinon.stub()
  11. rateLimitGetStub = @rateLimitGetStub
  12. @ratelimiter =
  13. addCount: sinon.stub()
  14. @CompileManager = SandboxedModule.require modulePath, requires:
  15. "settings-sharelatex": @settings =
  16. redis: web: {host: "localhost", port: 42}
  17. "redis-sharelatex":
  18. createClient: () => @rclient = { auth: () -> }
  19. "../DocumentUpdater/DocumentUpdaterHandler": @DocumentUpdaterHandler = {}
  20. "../Project/ProjectRootDocManager": @ProjectRootDocManager = {}
  21. "../../models/Project": Project: @Project = {}
  22. "../User/UserGetter": @UserGetter = {}
  23. "./ClsiManager": @ClsiManager = {}
  24. "../../infrastructure/RateLimiter": @ratelimiter
  25. "../../infrastructure/Metrics": @Metrics =
  26. Timer: class Timer
  27. done: sinon.stub()
  28. inc: sinon.stub()
  29. "logger-sharelatex": @logger = { log: sinon.stub() }
  30. @project_id = "mock-project-id-123"
  31. @user_id = "mock-user-id-123"
  32. @callback = sinon.stub()
  33. @limits = {
  34. timeout: 42
  35. }
  36. describe "compile", ->
  37. beforeEach ->
  38. @CompileManager._checkIfRecentlyCompiled = sinon.stub().callsArgWith(2, null, false)
  39. @CompileManager._ensureRootDocumentIsSet = sinon.stub().callsArgWith(1, null)
  40. @DocumentUpdaterHandler.flushProjectToMongo = sinon.stub().callsArgWith(1, null)
  41. @CompileManager.getProjectCompileLimits = sinon.stub().callsArgWith(1, null, @limits)
  42. @ClsiManager.sendRequest = sinon.stub().callsArgWith(2, null, @status = "mock-status", @outputFiles = "mock output files", @output = "mock output")
  43. describe "succesfully", ->
  44. beforeEach ->
  45. @CompileManager._checkIfAutoCompileLimitHasBeenHit = (_, cb)-> cb(null, true)
  46. @CompileManager.compile @project_id, @user_id, {}, @callback
  47. it "should check the project has not been recently compiled", ->
  48. @CompileManager._checkIfRecentlyCompiled
  49. .calledWith(@project_id, @user_id)
  50. .should.equal true
  51. it "should flush the project to the database", ->
  52. @DocumentUpdaterHandler.flushProjectToMongo
  53. .calledWith(@project_id)
  54. .should.equal true
  55. it "should ensure that the root document is set", ->
  56. @CompileManager._ensureRootDocumentIsSet
  57. .calledWith(@project_id)
  58. .should.equal true
  59. it "should get the project compile limits", ->
  60. @CompileManager.getProjectCompileLimits
  61. .calledWith(@project_id)
  62. .should.equal true
  63. it "should run the compile with the compile limits", ->
  64. @ClsiManager.sendRequest
  65. .calledWith(@project_id, {
  66. timeout: @limits.timeout
  67. })
  68. .should.equal true
  69. it "should call the callback with the output", ->
  70. @callback
  71. .calledWith(null, @status, @outputFiles, @output)
  72. .should.equal true
  73. it "should time the compile", ->
  74. @Metrics.Timer::done.called.should.equal true
  75. it "should log out the compile", ->
  76. @logger.log
  77. .calledWith(project_id: @project_id, user_id: @user_id, "compiling project")
  78. .should.equal true
  79. describe "when the project has been recently compiled", ->
  80. beforeEach ->
  81. @CompileManager._checkIfAutoCompileLimitHasBeenHit = (_, cb)-> cb(null, true)
  82. @CompileManager._checkIfRecentlyCompiled = sinon.stub().callsArgWith(2, null, true)
  83. @CompileManager.compile @project_id, @user_id, {}, @callback
  84. it "should return the callback with an error", ->
  85. @callback
  86. .calledWith(new Error("project was recently compiled so not continuing"))
  87. .should.equal true
  88. describe "should check the rate limit", ->
  89. it "should return", (done)->
  90. @CompileManager._checkIfAutoCompileLimitHasBeenHit = sinon.stub().callsArgWith(1, null, false)
  91. @CompileManager.compile @project_id, @user_id, {}, (err, status)->
  92. status.should.equal "autocompile-backoff"
  93. done()
  94. describe "getProjectCompileLimits", ->
  95. beforeEach ->
  96. @features = {
  97. compileTimeout: @timeout = 42
  98. compileGroup: @group = "priority"
  99. }
  100. @Project.findById = sinon.stub().callsArgWith(2, null, @project = { owner_ref: @owner_id = "owner-id-123" })
  101. @UserGetter.getUser = sinon.stub().callsArgWith(2, null, @user = { features: @features })
  102. @CompileManager.getProjectCompileLimits @project_id, @callback
  103. it "should look up the owner of the project", ->
  104. @Project.findById
  105. .calledWith(@project_id, { owner_ref: 1 })
  106. .should.equal true
  107. it "should look up the owner's features", ->
  108. @UserGetter.getUser
  109. .calledWith(@project.owner_ref, { features: 1 })
  110. .should.equal true
  111. it "should return the limits", ->
  112. @callback
  113. .calledWith(null, {
  114. timeout: @timeout
  115. compileGroup: @group
  116. })
  117. .should.equal true
  118. describe "deleteAuxFiles", ->
  119. beforeEach ->
  120. @CompileManager.getProjectCompileLimits = sinon.stub().callsArgWith 1, null, @limits = { compileGroup: "mock-compile-group" }
  121. @ClsiManager.deleteAuxFiles = sinon.stub().callsArg(2)
  122. @CompileManager.deleteAuxFiles @project_id, @callback
  123. it "should look up the compile group to use", ->
  124. @CompileManager.getProjectCompileLimits
  125. .calledWith(@project_id)
  126. .should.equal true
  127. it "should delete the aux files", ->
  128. @ClsiManager.deleteAuxFiles
  129. .calledWith(@project_id, @limits)
  130. .should.equal true
  131. it "should call the callback", ->
  132. @callback.called.should.equal true
  133. describe "_checkIfRecentlyCompiled", ->
  134. describe "when the key exists in redis", ->
  135. beforeEach ->
  136. @rclient.set = sinon.stub().callsArgWith(5, null, null)
  137. @CompileManager._checkIfRecentlyCompiled(@project_id, @user_id, @callback)
  138. it "should try to set the key", ->
  139. @rclient.set
  140. .calledWith("compile:#{@project_id}:#{@user_id}", true, "EX", @CompileManager.COMPILE_DELAY, "NX")
  141. .should.equal true
  142. it "should call the callback with true", ->
  143. @callback.calledWith(null, true).should.equal true
  144. describe "when the key does not exist in redis", ->
  145. beforeEach ->
  146. @rclient.set = sinon.stub().callsArgWith(5, null, "OK")
  147. @CompileManager._checkIfRecentlyCompiled(@project_id, @user_id, @callback)
  148. it "should try to set the key", ->
  149. @rclient.set
  150. .calledWith("compile:#{@project_id}:#{@user_id}", true, "EX", @CompileManager.COMPILE_DELAY, "NX")
  151. .should.equal true
  152. it "should call the callback with false", ->
  153. @callback.calledWith(null, false).should.equal true
  154. describe "_ensureRootDocumentIsSet", ->
  155. beforeEach ->
  156. @project = {}
  157. @Project.findById = sinon.stub().callsArgWith(2, null, @project)
  158. @ProjectRootDocManager.setRootDocAutomatically = sinon.stub().callsArgWith(1, null)
  159. describe "when the root doc is set", ->
  160. beforeEach ->
  161. @project.rootDoc_id = "root-doc-id"
  162. @CompileManager._ensureRootDocumentIsSet(@project_id, @callback)
  163. it "should find the project with only the rootDoc_id fiel", ->
  164. @Project.findById
  165. .calledWith(@project_id, "rootDoc_id")
  166. .should.equal true
  167. it "should not try to update the project rootDoc_id", ->
  168. @ProjectRootDocManager.setRootDocAutomatically
  169. .called.should.equal false
  170. it "should call the callback", ->
  171. @callback.called.should.equal true
  172. describe "when the root doc is not set", ->
  173. beforeEach ->
  174. @CompileManager._ensureRootDocumentIsSet(@project_id, @callback)
  175. it "should find the project with only the rootDoc_id fiel", ->
  176. @Project.findById
  177. .calledWith(@project_id, "rootDoc_id")
  178. .should.equal true
  179. it "should update the project rootDoc_id", ->
  180. @ProjectRootDocManager.setRootDocAutomatically
  181. .calledWith(@project_id)
  182. .should.equal true
  183. it "should call the callback", ->
  184. @callback.called.should.equal true
  185. describe "when the project does not exist", ->
  186. beforeEach ->
  187. @Project.findById = sinon.stub().callsArgWith(2, null, null)
  188. @CompileManager._ensureRootDocumentIsSet(@project_id, @callback)
  189. it "should call the callback with an error", ->
  190. @callback.calledWith(new Error("project not found")).should.equal true
  191. describe "_checkIfAutoCompileLimitHasBeenHit", ->
  192. it "should be able to compile if it is not an autocompile", (done)->
  193. @ratelimiter.addCount.callsArgWith(1, null, true)
  194. @CompileManager._checkIfAutoCompileLimitHasBeenHit false, (err, canCompile)=>
  195. canCompile.should.equal true
  196. done()
  197. it "should be able to compile if rate limit has remianing", (done)->
  198. @ratelimiter.addCount.callsArgWith(1, null, true)
  199. @CompileManager._checkIfAutoCompileLimitHasBeenHit true, (err, canCompile)=>
  200. args = @ratelimiter.addCount.args[0][0]
  201. args.throttle.should.equal 25
  202. args.subjectName.should.equal "everyone"
  203. args.timeInterval.should.equal 20
  204. args.endpointName.should.equal "auto_compile"
  205. canCompile.should.equal true
  206. done()
  207. it "should be not able to compile if rate limit has no remianing", (done)->
  208. @ratelimiter.addCount.callsArgWith(1, null, false)
  209. @CompileManager._checkIfAutoCompileLimitHasBeenHit true, (err, canCompile)=>
  210. canCompile.should.equal false
  211. done()
  212. it "should return false if there is an error in the rate limit", (done)->
  213. @ratelimiter.addCount.callsArgWith(1, "error")
  214. @CompileManager._checkIfAutoCompileLimitHasBeenHit true, (err, canCompile)=>
  215. canCompile.should.equal false
  216. done()