CompileControllerTests.js 26 KB

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