RequestParserTests.js 14 KB

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