CompileControllerTests.js 26 KB

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