CompileControllerTests.coffee 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486
  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. @res.setContentDisposition
  174. .calledWith('', filename: "test_nam_.pdf")
  175. .should.equal true
  176. it "should increment the pdf-downloads metric", ->
  177. @Metrics.inc
  178. .calledWith("pdf-downloads")
  179. .should.equal true
  180. it "should proxy the PDF from the CLSI", ->
  181. @CompileController.proxyToClsi.calledWith(@project_id, "/project/#{@project_id}/user/#{@user_id}/output/output.pdf", @req, @res, @next).should.equal true
  182. describe "when the a build-id is provided", ->
  183. beforeEach ->
  184. @req.params.build_id = @buildId = '1234-5678'
  185. @CompileController.proxyToClsi = sinon.stub()
  186. @RateLimiter.addCount.callsArgWith(1, null, true)
  187. @CompileController.downloadPdf(@req, @res, @next)
  188. it "should proxy the PDF from the CLSI, with a build-id", ->
  189. @CompileController.proxyToClsi.calledWith(
  190. @project_id,
  191. "/project/#{@project_id}/user/#{@user_id}/build/#{@buildId}/output/output.pdf",
  192. @req, @res, @next
  193. ).should.equal true
  194. describe "when the pdf is not going to be used in pdfjs viewer", ->
  195. it "should check the rate limiter when pdfng is not set", (done)->
  196. @req.query = {}
  197. @RateLimiter.addCount.callsArgWith(1, null, true)
  198. @CompileController.proxyToClsi = (project_id, url)=>
  199. @RateLimiter.addCount.args[0][0].throttle.should.equal 1000
  200. done()
  201. @CompileController.downloadPdf @req, @res
  202. it "should check the rate limiter when pdfng is false", (done)->
  203. @req.query = {pdfng:false}
  204. @RateLimiter.addCount.callsArgWith(1, null, true)
  205. @CompileController.proxyToClsi = (project_id, url)=>
  206. @RateLimiter.addCount.args[0][0].throttle.should.equal 1000
  207. done()
  208. @CompileController.downloadPdf @req, @res
  209. describe "getFileFromClsiWithoutUser", ->
  210. beforeEach ->
  211. @submission_id = 'sub-1234'
  212. @build_id = 123456
  213. @file = 'project.pdf'
  214. @req.params =
  215. submission_id: @submission_id
  216. build_id: @build_id
  217. file: @file
  218. @req.body = {}
  219. @expected_url = "/project/#{@submission_id}/build/#{@build_id}/output/#{@file}"
  220. @CompileController.proxyToClsiWithLimits = sinon.stub()
  221. describe "without limits specified", ->
  222. beforeEach ->
  223. @CompileController.getFileFromClsiWithoutUser @req, @res, @next
  224. it "should proxy to CLSI with correct URL and default limits", ->
  225. @CompileController.proxyToClsiWithLimits
  226. .calledWith(@submission_id, @expected_url, {compileGroup: 'standard'})
  227. .should.equal true
  228. describe "with limits specified", ->
  229. beforeEach ->
  230. @req.body = {compileTimeout: 600, compileGroup: 'special'}
  231. @CompileController.getFileFromClsiWithoutUser @req, @res, @next
  232. it "should proxy to CLSI with correct URL and specified limits", ->
  233. @CompileController.proxyToClsiWithLimits
  234. .calledWith(@submission_id, @expected_url, {compileGroup: 'special'})
  235. .should.equal true
  236. describe "proxyToClsi", ->
  237. beforeEach ->
  238. @request.returns(@proxy = {
  239. pipe: sinon.stub()
  240. on: sinon.stub()
  241. })
  242. @upstream =
  243. statusCode: 204
  244. headers: { "mock": "header" }
  245. @req.method = "mock-method"
  246. @req.headers = {
  247. 'Mock': 'Headers',
  248. 'Range': '123-456'
  249. 'If-Range': 'abcdef'
  250. 'If-Modified-Since': 'Mon, 15 Dec 2014 15:23:56 GMT'
  251. }
  252. describe "old pdf viewer", ->
  253. describe "user with standard priority", ->
  254. beforeEach ->
  255. @CompileManager.getProjectCompileLimits = sinon.stub().callsArgWith(1, null, {compileGroup: "standard"})
  256. @CompileController.proxyToClsi(@project_id, @url = "/test", @req, @res, @next)
  257. it "should open a request to the CLSI", ->
  258. @request
  259. .calledWith(
  260. jar:@jar
  261. method: @req.method
  262. url: "#{@settings.apis.clsi.url}#{@url}",
  263. timeout: 60 * 1000
  264. )
  265. .should.equal true
  266. it "should pass the request on to the client", ->
  267. @proxy.pipe
  268. .calledWith(@res)
  269. .should.equal true
  270. it "should bind an error handle to the request proxy", ->
  271. @proxy.on.calledWith("error").should.equal true
  272. describe "user with priority compile", ->
  273. beforeEach ->
  274. @CompileManager.getProjectCompileLimits = sinon.stub().callsArgWith(1, null, {compileGroup: "priority"})
  275. @CompileController.proxyToClsi(@project_id, @url = "/test", @req, @res, @next)
  276. describe "user with standard priority via query string", ->
  277. beforeEach ->
  278. @req.query = {compileGroup: 'standard'}
  279. @CompileController.proxyToClsi(@project_id, @url = "/test", @req, @res, @next)
  280. it "should open a request to the CLSI", ->
  281. @request
  282. .calledWith(
  283. jar:@jar
  284. method: @req.method
  285. url: "#{@settings.apis.clsi.url}#{@url}",
  286. timeout: 60 * 1000
  287. )
  288. .should.equal true
  289. it "should pass the request on to the client", ->
  290. @proxy.pipe
  291. .calledWith(@res)
  292. .should.equal true
  293. it "should bind an error handle to the request proxy", ->
  294. @proxy.on.calledWith("error").should.equal true
  295. describe "user with non-existent priority via query string", ->
  296. beforeEach ->
  297. @req.query = {compileGroup: 'foobar'}
  298. @CompileController.proxyToClsi(@project_id, @url = "/test", @req, @res, @next)
  299. it "should proxy to the standard url", ()->
  300. @request
  301. .calledWith(
  302. jar:@jar
  303. method: @req.method
  304. url: "#{@settings.apis.clsi.url}#{@url}",
  305. timeout: 60 * 1000
  306. )
  307. .should.equal true
  308. describe "user with build parameter via query string", ->
  309. beforeEach ->
  310. @CompileManager.getProjectCompileLimits = sinon.stub().callsArgWith(1, null, {compileGroup: "standard"})
  311. @req.query = {build: 1234}
  312. @CompileController.proxyToClsi(@project_id, @url = "/test", @req, @res, @next)
  313. it "should proxy to the standard url without the build parameter", ()->
  314. @request
  315. .calledWith(
  316. jar:@jar
  317. method: @req.method
  318. url: "#{@settings.apis.clsi.url}#{@url}",
  319. timeout: 60 * 1000
  320. )
  321. .should.equal true
  322. describe "new pdf viewer", ->
  323. beforeEach ->
  324. @req.query = {pdfng: true}
  325. describe "user with standard priority", ->
  326. beforeEach ->
  327. @CompileManager.getProjectCompileLimits = sinon.stub().callsArgWith(1, null, {compileGroup: "standard"})
  328. @CompileController.proxyToClsi(@project_id, @url = "/test", @req, @res, @next)
  329. it "should open a request to the CLSI", ->
  330. @request
  331. .calledWith(
  332. jar:@jar
  333. method: @req.method
  334. url: "#{@settings.apis.clsi.url}#{@url}",
  335. timeout: 60 * 1000
  336. headers: {
  337. 'Range': '123-456'
  338. 'If-Range': 'abcdef'
  339. 'If-Modified-Since': 'Mon, 15 Dec 2014 15:23:56 GMT'
  340. }
  341. )
  342. .should.equal true
  343. it "should pass the request on to the client", ->
  344. @proxy.pipe
  345. .calledWith(@res)
  346. .should.equal true
  347. it "should bind an error handle to the request proxy", ->
  348. @proxy.on.calledWith("error").should.equal true
  349. describe "user with build parameter via query string", ->
  350. beforeEach ->
  351. @CompileManager.getProjectCompileLimits = sinon.stub().callsArgWith(1, null, {compileGroup: "standard"})
  352. @req.query = {build: 1234, pdfng: true}
  353. @CompileController.proxyToClsi(@project_id, @url = "/test", @req, @res, @next)
  354. it "should proxy to the standard url with the build parameter", ()->
  355. @request.calledWith(
  356. jar:@jar
  357. method: @req.method
  358. qs: {build: 1234}
  359. url: "#{@settings.apis.clsi.url}#{@url}",
  360. timeout: 60 * 1000
  361. headers: {
  362. 'Range': '123-456'
  363. 'If-Range': 'abcdef'
  364. 'If-Modified-Since': 'Mon, 15 Dec 2014 15:23:56 GMT'
  365. }
  366. )
  367. .should.equal true
  368. describe "deleteAuxFiles", ->
  369. beforeEach ->
  370. @CompileManager.deleteAuxFiles = sinon.stub().callsArg(2)
  371. @req.params =
  372. Project_id: @project_id
  373. @res.sendStatus = sinon.stub()
  374. @CompileController.deleteAuxFiles @req, @res, @next
  375. it "should proxy to the CLSI", ->
  376. @CompileManager.deleteAuxFiles
  377. .calledWith(@project_id)
  378. .should.equal true
  379. it "should return a 200", ->
  380. @res.sendStatus
  381. .calledWith(200)
  382. .should.equal true
  383. describe "compileAndDownloadPdf", ->
  384. beforeEach ->
  385. @req =
  386. params:
  387. project_id:@project_id
  388. @CompileManager.compile.callsArgWith(3)
  389. @CompileController.proxyToClsi = sinon.stub()
  390. @res =
  391. send:=>
  392. it "should call compile in the compile manager", (done)->
  393. @CompileController.compileAndDownloadPdf @req, @res
  394. @CompileManager.compile.calledWith(@project_id).should.equal true
  395. done()
  396. it "should proxy the res to the clsi with correct url", (done)->
  397. @CompileController.compileAndDownloadPdf @req, @res
  398. sinon.assert.calledWith @CompileController.proxyToClsi, @project_id, "/project/#{@project_id}/output/output.pdf", @req, @res
  399. @CompileController.proxyToClsi.calledWith(@project_id, "/project/#{@project_id}/output/output.pdf", @req, @res).should.equal true
  400. done()
  401. describe "wordCount", ->
  402. beforeEach ->
  403. @CompileManager.wordCount = sinon.stub().callsArgWith(3, null, {content:"body"})
  404. @req.params =
  405. Project_id: @project_id
  406. @res.send = sinon.stub()
  407. @res.contentType = sinon.stub()
  408. @CompileController.wordCount @req, @res, @next
  409. it "should proxy to the CLSI", ->
  410. @CompileManager.wordCount
  411. .calledWith(@project_id, @user_id, false)
  412. .should.equal true
  413. it "should return a 200 and body", ->
  414. @res.send
  415. .calledWith({content:"body"})
  416. .should.equal true