RequestParserTests.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462
  1. /* eslint-disable
  2. handle-callback-err,
  3. no-return-assign,
  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. require('chai').should()
  15. const { expect } = require('chai')
  16. const modulePath = require('path').join(
  17. __dirname,
  18. '../../../app/js/RequestParser'
  19. )
  20. const tk = require('timekeeper')
  21. describe('RequestParser', function() {
  22. beforeEach(function() {
  23. tk.freeze()
  24. this.callback = sinon.stub()
  25. this.validResource = {
  26. path: 'main.tex',
  27. date: '12:00 01/02/03',
  28. content: 'Hello world'
  29. }
  30. this.validRequest = {
  31. compile: {
  32. token: 'token-123',
  33. options: {
  34. imageName: 'basicImageName/here:2017-1',
  35. compiler: 'pdflatex',
  36. timeout: 42
  37. },
  38. resources: []
  39. }
  40. }
  41. return (this.RequestParser = SandboxedModule.require(modulePath, {
  42. requires: {
  43. 'settings-sharelatex': (this.settings = {})
  44. }
  45. }))
  46. })
  47. afterEach(function() {
  48. return tk.reset()
  49. })
  50. describe('without a top level object', function() {
  51. beforeEach(function() {
  52. return this.RequestParser.parse([], this.callback)
  53. })
  54. return it('should return an error', function() {
  55. return this.callback
  56. .calledWith('top level object should have a compile attribute')
  57. .should.equal(true)
  58. })
  59. })
  60. describe('without a compile attribute', function() {
  61. beforeEach(function() {
  62. return this.RequestParser.parse({}, this.callback)
  63. })
  64. return it('should return an error', function() {
  65. return this.callback
  66. .calledWith('top level object should have a compile attribute')
  67. .should.equal(true)
  68. })
  69. })
  70. describe('without a valid compiler', function() {
  71. beforeEach(function() {
  72. this.validRequest.compile.options.compiler = 'not-a-compiler'
  73. return this.RequestParser.parse(this.validRequest, this.callback)
  74. })
  75. return it('should return an error', function() {
  76. return this.callback
  77. .calledWith(
  78. 'compiler attribute should be one of: pdflatex, latex, xelatex, lualatex'
  79. )
  80. .should.equal(true)
  81. })
  82. })
  83. describe('without a compiler specified', function() {
  84. beforeEach(function() {
  85. delete this.validRequest.compile.options.compiler
  86. return this.RequestParser.parse(this.validRequest, (error, data) => {
  87. this.data = data
  88. })
  89. })
  90. return it('should set the compiler to pdflatex by default', function() {
  91. return this.data.compiler.should.equal('pdflatex')
  92. })
  93. })
  94. describe('with imageName set', function() {
  95. beforeEach(function() {
  96. return this.RequestParser.parse(this.validRequest, (error, data) => {
  97. this.data = data
  98. })
  99. })
  100. return it('should set the imageName', function() {
  101. return this.data.imageName.should.equal('basicImageName/here:2017-1')
  102. })
  103. })
  104. describe('when image restrictions are present', function() {
  105. beforeEach(function() {
  106. this.settings.clsi = { docker: {} }
  107. this.settings.clsi.docker.allowedImages = [
  108. 'repo/name:tag1',
  109. 'repo/name:tag2'
  110. ]
  111. })
  112. describe('with imageName set to something invalid', function() {
  113. beforeEach(function() {
  114. const request = this.validRequest
  115. request.compile.options.imageName = 'something/different:latest'
  116. this.RequestParser.parse(request, (error, data) => {
  117. this.error = error
  118. this.data = data
  119. })
  120. })
  121. it('should throw an error for imageName', function() {
  122. expect(String(this.error)).to.include(
  123. 'imageName attribute should be one of'
  124. )
  125. })
  126. })
  127. describe('with imageName set to something valid', function() {
  128. beforeEach(function() {
  129. const request = this.validRequest
  130. request.compile.options.imageName = 'repo/name:tag1'
  131. this.RequestParser.parse(request, (error, data) => {
  132. this.error = error
  133. this.data = data
  134. })
  135. })
  136. it('should set the imageName', function() {
  137. this.data.imageName.should.equal('repo/name:tag1')
  138. })
  139. })
  140. })
  141. describe('with flags set', function() {
  142. beforeEach(function() {
  143. this.validRequest.compile.options.flags = ['-file-line-error']
  144. return this.RequestParser.parse(this.validRequest, (error, data) => {
  145. this.data = data
  146. })
  147. })
  148. return it('should set the flags attribute', function() {
  149. return expect(this.data.flags).to.deep.equal(['-file-line-error'])
  150. })
  151. })
  152. describe('with flags not specified', function() {
  153. beforeEach(function() {
  154. return this.RequestParser.parse(this.validRequest, (error, data) => {
  155. this.data = data
  156. })
  157. })
  158. return it('it should have an empty flags list', function() {
  159. return expect(this.data.flags).to.deep.equal([])
  160. })
  161. })
  162. describe('without a timeout specified', function() {
  163. beforeEach(function() {
  164. delete this.validRequest.compile.options.timeout
  165. return this.RequestParser.parse(this.validRequest, (error, data) => {
  166. this.data = data
  167. })
  168. })
  169. return it('should set the timeout to MAX_TIMEOUT', function() {
  170. return this.data.timeout.should.equal(
  171. this.RequestParser.MAX_TIMEOUT * 1000
  172. )
  173. })
  174. })
  175. describe('with a timeout larger than the maximum', function() {
  176. beforeEach(function() {
  177. this.validRequest.compile.options.timeout =
  178. this.RequestParser.MAX_TIMEOUT + 1
  179. return this.RequestParser.parse(this.validRequest, (error, data) => {
  180. this.data = data
  181. })
  182. })
  183. return it('should set the timeout to MAX_TIMEOUT', function() {
  184. return this.data.timeout.should.equal(
  185. this.RequestParser.MAX_TIMEOUT * 1000
  186. )
  187. })
  188. })
  189. describe('with a timeout', function() {
  190. beforeEach(function() {
  191. return this.RequestParser.parse(this.validRequest, (error, data) => {
  192. this.data = data
  193. })
  194. })
  195. return it('should set the timeout (in milliseconds)', function() {
  196. return this.data.timeout.should.equal(
  197. this.validRequest.compile.options.timeout * 1000
  198. )
  199. })
  200. })
  201. describe('with a resource without a path', function() {
  202. beforeEach(function() {
  203. delete this.validResource.path
  204. this.validRequest.compile.resources.push(this.validResource)
  205. return this.RequestParser.parse(this.validRequest, this.callback)
  206. })
  207. return it('should return an error', function() {
  208. return this.callback
  209. .calledWith('all resources should have a path attribute')
  210. .should.equal(true)
  211. })
  212. })
  213. describe('with a resource with a path', function() {
  214. beforeEach(function() {
  215. this.validResource.path = this.path = 'test.tex'
  216. this.validRequest.compile.resources.push(this.validResource)
  217. this.RequestParser.parse(this.validRequest, this.callback)
  218. return (this.data = this.callback.args[0][1])
  219. })
  220. return it('should return the path in the parsed response', function() {
  221. return this.data.resources[0].path.should.equal(this.path)
  222. })
  223. })
  224. describe('with a resource with a malformed modified date', function() {
  225. beforeEach(function() {
  226. this.validResource.modified = 'not-a-date'
  227. this.validRequest.compile.resources.push(this.validResource)
  228. return this.RequestParser.parse(this.validRequest, this.callback)
  229. })
  230. return it('should return an error', function() {
  231. return this.callback
  232. .calledWith(
  233. 'resource modified date could not be understood: ' +
  234. this.validResource.modified
  235. )
  236. .should.equal(true)
  237. })
  238. })
  239. describe('with a resource with a valid date', function() {
  240. beforeEach(function() {
  241. this.date = '12:00 01/02/03'
  242. this.validResource.modified = this.date
  243. this.validRequest.compile.resources.push(this.validResource)
  244. this.RequestParser.parse(this.validRequest, this.callback)
  245. return (this.data = this.callback.args[0][1])
  246. })
  247. return it('should return the date as a Javascript Date object', function() {
  248. ;(this.data.resources[0].modified instanceof Date).should.equal(true)
  249. return this.data.resources[0].modified
  250. .getTime()
  251. .should.equal(Date.parse(this.date))
  252. })
  253. })
  254. describe('with a resource without either a content or URL attribute', function() {
  255. beforeEach(function() {
  256. delete this.validResource.url
  257. delete this.validResource.content
  258. this.validRequest.compile.resources.push(this.validResource)
  259. return this.RequestParser.parse(this.validRequest, this.callback)
  260. })
  261. return it('should return an error', function() {
  262. return this.callback
  263. .calledWith(
  264. 'all resources should have either a url or content attribute'
  265. )
  266. .should.equal(true)
  267. })
  268. })
  269. describe('with a resource where the content is not a string', function() {
  270. beforeEach(function() {
  271. this.validResource.content = []
  272. this.validRequest.compile.resources.push(this.validResource)
  273. return this.RequestParser.parse(this.validRequest, this.callback)
  274. })
  275. return it('should return an error', function() {
  276. return this.callback
  277. .calledWith('content attribute should be a string')
  278. .should.equal(true)
  279. })
  280. })
  281. describe('with a resource where the url is not a string', function() {
  282. beforeEach(function() {
  283. this.validResource.url = []
  284. this.validRequest.compile.resources.push(this.validResource)
  285. return this.RequestParser.parse(this.validRequest, this.callback)
  286. })
  287. return it('should return an error', function() {
  288. return this.callback
  289. .calledWith('url attribute should be a string')
  290. .should.equal(true)
  291. })
  292. })
  293. describe('with a resource with a url', function() {
  294. beforeEach(function() {
  295. this.validResource.url = this.url = 'www.example.com'
  296. this.validRequest.compile.resources.push(this.validResource)
  297. this.RequestParser.parse(this.validRequest, this.callback)
  298. return (this.data = this.callback.args[0][1])
  299. })
  300. return it('should return the url in the parsed response', function() {
  301. return this.data.resources[0].url.should.equal(this.url)
  302. })
  303. })
  304. describe('with a resource with a content attribute', function() {
  305. beforeEach(function() {
  306. this.validResource.content = this.content = 'Hello world'
  307. this.validRequest.compile.resources.push(this.validResource)
  308. this.RequestParser.parse(this.validRequest, this.callback)
  309. return (this.data = this.callback.args[0][1])
  310. })
  311. return it('should return the content in the parsed response', function() {
  312. return this.data.resources[0].content.should.equal(this.content)
  313. })
  314. })
  315. describe('without a root resource path', function() {
  316. beforeEach(function() {
  317. delete this.validRequest.compile.rootResourcePath
  318. this.RequestParser.parse(this.validRequest, this.callback)
  319. return (this.data = this.callback.args[0][1])
  320. })
  321. return it("should set the root resource path to 'main.tex' by default", function() {
  322. return this.data.rootResourcePath.should.equal('main.tex')
  323. })
  324. })
  325. describe('with a root resource path', function() {
  326. beforeEach(function() {
  327. this.validRequest.compile.rootResourcePath = this.path = 'test.tex'
  328. this.RequestParser.parse(this.validRequest, this.callback)
  329. return (this.data = this.callback.args[0][1])
  330. })
  331. return it('should return the root resource path in the parsed response', function() {
  332. return this.data.rootResourcePath.should.equal(this.path)
  333. })
  334. })
  335. describe('with a root resource path that is not a string', function() {
  336. beforeEach(function() {
  337. this.validRequest.compile.rootResourcePath = []
  338. return this.RequestParser.parse(this.validRequest, this.callback)
  339. })
  340. return it('should return an error', function() {
  341. return this.callback
  342. .calledWith('rootResourcePath attribute should be a string')
  343. .should.equal(true)
  344. })
  345. })
  346. describe('with a root resource path that needs escaping', function() {
  347. beforeEach(function() {
  348. this.badPath = '`rm -rf foo`.tex'
  349. this.goodPath = 'rm -rf foo.tex'
  350. this.validRequest.compile.rootResourcePath = this.badPath
  351. this.validRequest.compile.resources.push({
  352. path: this.badPath,
  353. date: '12:00 01/02/03',
  354. content: 'Hello world'
  355. })
  356. this.RequestParser.parse(this.validRequest, this.callback)
  357. return (this.data = this.callback.args[0][1])
  358. })
  359. it('should return the escaped resource', function() {
  360. return this.data.rootResourcePath.should.equal(this.goodPath)
  361. })
  362. return it('should also escape the resource path', function() {
  363. return this.data.resources[0].path.should.equal(this.goodPath)
  364. })
  365. })
  366. describe('with a root resource path that has a relative path', function() {
  367. beforeEach(function() {
  368. this.validRequest.compile.rootResourcePath = 'foo/../../bar.tex'
  369. this.RequestParser.parse(this.validRequest, this.callback)
  370. return (this.data = this.callback.args[0][1])
  371. })
  372. return it('should return an error', function() {
  373. return this.callback
  374. .calledWith('relative path in root resource')
  375. .should.equal(true)
  376. })
  377. })
  378. describe('with a root resource path that has unescaped + relative path', function() {
  379. beforeEach(function() {
  380. this.validRequest.compile.rootResourcePath = 'foo/#../bar.tex'
  381. this.RequestParser.parse(this.validRequest, this.callback)
  382. return (this.data = this.callback.args[0][1])
  383. })
  384. return it('should return an error', function() {
  385. return this.callback
  386. .calledWith('relative path in root resource')
  387. .should.equal(true)
  388. })
  389. })
  390. return describe('with an unknown syncType', function() {
  391. beforeEach(function() {
  392. this.validRequest.compile.options.syncType = 'unexpected'
  393. this.RequestParser.parse(this.validRequest, this.callback)
  394. return (this.data = this.callback.args[0][1])
  395. })
  396. return it('should return an error', function() {
  397. return this.callback
  398. .calledWith('syncType attribute should be one of: full, incremental')
  399. .should.equal(true)
  400. })
  401. })
  402. })