CompileControllerTests.js 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914
  1. /* eslint-disable mocha/handle-done-callback */
  2. const sinon = require('sinon')
  3. const { expect } = require('chai')
  4. const modulePath = '../../../../app/src/Features/Compile/CompileController.js'
  5. const SandboxedModule = require('sandboxed-module')
  6. const MockRequest = require('../helpers/MockRequest')
  7. const MockResponse = require('../helpers/MockResponse')
  8. describe('CompileController', function () {
  9. beforeEach(function () {
  10. this.user_id = 'wat'
  11. this.user = {
  12. _id: this.user_id,
  13. email: 'user@example.com',
  14. features: {
  15. compileGroup: 'premium',
  16. compileTimeout: 100,
  17. },
  18. }
  19. this.CompileManager = { compile: sinon.stub() }
  20. this.ClsiManager = {}
  21. this.UserGetter = { getUser: sinon.stub() }
  22. this.RateLimiter = { addCount: sinon.stub() }
  23. this.settings = {
  24. apis: {
  25. clsi: {
  26. url: 'clsi.example.com',
  27. defaultBackendClass: 'e2',
  28. },
  29. clsi_priority: {
  30. url: 'clsi-priority.example.com',
  31. },
  32. },
  33. defaultFeatures: {
  34. compileGroup: 'standard',
  35. compileTimeout: 60,
  36. },
  37. }
  38. this.jar = { cookie: 'stuff' }
  39. this.ClsiCookieManager = {
  40. getCookieJar: sinon.stub().yields(null, this.jar),
  41. }
  42. this.SessionManager = {
  43. getLoggedInUser: sinon.stub().callsArgWith(1, null, this.user),
  44. getLoggedInUserId: sinon.stub().returns(this.user_id),
  45. getSessionUser: sinon.stub().returns(this.user),
  46. isUserLoggedIn: sinon.stub().returns(true),
  47. }
  48. this.CompileController = SandboxedModule.require(modulePath, {
  49. requires: {
  50. '@overleaf/settings': this.settings,
  51. request: (this.request = sinon.stub()),
  52. '../Project/ProjectGetter': (this.ProjectGetter = {}),
  53. '@overleaf/metrics': (this.Metrics = { inc: sinon.stub() }),
  54. './CompileManager': this.CompileManager,
  55. '../User/UserGetter': this.UserGetter,
  56. './ClsiManager': this.ClsiManager,
  57. '../Authentication/SessionManager': this.SessionManager,
  58. '../../infrastructure/RateLimiter': this.RateLimiter,
  59. './ClsiCookieManager': () => this.ClsiCookieManager,
  60. '../SplitTests/SplitTestHandler': {
  61. getAssignment: (this.getAssignment = sinon.stub().yields(null, {
  62. variant: 'default',
  63. })),
  64. },
  65. '../Analytics/AnalyticsManager': {
  66. recordEventForSession: sinon.stub(),
  67. },
  68. },
  69. })
  70. this.projectId = 'project-id'
  71. this.next = sinon.stub()
  72. this.req = new MockRequest()
  73. this.res = new MockResponse()
  74. })
  75. describe('compile', function () {
  76. beforeEach(function () {
  77. this.req.params = { Project_id: this.projectId }
  78. this.req.session = {}
  79. this.CompileManager.compile = sinon.stub().callsArgWith(
  80. 3,
  81. null,
  82. (this.status = 'success'),
  83. (this.outputFiles = [
  84. {
  85. path: 'output.pdf',
  86. url: `/project/${this.projectId}/user/${this.user_id}/build/id/output.pdf`,
  87. type: 'pdf',
  88. },
  89. ])
  90. )
  91. })
  92. describe('pdfDownloadDomain', function () {
  93. beforeEach(function () {
  94. this.settings.pdfDownloadDomain = 'https://compiles.overleaf.test'
  95. })
  96. describe('when clsi does not emit zone prefix', function () {
  97. beforeEach(function (done) {
  98. this.res.callback = done
  99. this.CompileController.compile(this.req, this.res, this.next)
  100. })
  101. it('should add domain verbatim', function () {
  102. this.res.statusCode.should.equal(200)
  103. this.res.body.should.equal(
  104. JSON.stringify({
  105. status: this.status,
  106. outputFiles: [
  107. {
  108. path: 'output.pdf',
  109. url: `/project/${this.projectId}/user/${this.user_id}/build/id/output.pdf`,
  110. type: 'pdf',
  111. },
  112. ],
  113. pdfDownloadDomain: 'https://compiles.overleaf.test',
  114. })
  115. )
  116. })
  117. })
  118. describe('when clsi emits a zone prefix', function () {
  119. beforeEach(function (done) {
  120. this.res.callback = done
  121. this.CompileManager.compile = sinon.stub().callsArgWith(
  122. 3,
  123. null,
  124. (this.status = 'success'),
  125. (this.outputFiles = [
  126. {
  127. path: 'output.pdf',
  128. url: `/project/${this.projectId}/user/${this.user_id}/build/id/output.pdf`,
  129. type: 'pdf',
  130. },
  131. ]),
  132. undefined, // clsiServerId
  133. undefined, // limits
  134. undefined, // validationProblems
  135. undefined, // stats
  136. undefined, // timings
  137. '/zone/b'
  138. )
  139. this.CompileController.compile(this.req, this.res, this.next)
  140. })
  141. it('should add the zone prefix', function () {
  142. this.res.statusCode.should.equal(200)
  143. this.res.body.should.equal(
  144. JSON.stringify({
  145. status: this.status,
  146. outputFiles: [
  147. {
  148. path: 'output.pdf',
  149. url: `/project/${this.projectId}/user/${this.user_id}/build/id/output.pdf`,
  150. type: 'pdf',
  151. },
  152. ],
  153. pdfDownloadDomain: 'https://compiles.overleaf.test/zone/b',
  154. })
  155. )
  156. })
  157. })
  158. })
  159. describe('when not an auto compile', function () {
  160. beforeEach(function (done) {
  161. this.res.callback = done
  162. this.CompileController.compile(this.req, this.res, this.next)
  163. })
  164. it('should look up the user id', function () {
  165. this.SessionManager.getLoggedInUserId
  166. .calledWith(this.req.session)
  167. .should.equal(true)
  168. })
  169. it('should do the compile without the auto compile flag', function () {
  170. this.CompileManager.compile.should.have.been.calledWith(
  171. this.projectId,
  172. this.user_id,
  173. {
  174. isAutoCompile: false,
  175. enablePdfCaching: false,
  176. fileLineErrors: false,
  177. stopOnFirstError: false,
  178. }
  179. )
  180. })
  181. it('should set the content-type of the response to application/json', function () {
  182. this.res.type.should.equal('application/json')
  183. })
  184. it('should send a successful response reporting the status and files', function () {
  185. this.res.statusCode.should.equal(200)
  186. this.res.body.should.equal(
  187. JSON.stringify({
  188. status: this.status,
  189. outputFiles: this.outputFiles,
  190. })
  191. )
  192. })
  193. })
  194. describe('when an auto compile', function () {
  195. beforeEach(function (done) {
  196. this.res.callback = done
  197. this.req.query = { auto_compile: 'true' }
  198. this.CompileController.compile(this.req, this.res, this.next)
  199. })
  200. it('should do the compile with the auto compile flag', function () {
  201. this.CompileManager.compile.should.have.been.calledWith(
  202. this.projectId,
  203. this.user_id,
  204. {
  205. isAutoCompile: true,
  206. enablePdfCaching: false,
  207. fileLineErrors: false,
  208. stopOnFirstError: false,
  209. }
  210. )
  211. })
  212. })
  213. describe('with the draft attribute', function () {
  214. beforeEach(function (done) {
  215. this.res.callback = done
  216. this.req.body = { draft: true }
  217. this.CompileController.compile(this.req, this.res, this.next)
  218. })
  219. it('should do the compile without the draft compile flag', function () {
  220. this.CompileManager.compile.should.have.been.calledWith(
  221. this.projectId,
  222. this.user_id,
  223. {
  224. isAutoCompile: false,
  225. enablePdfCaching: false,
  226. draft: true,
  227. fileLineErrors: false,
  228. stopOnFirstError: false,
  229. }
  230. )
  231. })
  232. })
  233. })
  234. describe('compileSubmission', function () {
  235. beforeEach(function () {
  236. this.submission_id = 'sub-1234'
  237. this.req.params = { submission_id: this.submission_id }
  238. this.req.body = {}
  239. this.ClsiManager.sendExternalRequest = sinon
  240. .stub()
  241. .callsArgWith(
  242. 3,
  243. null,
  244. (this.status = 'success'),
  245. (this.outputFiles = ['mock-output-files']),
  246. (this.clsiServerId = 'mock-server-id'),
  247. (this.validationProblems = null)
  248. )
  249. })
  250. it('should set the content-type of the response to application/json', function () {
  251. this.CompileController.compileSubmission(this.req, this.res, this.next)
  252. this.res.contentType.calledWith('application/json').should.equal(true)
  253. })
  254. it('should send a successful response reporting the status and files', function () {
  255. this.CompileController.compileSubmission(this.req, this.res, this.next)
  256. this.res.statusCode.should.equal(200)
  257. this.res.body.should.equal(
  258. JSON.stringify({
  259. status: this.status,
  260. outputFiles: this.outputFiles,
  261. clsiServerId: 'mock-server-id',
  262. validationProblems: null,
  263. })
  264. )
  265. })
  266. describe('with compileGroup and timeout', function () {
  267. beforeEach(function () {
  268. this.req.body = {
  269. compileGroup: 'special',
  270. timeout: 600,
  271. }
  272. this.CompileController.compileSubmission(this.req, this.res, this.next)
  273. })
  274. it('should use the supplied values', function () {
  275. this.ClsiManager.sendExternalRequest.should.have.been.calledWith(
  276. this.submission_id,
  277. { compileGroup: 'special', timeout: 600 },
  278. { compileGroup: 'special', compileBackendClass: 'e2', timeout: 600 }
  279. )
  280. })
  281. })
  282. describe('with other supported options but not compileGroup and timeout', function () {
  283. beforeEach(function () {
  284. this.req.body = {
  285. rootResourcePath: 'main.tex',
  286. compiler: 'lualatex',
  287. draft: true,
  288. check: 'validate',
  289. }
  290. this.CompileController.compileSubmission(this.req, this.res, this.next)
  291. })
  292. it('should use the other options but default values for compileGroup and timeout', function () {
  293. this.ClsiManager.sendExternalRequest.should.have.been.calledWith(
  294. this.submission_id,
  295. {
  296. rootResourcePath: 'main.tex',
  297. compiler: 'lualatex',
  298. draft: true,
  299. check: 'validate',
  300. },
  301. {
  302. rootResourcePath: 'main.tex',
  303. compiler: 'lualatex',
  304. draft: true,
  305. check: 'validate',
  306. compileGroup: 'standard',
  307. compileBackendClass: 'e2',
  308. timeout: 60,
  309. }
  310. )
  311. })
  312. })
  313. })
  314. describe('downloadPdf', function () {
  315. beforeEach(function () {
  316. this.req.params = { Project_id: this.projectId }
  317. this.req.query = { pdfng: true }
  318. this.project = { name: 'test namè' }
  319. this.ProjectGetter.getProject = sinon
  320. .stub()
  321. .callsArgWith(2, null, this.project)
  322. })
  323. describe('when downloading for embedding', function () {
  324. beforeEach(function () {
  325. this.CompileController.proxyToClsi = sinon.stub()
  326. this.RateLimiter.addCount.callsArgWith(1, null, true)
  327. this.CompileController.downloadPdf(this.req, this.res, this.next)
  328. })
  329. it('should look up the project', function () {
  330. this.ProjectGetter.getProject
  331. .calledWith(this.projectId, { name: 1 })
  332. .should.equal(true)
  333. })
  334. it('should set the content-type of the response to application/pdf', function () {
  335. this.res.contentType.calledWith('application/pdf').should.equal(true)
  336. })
  337. it('should set the content-disposition header with a safe version of the project name', function () {
  338. this.res.setContentDisposition
  339. .calledWith('', { filename: 'test_nam_.pdf' })
  340. .should.equal(true)
  341. })
  342. it('should increment the pdf-downloads metric', function () {
  343. this.Metrics.inc.calledWith('pdf-downloads').should.equal(true)
  344. })
  345. it('should proxy the PDF from the CLSI', function () {
  346. this.CompileController.proxyToClsi
  347. .calledWith(
  348. this.projectId,
  349. `/project/${this.projectId}/user/${this.user_id}/output/output.pdf`,
  350. this.req,
  351. this.res,
  352. this.next
  353. )
  354. .should.equal(true)
  355. })
  356. })
  357. describe('when the a build-id is provided', function () {
  358. beforeEach(function () {
  359. this.req.params.build_id = this.buildId = '1234-5678'
  360. this.CompileController.proxyToClsi = sinon.stub()
  361. this.RateLimiter.addCount.callsArgWith(1, null, true)
  362. this.CompileController.downloadPdf(this.req, this.res, this.next)
  363. })
  364. it('should proxy the PDF from the CLSI, with a build-id', function () {
  365. this.CompileController.proxyToClsi
  366. .calledWith(
  367. this.projectId,
  368. `/project/${this.projectId}/user/${this.user_id}/build/${this.buildId}/output/output.pdf`,
  369. this.req,
  370. this.res,
  371. this.next
  372. )
  373. .should.equal(true)
  374. })
  375. })
  376. describe('when the pdf is not going to be used in pdfjs viewer', function () {
  377. it('should check the rate limiter when pdfng is not set', function (done) {
  378. this.req.query = {}
  379. this.RateLimiter.addCount.callsArgWith(1, null, true)
  380. this.CompileController.proxyToClsi = (projectId, url) => {
  381. this.RateLimiter.addCount.args[0][0].throttle.should.equal(1000)
  382. done()
  383. }
  384. this.CompileController.downloadPdf(this.req, this.res)
  385. })
  386. it('should check the rate limiter when pdfng is false', function (done) {
  387. this.req.query = { pdfng: false }
  388. this.RateLimiter.addCount.callsArgWith(1, null, true)
  389. this.CompileController.proxyToClsi = (projectId, url) => {
  390. this.RateLimiter.addCount.args[0][0].throttle.should.equal(1000)
  391. done()
  392. }
  393. this.CompileController.downloadPdf(this.req, this.res)
  394. })
  395. })
  396. })
  397. describe('getFileFromClsiWithoutUser', function () {
  398. beforeEach(function () {
  399. this.submission_id = 'sub-1234'
  400. this.build_id = 123456
  401. this.file = 'project.pdf'
  402. this.req.params = {
  403. submission_id: this.submission_id,
  404. build_id: this.build_id,
  405. file: this.file,
  406. }
  407. this.req.body = {}
  408. this.expected_url = `/project/${this.submission_id}/build/${this.build_id}/output/${this.file}`
  409. this.CompileController.proxyToClsiWithLimits = sinon.stub()
  410. })
  411. describe('without limits specified', function () {
  412. beforeEach(function () {
  413. this.CompileController.getFileFromClsiWithoutUser(
  414. this.req,
  415. this.res,
  416. this.next
  417. )
  418. })
  419. it('should proxy to CLSI with correct URL and default limits', function () {
  420. this.CompileController.proxyToClsiWithLimits.should.have.been.calledWith(
  421. this.submission_id,
  422. this.expected_url,
  423. {
  424. compileGroup: 'standard',
  425. compileBackendClass: 'e2',
  426. }
  427. )
  428. })
  429. })
  430. describe('with limits specified', function () {
  431. beforeEach(function () {
  432. this.req.body = { compileTimeout: 600, compileGroup: 'special' }
  433. this.CompileController.getFileFromClsiWithoutUser(
  434. this.req,
  435. this.res,
  436. this.next
  437. )
  438. })
  439. it('should proxy to CLSI with correct URL and specified limits', function () {
  440. this.CompileController.proxyToClsiWithLimits.should.have.been.calledWith(
  441. this.submission_id,
  442. this.expected_url,
  443. {
  444. compileGroup: 'special',
  445. compileBackendClass: 'e2',
  446. }
  447. )
  448. })
  449. })
  450. })
  451. describe('proxySyncCode', function () {
  452. let file, line, column, imageName
  453. beforeEach(function (done) {
  454. this.req.params = { Project_id: this.projectId }
  455. file = 'main.tex'
  456. line = String(Date.now())
  457. column = String(Date.now() + 1)
  458. this.req.query = { file, line, column }
  459. imageName = 'foo/bar:tag-0'
  460. this.ProjectGetter.getProject = sinon.stub().yields(null, { imageName })
  461. this.next.callsFake(done)
  462. this.res.callback = done
  463. this.CompileController.proxyToClsi = sinon.stub().callsFake(() => done())
  464. this.CompileController.proxySyncCode(this.req, this.res, this.next)
  465. })
  466. it('should proxy the request with an imageName', function () {
  467. expect(this.CompileController.proxyToClsi).to.have.been.calledWith(
  468. this.projectId,
  469. {
  470. url: `/project/${this.projectId}/user/${this.user_id}/sync/code`,
  471. qs: { file, line, column, imageName },
  472. },
  473. this.req,
  474. this.res,
  475. this.next
  476. )
  477. })
  478. })
  479. describe('proxySyncPdf', function () {
  480. let page, h, v, imageName
  481. beforeEach(function (done) {
  482. this.req.params = { Project_id: this.projectId }
  483. page = String(Date.now())
  484. h = String(Math.random())
  485. v = String(Math.random())
  486. this.req.query = { page, h, v }
  487. imageName = 'foo/bar:tag-1'
  488. this.ProjectGetter.getProject = sinon.stub().yields(null, { imageName })
  489. this.next.callsFake(done)
  490. this.res.callback = done
  491. this.CompileController.proxyToClsi = sinon.stub().callsFake(() => done())
  492. this.CompileController.proxySyncPdf(this.req, this.res, this.next)
  493. })
  494. it('should proxy the request with an imageName', function () {
  495. expect(this.CompileController.proxyToClsi).to.have.been.calledWith(
  496. this.projectId,
  497. {
  498. url: `/project/${this.projectId}/user/${this.user_id}/sync/pdf`,
  499. qs: { page, h, v, imageName },
  500. },
  501. this.req,
  502. this.res,
  503. this.next
  504. )
  505. })
  506. })
  507. describe('proxyToClsi', function () {
  508. beforeEach(function () {
  509. this.request.returns(
  510. (this.proxy = {
  511. pipe: sinon.stub(),
  512. on: sinon.stub(),
  513. })
  514. )
  515. this.upstream = {
  516. statusCode: 204,
  517. headers: { mock: 'header' },
  518. }
  519. this.req.method = 'mock-method'
  520. this.req.headers = {
  521. Mock: 'Headers',
  522. Range: '123-456',
  523. 'If-Range': 'abcdef',
  524. 'If-Modified-Since': 'Mon, 15 Dec 2014 15:23:56 GMT',
  525. }
  526. })
  527. describe('old pdf viewer', function () {
  528. describe('user with standard priority', function () {
  529. beforeEach(function () {
  530. this.CompileManager.getProjectCompileLimits = sinon
  531. .stub()
  532. .callsArgWith(1, null, {
  533. compileGroup: 'standard',
  534. compileBackendClass: 'e2',
  535. })
  536. this.CompileController.proxyToClsi(
  537. this.projectId,
  538. (this.url = '/test'),
  539. this.req,
  540. this.res,
  541. this.next
  542. )
  543. })
  544. it('should open a request to the CLSI', function () {
  545. this.request
  546. .calledWith({
  547. jar: this.jar,
  548. qs: { compileGroup: 'standard', compileBackendClass: 'e2' },
  549. method: this.req.method,
  550. url: `${this.settings.apis.clsi.url}${this.url}`,
  551. timeout: 60 * 1000,
  552. })
  553. .should.equal(true)
  554. })
  555. it('should pass the request on to the client', function () {
  556. this.proxy.pipe.calledWith(this.res).should.equal(true)
  557. })
  558. it('should bind an error handle to the request proxy', function () {
  559. this.proxy.on.calledWith('error').should.equal(true)
  560. })
  561. })
  562. describe('user with priority compile', function () {
  563. beforeEach(function () {
  564. this.CompileManager.getProjectCompileLimits = sinon
  565. .stub()
  566. .callsArgWith(1, null, { compileGroup: 'priority' })
  567. this.CompileController.proxyToClsi(
  568. this.projectId,
  569. (this.url = '/test'),
  570. this.req,
  571. this.res,
  572. this.next
  573. )
  574. })
  575. })
  576. describe('user with standard priority via query string', function () {
  577. beforeEach(function () {
  578. this.req.query = { compileGroup: 'standard' }
  579. this.CompileManager.getProjectCompileLimits = sinon
  580. .stub()
  581. .callsArgWith(1, null, {
  582. compileGroup: 'standard',
  583. compileBackendClass: 'e2',
  584. })
  585. this.CompileController.proxyToClsi(
  586. this.projectId,
  587. (this.url = '/test'),
  588. this.req,
  589. this.res,
  590. this.next
  591. )
  592. })
  593. it('should open a request to the CLSI', function () {
  594. this.request
  595. .calledWith({
  596. jar: this.jar,
  597. qs: { compileGroup: 'standard', compileBackendClass: 'e2' },
  598. method: this.req.method,
  599. url: `${this.settings.apis.clsi.url}${this.url}`,
  600. timeout: 60 * 1000,
  601. })
  602. .should.equal(true)
  603. })
  604. it('should pass the request on to the client', function () {
  605. this.proxy.pipe.calledWith(this.res).should.equal(true)
  606. })
  607. it('should bind an error handle to the request proxy', function () {
  608. this.proxy.on.calledWith('error').should.equal(true)
  609. })
  610. })
  611. describe('user with non-existent priority via query string', function () {
  612. beforeEach(function () {
  613. this.req.query = { compileGroup: 'foobar' }
  614. this.CompileManager.getProjectCompileLimits = sinon
  615. .stub()
  616. .callsArgWith(1, null, {
  617. compileGroup: 'standard',
  618. compileBackendClass: 'e2',
  619. })
  620. this.CompileController.proxyToClsi(
  621. this.projectId,
  622. (this.url = '/test'),
  623. this.req,
  624. this.res,
  625. this.next
  626. )
  627. })
  628. it('should proxy to the standard url', function () {
  629. this.request
  630. .calledWith({
  631. jar: this.jar,
  632. qs: { compileGroup: 'standard', compileBackendClass: 'e2' },
  633. method: this.req.method,
  634. url: `${this.settings.apis.clsi.url}${this.url}`,
  635. timeout: 60 * 1000,
  636. })
  637. .should.equal(true)
  638. })
  639. })
  640. describe('user with build parameter via query string', function () {
  641. beforeEach(function () {
  642. this.CompileManager.getProjectCompileLimits = sinon
  643. .stub()
  644. .callsArgWith(1, null, {
  645. compileGroup: 'standard',
  646. compileBackendClass: 'e2',
  647. })
  648. this.req.query = { build: 1234 }
  649. this.CompileController.proxyToClsi(
  650. this.projectId,
  651. (this.url = '/test'),
  652. this.req,
  653. this.res,
  654. this.next
  655. )
  656. })
  657. it('should proxy to the standard url without the build parameter', function () {
  658. this.request
  659. .calledWith({
  660. jar: this.jar,
  661. qs: { compileGroup: 'standard', compileBackendClass: 'e2' },
  662. method: this.req.method,
  663. url: `${this.settings.apis.clsi.url}${this.url}`,
  664. timeout: 60 * 1000,
  665. })
  666. .should.equal(true)
  667. })
  668. })
  669. })
  670. describe('new pdf viewer', function () {
  671. beforeEach(function () {
  672. this.req.query = { pdfng: true }
  673. })
  674. describe('user with standard priority', function () {
  675. beforeEach(function () {
  676. this.CompileManager.getProjectCompileLimits = sinon
  677. .stub()
  678. .callsArgWith(1, null, {
  679. compileGroup: 'standard',
  680. compileBackendClass: 'e2',
  681. })
  682. this.CompileController.proxyToClsi(
  683. this.projectId,
  684. (this.url = '/test'),
  685. this.req,
  686. this.res,
  687. this.next
  688. )
  689. })
  690. it('should open a request to the CLSI', function () {
  691. this.request
  692. .calledWith({
  693. jar: this.jar,
  694. qs: { compileGroup: 'standard', compileBackendClass: 'e2' },
  695. method: this.req.method,
  696. url: `${this.settings.apis.clsi.url}${this.url}`,
  697. timeout: 60 * 1000,
  698. headers: {
  699. Range: '123-456',
  700. 'If-Range': 'abcdef',
  701. 'If-Modified-Since': 'Mon, 15 Dec 2014 15:23:56 GMT',
  702. },
  703. })
  704. .should.equal(true)
  705. })
  706. it('should pass the request on to the client', function () {
  707. this.proxy.pipe.calledWith(this.res).should.equal(true)
  708. })
  709. it('should bind an error handle to the request proxy', function () {
  710. this.proxy.on.calledWith('error').should.equal(true)
  711. })
  712. })
  713. describe('user with build parameter via query string', function () {
  714. beforeEach(function () {
  715. this.CompileManager.getProjectCompileLimits = sinon
  716. .stub()
  717. .callsArgWith(1, null, {
  718. compileGroup: 'standard',
  719. compileBackendClass: 'e2',
  720. })
  721. this.req.query = { build: 1234, pdfng: true }
  722. this.CompileController.proxyToClsi(
  723. this.projectId,
  724. (this.url = '/test'),
  725. this.req,
  726. this.res,
  727. this.next
  728. )
  729. })
  730. it('should proxy to the standard url with the build parameter', function () {
  731. this.request
  732. .calledWith({
  733. jar: this.jar,
  734. method: this.req.method,
  735. qs: {
  736. build: 1234,
  737. compileGroup: 'standard',
  738. compileBackendClass: 'e2',
  739. },
  740. url: `${this.settings.apis.clsi.url}${this.url}`,
  741. timeout: 60 * 1000,
  742. headers: {
  743. Range: '123-456',
  744. 'If-Range': 'abcdef',
  745. 'If-Modified-Since': 'Mon, 15 Dec 2014 15:23:56 GMT',
  746. },
  747. })
  748. .should.equal(true)
  749. })
  750. })
  751. })
  752. })
  753. describe('deleteAuxFiles', function () {
  754. beforeEach(function () {
  755. this.CompileManager.deleteAuxFiles = sinon.stub().yields()
  756. this.req.params = { Project_id: this.projectId }
  757. this.req.query = { clsiserverid: 'node-1' }
  758. this.res.sendStatus = sinon.stub()
  759. this.CompileController.deleteAuxFiles(this.req, this.res, this.next)
  760. })
  761. it('should proxy to the CLSI', function () {
  762. this.CompileManager.deleteAuxFiles
  763. .calledWith(this.projectId, this.user_id, 'node-1')
  764. .should.equal(true)
  765. })
  766. it('should return a 200', function () {
  767. this.res.sendStatus.calledWith(200).should.equal(true)
  768. })
  769. })
  770. describe('compileAndDownloadPdf', function () {
  771. beforeEach(function () {
  772. this.req = {
  773. params: {
  774. project_id: this.projectId,
  775. },
  776. }
  777. this.CompileManager.compile.callsArgWith(3)
  778. this.CompileController.proxyToClsi = sinon.stub()
  779. this.res = { send: () => {}, sendStatus: sinon.stub() }
  780. })
  781. it('should call compile in the compile manager', function (done) {
  782. this.CompileController.compileAndDownloadPdf(this.req, this.res)
  783. this.CompileManager.compile.calledWith(this.projectId).should.equal(true)
  784. done()
  785. })
  786. it('should proxy the res to the clsi with correct url', function (done) {
  787. this.CompileController.compileAndDownloadPdf(this.req, this.res)
  788. sinon.assert.calledWith(
  789. this.CompileController.proxyToClsi,
  790. this.projectId,
  791. `/project/${this.projectId}/output/output.pdf`,
  792. this.req,
  793. this.res
  794. )
  795. this.CompileController.proxyToClsi
  796. .calledWith(
  797. this.projectId,
  798. `/project/${this.projectId}/output/output.pdf`,
  799. this.req,
  800. this.res
  801. )
  802. .should.equal(true)
  803. done()
  804. })
  805. it('should not download anything on compilation failures', function () {
  806. this.CompileManager.compile.yields(new Error('failed'))
  807. this.CompileController.compileAndDownloadPdf(this.req, this.res)
  808. this.res.sendStatus.should.have.been.calledWith(500)
  809. this.CompileController.proxyToClsi.should.not.have.been.called
  810. })
  811. })
  812. describe('wordCount', function () {
  813. beforeEach(function () {
  814. this.CompileManager.wordCount = sinon
  815. .stub()
  816. .yields(null, { content: 'body' })
  817. this.req.params = { Project_id: this.projectId }
  818. this.req.query = { clsiserverid: 'node-42' }
  819. this.res.json = sinon.stub()
  820. this.res.contentType = sinon.stub()
  821. this.CompileController.wordCount(this.req, this.res, this.next)
  822. })
  823. it('should proxy to the CLSI', function () {
  824. this.CompileManager.wordCount
  825. .calledWith(this.projectId, this.user_id, false, 'node-42')
  826. .should.equal(true)
  827. })
  828. it('should return a 200 and body', function () {
  829. this.res.json.calledWith({ content: 'body' }).should.equal(true)
  830. })
  831. })
  832. })