CompileControllerTests.js 29 KB

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