RequestParserTests.js 15 KB

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