CompileControllerTests.js 29 KB

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