CompileControllerTests.js 29 KB

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