CompileControllerTests.js 29 KB

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