CompileControllerTests.coffee 15 KB

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