CompileControllerTests.coffee 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473
  1. sinon = require('sinon')
  2. chai = require('chai')
  3. should = chai.should()
  4. assert = require("chai").assert
  5. expect = chai.expect
  6. modulePath = "../../../../app/js/Features/Compile/CompileController.js"
  7. SandboxedModule = require('sandboxed-module')
  8. MockRequest = require "../helpers/MockRequest"
  9. MockResponse = require "../helpers/MockResponse"
  10. describe "CompileController", ->
  11. beforeEach ->
  12. @user_id = 'wat'
  13. @user =
  14. _id: @user_id
  15. email: 'user@example.com'
  16. features:
  17. compileGroup: "premium"
  18. compileTimeout: 100
  19. @CompileManager =
  20. compile: sinon.stub()
  21. @ClsiManager = {}
  22. @UserGetter =
  23. getUser:sinon.stub()
  24. @RateLimiter = {addCount:sinon.stub()}
  25. @settings =
  26. apis:
  27. clsi:
  28. url: "clsi.example.com"
  29. clsi_priority:
  30. url: "clsi-priority.example.com"
  31. defaultFeatures:
  32. compileGroup: 'standard'
  33. compileTimeout: 60
  34. @jar = {cookie:"stuff"}
  35. @ClsiCookieManager =
  36. getCookieJar:sinon.stub().callsArgWith(1, null, @jar)
  37. @AuthenticationController =
  38. getLoggedInUser: sinon.stub().callsArgWith(1, null, @user)
  39. getLoggedInUserId: sinon.stub().returns(@user_id)
  40. getSessionUser: sinon.stub().returns(@user)
  41. isUserLoggedIn: sinon.stub().returns(true)
  42. @CompileController = SandboxedModule.require modulePath, requires:
  43. "settings-sharelatex": @settings
  44. "request": @request = sinon.stub()
  45. '../Project/ProjectGetter': @ProjectGetter = {}
  46. "logger-sharelatex": @logger = { log: sinon.stub(), error: sinon.stub() }
  47. "metrics-sharelatex": @Metrics = { inc: sinon.stub() }
  48. "./CompileManager":@CompileManager
  49. "../User/UserGetter":@UserGetter
  50. "./ClsiManager": @ClsiManager
  51. "../Authentication/AuthenticationController": @AuthenticationController
  52. "../../infrastructure/RateLimiter":@RateLimiter
  53. "./ClsiCookieManager": ()=> @ClsiCookieManager
  54. @project_id = "project-id"
  55. @next = sinon.stub()
  56. @req = new MockRequest()
  57. @res = new MockResponse()
  58. describe "compile", ->
  59. beforeEach ->
  60. @req.params =
  61. Project_id: @project_id
  62. @req.session = {}
  63. @CompileManager.compile = sinon.stub().callsArgWith(3, null, @status = "success", @outputFiles = ["mock-output-files"])
  64. describe "when not an auto compile", ->
  65. beforeEach ->
  66. @CompileController.compile @req, @res, @next
  67. it "should look up the user id", ->
  68. @AuthenticationController.getLoggedInUserId
  69. .calledWith(@req)
  70. .should.equal true
  71. it "should do the compile without the auto compile flag", ->
  72. @CompileManager.compile
  73. .calledWith(@project_id, @user_id, { isAutoCompile: false })
  74. .should.equal true
  75. it "should set the content-type of the response to application/json", ->
  76. @res.contentType
  77. .calledWith("application/json")
  78. .should.equal true
  79. it "should send a successful response reporting the status and files", ->
  80. @res.statusCode.should.equal 200
  81. @res.body.should.equal JSON.stringify({
  82. status: @status
  83. outputFiles: @outputFiles
  84. })
  85. describe "when an auto compile", ->
  86. beforeEach ->
  87. @req.query =
  88. auto_compile: "true"
  89. @CompileController.compile @req, @res, @next
  90. it "should do the compile with the auto compile flag", ->
  91. @CompileManager.compile
  92. .calledWith(@project_id, @user_id, { isAutoCompile: true })
  93. .should.equal true
  94. describe "with the draft attribute", ->
  95. beforeEach ->
  96. @req.body =
  97. draft: true
  98. @CompileController.compile @req, @res, @next
  99. it "should do the compile without the draft compile flag", ->
  100. @CompileManager.compile
  101. .calledWith(@project_id, @user_id, { isAutoCompile: false, draft: true })
  102. .should.equal true
  103. describe "compileSubmission", ->
  104. beforeEach ->
  105. @submission_id = 'sub-1234'
  106. @req.params =
  107. submission_id: @submission_id
  108. @req.body = {}
  109. @ClsiManager.sendExternalRequest = sinon.stub()
  110. .callsArgWith(3, null, @status = "success", @outputFiles = ["mock-output-files"], \
  111. @clsiServerId = "mock-server-id", @validationProblems = null)
  112. it "should set the content-type of the response to application/json", ->
  113. @CompileController.compileSubmission @req, @res, @next
  114. @res.contentType
  115. .calledWith("application/json")
  116. .should.equal true
  117. it "should send a successful response reporting the status and files", ->
  118. @CompileController.compileSubmission @req, @res, @next
  119. @res.statusCode.should.equal 200
  120. @res.body.should.equal JSON.stringify({
  121. status: @status
  122. outputFiles: @outputFiles
  123. clsiServerId: 'mock-server-id'
  124. validationProblems: null
  125. })
  126. describe "with compileGroup and timeout", ->
  127. beforeEach ->
  128. @req.body =
  129. compileGroup: 'special'
  130. timeout: 600
  131. @CompileController.compileSubmission @req, @res, @next
  132. it "should use the supplied values", ->
  133. @ClsiManager.sendExternalRequest
  134. .calledWith(@submission_id, { compileGroup: 'special', timeout: 600 }, \
  135. { compileGroup: 'special', timeout: 600 })
  136. .should.equal true
  137. describe "with other supported options but not compileGroup and timeout", ->
  138. beforeEach ->
  139. @req.body =
  140. rootResourcePath: 'main.tex'
  141. compiler: 'lualatex'
  142. draft: true
  143. check: 'validate'
  144. @CompileController.compileSubmission @req, @res, @next
  145. it "should use the other options but default values for compileGroup and timeout", ->
  146. @ClsiManager.sendExternalRequest
  147. .calledWith(@submission_id, \
  148. {rootResourcePath: 'main.tex', compiler: 'lualatex', draft: true, check: 'validate'}, \
  149. {rootResourcePath: 'main.tex', compiler: 'lualatex', draft: true, check: 'validate', \
  150. compileGroup: 'standard', timeout: 60})
  151. .should.equal true
  152. describe "downloadPdf", ->
  153. beforeEach ->
  154. @req.params =
  155. Project_id: @project_id
  156. @req.query = {pdfng:true}
  157. @project = name: "test namè"
  158. @ProjectGetter.getProject = sinon.stub().callsArgWith(2, null, @project)
  159. describe "when downloading for embedding", ->
  160. beforeEach ->
  161. @CompileController.proxyToClsi = sinon.stub()
  162. @RateLimiter.addCount.callsArgWith(1, null, true)
  163. @CompileController.downloadPdf(@req, @res, @next)
  164. it "should look up the project", ->
  165. @ProjectGetter.getProject
  166. .calledWith(@project_id, {name: 1})
  167. .should.equal true
  168. it "should set the content-type of the response to application/pdf", ->
  169. @res.contentType
  170. .calledWith("application/pdf")
  171. .should.equal true
  172. it "should set the content-disposition header with a safe version of the project name", ->
  173. console.log @res.setContentDisposition.args[0]
  174. @res.setContentDisposition
  175. .calledWith('', filename: "test_nam_.pdf")
  176. .should.equal true
  177. it "should increment the pdf-downloads metric", ->
  178. @Metrics.inc
  179. .calledWith("pdf-downloads")
  180. .should.equal true
  181. it "should proxy the PDF from the CLSI", ->
  182. @CompileController.proxyToClsi.calledWith(@project_id, "/project/#{@project_id}/user/#{@user_id}/output/output.pdf", @req, @res, @next).should.equal true
  183. describe "when the pdf is not going to be used in pdfjs viewer", ->
  184. it "should check the rate limiter when pdfng is not set", (done)->
  185. @req.query = {}
  186. @RateLimiter.addCount.callsArgWith(1, null, true)
  187. @CompileController.proxyToClsi = (project_id, url)=>
  188. @RateLimiter.addCount.args[0][0].throttle.should.equal 1000
  189. done()
  190. @CompileController.downloadPdf @req, @res
  191. it "should check the rate limiter when pdfng is false", (done)->
  192. @req.query = {pdfng:false}
  193. @RateLimiter.addCount.callsArgWith(1, null, true)
  194. @CompileController.proxyToClsi = (project_id, url)=>
  195. @RateLimiter.addCount.args[0][0].throttle.should.equal 1000
  196. done()
  197. @CompileController.downloadPdf @req, @res
  198. describe "getFileFromClsiWithoutUser", ->
  199. beforeEach ->
  200. @submission_id = 'sub-1234'
  201. @build_id = 123456
  202. @file = 'project.pdf'
  203. @req.params =
  204. submission_id: @submission_id
  205. build_id: @build_id
  206. file: @file
  207. @req.body = {}
  208. @expected_url = "/project/#{@submission_id}/build/#{@build_id}/output/#{@file}"
  209. @CompileController.proxyToClsiWithLimits = sinon.stub()
  210. describe "without limits specified", ->
  211. beforeEach ->
  212. @CompileController.getFileFromClsiWithoutUser @req, @res, @next
  213. it "should proxy to CLSI with correct URL and default limits", ->
  214. @CompileController.proxyToClsiWithLimits
  215. .calledWith(@submission_id, @expected_url, {compileGroup: 'standard'})
  216. .should.equal true
  217. describe "with limits specified", ->
  218. beforeEach ->
  219. @req.body = {compileTimeout: 600, compileGroup: 'special'}
  220. @CompileController.getFileFromClsiWithoutUser @req, @res, @next
  221. it "should proxy to CLSI with correct URL and specified limits", ->
  222. @CompileController.proxyToClsiWithLimits
  223. .calledWith(@submission_id, @expected_url, {compileGroup: 'special'})
  224. .should.equal true
  225. describe "proxyToClsi", ->
  226. beforeEach ->
  227. @request.returns(@proxy = {
  228. pipe: sinon.stub()
  229. on: sinon.stub()
  230. })
  231. @upstream =
  232. statusCode: 204
  233. headers: { "mock": "header" }
  234. @req.method = "mock-method"
  235. @req.headers = {
  236. 'Mock': 'Headers',
  237. 'Range': '123-456'
  238. 'If-Range': 'abcdef'
  239. 'If-Modified-Since': 'Mon, 15 Dec 2014 15:23:56 GMT'
  240. }
  241. describe "old pdf viewer", ->
  242. describe "user with standard priority", ->
  243. beforeEach ->
  244. @CompileManager.getProjectCompileLimits = sinon.stub().callsArgWith(1, null, {compileGroup: "standard"})
  245. @CompileController.proxyToClsi(@project_id, @url = "/test", @req, @res, @next)
  246. it "should open a request to the CLSI", ->
  247. @request
  248. .calledWith(
  249. jar:@jar
  250. method: @req.method
  251. url: "#{@settings.apis.clsi.url}#{@url}",
  252. timeout: 60 * 1000
  253. )
  254. .should.equal true
  255. it "should pass the request on to the client", ->
  256. @proxy.pipe
  257. .calledWith(@res)
  258. .should.equal true
  259. it "should bind an error handle to the request proxy", ->
  260. @proxy.on.calledWith("error").should.equal true
  261. describe "user with priority compile", ->
  262. beforeEach ->
  263. @CompileManager.getProjectCompileLimits = sinon.stub().callsArgWith(1, null, {compileGroup: "priority"})
  264. @CompileController.proxyToClsi(@project_id, @url = "/test", @req, @res, @next)
  265. describe "user with standard priority via query string", ->
  266. beforeEach ->
  267. @req.query = {compileGroup: 'standard'}
  268. @CompileController.proxyToClsi(@project_id, @url = "/test", @req, @res, @next)
  269. it "should open a request to the CLSI", ->
  270. @request
  271. .calledWith(
  272. jar:@jar
  273. method: @req.method
  274. url: "#{@settings.apis.clsi.url}#{@url}",
  275. timeout: 60 * 1000
  276. )
  277. .should.equal true
  278. it "should pass the request on to the client", ->
  279. @proxy.pipe
  280. .calledWith(@res)
  281. .should.equal true
  282. it "should bind an error handle to the request proxy", ->
  283. @proxy.on.calledWith("error").should.equal true
  284. describe "user with non-existent priority via query string", ->
  285. beforeEach ->
  286. @req.query = {compileGroup: 'foobar'}
  287. @CompileController.proxyToClsi(@project_id, @url = "/test", @req, @res, @next)
  288. it "should proxy to the standard url", ()->
  289. @request
  290. .calledWith(
  291. jar:@jar
  292. method: @req.method
  293. url: "#{@settings.apis.clsi.url}#{@url}",
  294. timeout: 60 * 1000
  295. )
  296. .should.equal true
  297. describe "user with build parameter via query string", ->
  298. beforeEach ->
  299. @CompileManager.getProjectCompileLimits = sinon.stub().callsArgWith(1, null, {compileGroup: "standard"})
  300. @req.query = {build: 1234}
  301. @CompileController.proxyToClsi(@project_id, @url = "/test", @req, @res, @next)
  302. it "should proxy to the standard url without the build parameter", ()->
  303. @request
  304. .calledWith(
  305. jar:@jar
  306. method: @req.method
  307. url: "#{@settings.apis.clsi.url}#{@url}",
  308. timeout: 60 * 1000
  309. )
  310. .should.equal true
  311. describe "new pdf viewer", ->
  312. beforeEach ->
  313. @req.query = {pdfng: true}
  314. describe "user with standard priority", ->
  315. beforeEach ->
  316. @CompileManager.getProjectCompileLimits = sinon.stub().callsArgWith(1, null, {compileGroup: "standard"})
  317. @CompileController.proxyToClsi(@project_id, @url = "/test", @req, @res, @next)
  318. it "should open a request to the CLSI", ->
  319. @request
  320. .calledWith(
  321. jar:@jar
  322. method: @req.method
  323. url: "#{@settings.apis.clsi.url}#{@url}",
  324. timeout: 60 * 1000
  325. headers: {
  326. 'Range': '123-456'
  327. 'If-Range': 'abcdef'
  328. 'If-Modified-Since': 'Mon, 15 Dec 2014 15:23:56 GMT'
  329. }
  330. )
  331. .should.equal true
  332. it "should pass the request on to the client", ->
  333. @proxy.pipe
  334. .calledWith(@res)
  335. .should.equal true
  336. it "should bind an error handle to the request proxy", ->
  337. @proxy.on.calledWith("error").should.equal true
  338. describe "user with build parameter via query string", ->
  339. beforeEach ->
  340. @CompileManager.getProjectCompileLimits = sinon.stub().callsArgWith(1, null, {compileGroup: "standard"})
  341. @req.query = {build: 1234, pdfng: true}
  342. @CompileController.proxyToClsi(@project_id, @url = "/test", @req, @res, @next)
  343. it "should proxy to the standard url with the build parameter", ()->
  344. @request.calledWith(
  345. jar:@jar
  346. method: @req.method
  347. qs: {build: 1234}
  348. url: "#{@settings.apis.clsi.url}#{@url}",
  349. timeout: 60 * 1000
  350. headers: {
  351. 'Range': '123-456'
  352. 'If-Range': 'abcdef'
  353. 'If-Modified-Since': 'Mon, 15 Dec 2014 15:23:56 GMT'
  354. }
  355. )
  356. .should.equal true
  357. describe "deleteAuxFiles", ->
  358. beforeEach ->
  359. @CompileManager.deleteAuxFiles = sinon.stub().callsArg(2)
  360. @req.params =
  361. Project_id: @project_id
  362. @res.sendStatus = sinon.stub()
  363. @CompileController.deleteAuxFiles @req, @res, @next
  364. it "should proxy to the CLSI", ->
  365. @CompileManager.deleteAuxFiles
  366. .calledWith(@project_id)
  367. .should.equal true
  368. it "should return a 200", ->
  369. @res.sendStatus
  370. .calledWith(200)
  371. .should.equal true
  372. describe "compileAndDownloadPdf", ->
  373. beforeEach ->
  374. @req =
  375. params:
  376. project_id:@project_id
  377. @CompileManager.compile.callsArgWith(3)
  378. @CompileController.proxyToClsi = sinon.stub()
  379. @res =
  380. send:=>
  381. it "should call compile in the compile manager", (done)->
  382. @CompileController.compileAndDownloadPdf @req, @res
  383. @CompileManager.compile.calledWith(@project_id).should.equal true
  384. done()
  385. it "should proxy the res to the clsi with correct url", (done)->
  386. @CompileController.compileAndDownloadPdf @req, @res
  387. sinon.assert.calledWith @CompileController.proxyToClsi, @project_id, "/project/#{@project_id}/output/output.pdf", @req, @res
  388. @CompileController.proxyToClsi.calledWith(@project_id, "/project/#{@project_id}/output/output.pdf", @req, @res).should.equal true
  389. done()
  390. describe "wordCount", ->
  391. beforeEach ->
  392. @CompileManager.wordCount = sinon.stub().callsArgWith(3, null, {content:"body"})
  393. @req.params =
  394. Project_id: @project_id
  395. @res.send = sinon.stub()
  396. @res.contentType = sinon.stub()
  397. @CompileController.wordCount @req, @res, @next
  398. it "should proxy to the CLSI", ->
  399. @CompileManager.wordCount
  400. .calledWith(@project_id, @user_id, false)
  401. .should.equal true
  402. it "should return a 200 and body", ->
  403. @res.send
  404. .calledWith({content:"body"})
  405. .should.equal true