CompileControllerTests.js 28 KB

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