CompileControllerTests.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381
  1. /* eslint-disable
  2. no-return-assign,
  3. no-unused-vars,
  4. */
  5. // TODO: This file was created by bulk-decaffeinate.
  6. // Fix any style issues and re-enable lint.
  7. /*
  8. * decaffeinate suggestions:
  9. * DS102: Remove unnecessary code created because of implicit returns
  10. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  11. */
  12. const SandboxedModule = require('sandboxed-module')
  13. const sinon = require('sinon')
  14. const { expect } = require('chai')
  15. const modulePath = require('path').join(
  16. __dirname,
  17. '../../../app/js/CompileController'
  18. )
  19. const tk = require('timekeeper')
  20. describe('CompileController', function () {
  21. beforeEach(function () {
  22. this.CompileController = SandboxedModule.require(modulePath, {
  23. requires: {
  24. './CompileManager': (this.CompileManager = {}),
  25. './RequestParser': (this.RequestParser = {}),
  26. 'settings-sharelatex': (this.Settings = {
  27. apis: {
  28. clsi: {
  29. url: 'http://clsi.example.com'
  30. }
  31. }
  32. }),
  33. './ProjectPersistenceManager': (this.ProjectPersistenceManager = {})
  34. }
  35. })
  36. this.Settings.externalUrl = 'http://www.example.com'
  37. this.req = {}
  38. this.res = {}
  39. return (this.next = sinon.stub())
  40. })
  41. describe('compile', function () {
  42. beforeEach(function () {
  43. this.req.body = {
  44. compile: 'mock-body'
  45. }
  46. this.req.params = { project_id: (this.project_id = 'project-id-123') }
  47. this.request = {
  48. compile: 'mock-parsed-request'
  49. }
  50. this.request_with_project_id = {
  51. compile: this.request.compile,
  52. project_id: this.project_id
  53. }
  54. this.output_files = [
  55. {
  56. path: 'output.pdf',
  57. type: 'pdf',
  58. build: 1234
  59. },
  60. {
  61. path: 'output.log',
  62. type: 'log',
  63. build: 1234
  64. }
  65. ]
  66. this.RequestParser.parse = sinon
  67. .stub()
  68. .callsArgWith(1, null, this.request)
  69. this.ProjectPersistenceManager.markProjectAsJustAccessed = sinon
  70. .stub()
  71. .callsArg(1)
  72. this.res.status = sinon.stub().returnsThis()
  73. return (this.res.send = sinon.stub())
  74. })
  75. describe('successfully', function () {
  76. beforeEach(function () {
  77. this.CompileManager.doCompileWithLock = sinon
  78. .stub()
  79. .callsArgWith(1, null, this.output_files)
  80. return this.CompileController.compile(this.req, this.res)
  81. })
  82. it('should parse the request', function () {
  83. return this.RequestParser.parse
  84. .calledWith(this.req.body)
  85. .should.equal(true)
  86. })
  87. it('should run the compile for the specified project', function () {
  88. return this.CompileManager.doCompileWithLock
  89. .calledWith(this.request_with_project_id)
  90. .should.equal(true)
  91. })
  92. it('should mark the project as accessed', function () {
  93. return this.ProjectPersistenceManager.markProjectAsJustAccessed
  94. .calledWith(this.project_id)
  95. .should.equal(true)
  96. })
  97. return it('should return the JSON response', function () {
  98. this.res.status.calledWith(200).should.equal(true)
  99. return this.res.send
  100. .calledWith({
  101. compile: {
  102. status: 'success',
  103. error: null,
  104. outputFiles: this.output_files.map((file) => {
  105. return {
  106. url: `${this.Settings.apis.clsi.url}/project/${this.project_id}/build/${file.build}/output/${file.path}`,
  107. path: file.path,
  108. type: file.type,
  109. build: file.build
  110. }
  111. })
  112. }
  113. })
  114. .should.equal(true)
  115. })
  116. })
  117. describe('with user provided fake_output.pdf', function () {
  118. beforeEach(function () {
  119. this.output_files = [
  120. {
  121. path: 'fake_output.pdf',
  122. type: 'pdf',
  123. build: 1234
  124. },
  125. {
  126. path: 'output.log',
  127. type: 'log',
  128. build: 1234
  129. }
  130. ]
  131. this.CompileManager.doCompileWithLock = sinon
  132. .stub()
  133. .callsArgWith(1, null, this.output_files)
  134. this.CompileController.compile(this.req, this.res)
  135. })
  136. it('should return the JSON response with status failure', function () {
  137. this.res.status.calledWith(200).should.equal(true)
  138. this.res.send
  139. .calledWith({
  140. compile: {
  141. status: 'failure',
  142. error: null,
  143. outputFiles: this.output_files.map((file) => {
  144. return {
  145. url: `${this.Settings.apis.clsi.url}/project/${this.project_id}/build/${file.build}/output/${file.path}`,
  146. path: file.path,
  147. type: file.type,
  148. build: file.build
  149. }
  150. })
  151. }
  152. })
  153. .should.equal(true)
  154. })
  155. })
  156. describe('with an error', function () {
  157. beforeEach(function () {
  158. this.CompileManager.doCompileWithLock = sinon
  159. .stub()
  160. .callsArgWith(1, new Error((this.message = 'error message')), null)
  161. return this.CompileController.compile(this.req, this.res)
  162. })
  163. return it('should return the JSON response with the error', function () {
  164. this.res.status.calledWith(500).should.equal(true)
  165. return this.res.send
  166. .calledWith({
  167. compile: {
  168. status: 'error',
  169. error: this.message,
  170. outputFiles: []
  171. }
  172. })
  173. .should.equal(true)
  174. })
  175. })
  176. describe('when the request times out', function () {
  177. beforeEach(function () {
  178. this.error = new Error((this.message = 'container timed out'))
  179. this.error.timedout = true
  180. this.CompileManager.doCompileWithLock = sinon
  181. .stub()
  182. .callsArgWith(1, this.error, null)
  183. return this.CompileController.compile(this.req, this.res)
  184. })
  185. return it('should return the JSON response with the timeout status', function () {
  186. this.res.status.calledWith(200).should.equal(true)
  187. return this.res.send
  188. .calledWith({
  189. compile: {
  190. status: 'timedout',
  191. error: this.message,
  192. outputFiles: []
  193. }
  194. })
  195. .should.equal(true)
  196. })
  197. })
  198. return describe('when the request returns no output files', function () {
  199. beforeEach(function () {
  200. this.CompileManager.doCompileWithLock = sinon
  201. .stub()
  202. .callsArgWith(1, null, [])
  203. return this.CompileController.compile(this.req, this.res)
  204. })
  205. return it('should return the JSON response with the failure status', function () {
  206. this.res.status.calledWith(200).should.equal(true)
  207. return this.res.send
  208. .calledWith({
  209. compile: {
  210. error: null,
  211. status: 'failure',
  212. outputFiles: []
  213. }
  214. })
  215. .should.equal(true)
  216. })
  217. })
  218. })
  219. describe('syncFromCode', function () {
  220. beforeEach(function () {
  221. this.file = 'main.tex'
  222. this.line = 42
  223. this.column = 5
  224. this.project_id = 'mock-project-id'
  225. this.req.params = { project_id: this.project_id }
  226. this.req.query = {
  227. file: this.file,
  228. line: this.line.toString(),
  229. column: this.column.toString()
  230. }
  231. this.res.json = sinon.stub()
  232. this.CompileManager.syncFromCode = sinon
  233. .stub()
  234. .callsArgWith(5, null, (this.pdfPositions = ['mock-positions']))
  235. return this.CompileController.syncFromCode(this.req, this.res, this.next)
  236. })
  237. it('should find the corresponding location in the PDF', function () {
  238. return this.CompileManager.syncFromCode
  239. .calledWith(
  240. this.project_id,
  241. undefined,
  242. this.file,
  243. this.line,
  244. this.column
  245. )
  246. .should.equal(true)
  247. })
  248. return it('should return the positions', function () {
  249. return this.res.json
  250. .calledWith({
  251. pdf: this.pdfPositions
  252. })
  253. .should.equal(true)
  254. })
  255. })
  256. describe('syncFromPdf', function () {
  257. beforeEach(function () {
  258. this.page = 5
  259. this.h = 100.23
  260. this.v = 45.67
  261. this.project_id = 'mock-project-id'
  262. this.req.params = { project_id: this.project_id }
  263. this.req.query = {
  264. page: this.page.toString(),
  265. h: this.h.toString(),
  266. v: this.v.toString()
  267. }
  268. this.res.json = sinon.stub()
  269. this.CompileManager.syncFromPdf = sinon
  270. .stub()
  271. .callsArgWith(5, null, (this.codePositions = ['mock-positions']))
  272. return this.CompileController.syncFromPdf(this.req, this.res, this.next)
  273. })
  274. it('should find the corresponding location in the code', function () {
  275. return this.CompileManager.syncFromPdf
  276. .calledWith(this.project_id, undefined, this.page, this.h, this.v)
  277. .should.equal(true)
  278. })
  279. return it('should return the positions', function () {
  280. return this.res.json
  281. .calledWith({
  282. code: this.codePositions
  283. })
  284. .should.equal(true)
  285. })
  286. })
  287. return describe('wordcount', function () {
  288. beforeEach(function () {
  289. this.file = 'main.tex'
  290. this.project_id = 'mock-project-id'
  291. this.req.params = { project_id: this.project_id }
  292. this.req.query = {
  293. file: this.file,
  294. image: (this.image = 'example.com/image')
  295. }
  296. this.res.json = sinon.stub()
  297. this.CompileManager.wordcount = sinon
  298. .stub()
  299. .callsArgWith(4, null, (this.texcount = ['mock-texcount']))
  300. })
  301. it('should return the word count of a file', function () {
  302. this.CompileController.wordcount(this.req, this.res, this.next)
  303. return this.CompileManager.wordcount
  304. .calledWith(this.project_id, undefined, this.file, this.image)
  305. .should.equal(true)
  306. })
  307. it('should return the texcount info', function () {
  308. this.CompileController.wordcount(this.req, this.res, this.next)
  309. return this.res.json
  310. .calledWith({
  311. texcount: this.texcount
  312. })
  313. .should.equal(true)
  314. })
  315. describe('when allowedImages is set', function () {
  316. beforeEach(function () {
  317. this.Settings.clsi = { docker: {} }
  318. this.Settings.clsi.docker.allowedImages = [
  319. 'repo/image:tag1',
  320. 'repo/image:tag2'
  321. ]
  322. this.res.send = sinon.stub()
  323. this.res.status = sinon.stub().returns({ send: this.res.send })
  324. })
  325. describe('with an invalid image', function () {
  326. beforeEach(function () {
  327. this.req.query.image = 'something/evil:1337'
  328. this.CompileController.wordcount(this.req, this.res, this.next)
  329. })
  330. it('should return a 400', function () {
  331. expect(this.res.status.calledWith(400)).to.equal(true)
  332. })
  333. it('should not run the query', function () {
  334. expect(this.CompileManager.wordcount.called).to.equal(false)
  335. })
  336. })
  337. describe('with a valid image', function () {
  338. beforeEach(function () {
  339. this.req.query.image = 'repo/image:tag1'
  340. this.CompileController.wordcount(this.req, this.res, this.next)
  341. })
  342. it('should not return a 400', function () {
  343. expect(this.res.status.calledWith(400)).to.equal(false)
  344. })
  345. it('should run the query', function () {
  346. expect(this.CompileManager.wordcount.called).to.equal(true)
  347. })
  348. })
  349. })
  350. })
  351. })