HttpControllerTests.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513
  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 { assert, expect } = require('chai')
  15. const modulePath = require('path').join(
  16. __dirname,
  17. '../../../app/js/HttpController'
  18. )
  19. const { ObjectId } = require('mongodb')
  20. describe('HttpController', function () {
  21. beforeEach(function () {
  22. const settings = {
  23. max_doc_length: 2 * 1024 * 1024
  24. }
  25. this.HttpController = SandboxedModule.require(modulePath, {
  26. requires: {
  27. './DocManager': (this.DocManager = {}),
  28. './DocArchiveManager': (this.DocArchiveManager = {}),
  29. 'settings-sharelatex': settings,
  30. './HealthChecker': {}
  31. }
  32. })
  33. this.res = {
  34. send: sinon.stub(),
  35. sendStatus: sinon.stub(),
  36. json: sinon.stub(),
  37. setHeader: sinon.stub()
  38. }
  39. this.res.status = sinon.stub().returns(this.res)
  40. this.req = { query: {} }
  41. this.next = sinon.stub()
  42. this.project_id = 'mock-project-id'
  43. this.doc_id = 'mock-doc-id'
  44. this.doc = {
  45. _id: this.doc_id,
  46. lines: ['mock', 'lines', ' here', '', '', ' spaces '],
  47. version: 42,
  48. rev: 5
  49. }
  50. return (this.deletedDoc = {
  51. deleted: true,
  52. _id: this.doc_id,
  53. lines: ['mock', 'lines', ' here', '', '', ' spaces '],
  54. version: 42,
  55. rev: 5
  56. })
  57. })
  58. describe('getDoc', function () {
  59. describe('without deleted docs', function () {
  60. beforeEach(function () {
  61. this.req.params = {
  62. project_id: this.project_id,
  63. doc_id: this.doc_id
  64. }
  65. this.DocManager.getFullDoc = sinon
  66. .stub()
  67. .callsArgWith(2, null, this.doc)
  68. return this.HttpController.getDoc(this.req, this.res, this.next)
  69. })
  70. it('should get the document with the version (including deleted)', function () {
  71. return this.DocManager.getFullDoc
  72. .calledWith(this.project_id, this.doc_id)
  73. .should.equal(true)
  74. })
  75. return it('should return the doc as JSON', function () {
  76. return this.res.json
  77. .calledWith({
  78. _id: this.doc_id,
  79. lines: this.doc.lines,
  80. rev: this.doc.rev,
  81. version: this.doc.version
  82. })
  83. .should.equal(true)
  84. })
  85. })
  86. return describe('which is deleted', function () {
  87. beforeEach(function () {
  88. this.req.params = {
  89. project_id: this.project_id,
  90. doc_id: this.doc_id
  91. }
  92. return (this.DocManager.getFullDoc = sinon
  93. .stub()
  94. .callsArgWith(2, null, this.deletedDoc))
  95. })
  96. it('should get the doc from the doc manager', function () {
  97. this.HttpController.getDoc(this.req, this.res, this.next)
  98. return this.DocManager.getFullDoc
  99. .calledWith(this.project_id, this.doc_id)
  100. .should.equal(true)
  101. })
  102. it('should return 404 if the query string delete is not set ', function () {
  103. this.HttpController.getDoc(this.req, this.res, this.next)
  104. return this.res.sendStatus.calledWith(404).should.equal(true)
  105. })
  106. return it('should return the doc as JSON if include_deleted is set to true', function () {
  107. this.req.query.include_deleted = 'true'
  108. this.HttpController.getDoc(this.req, this.res, this.next)
  109. return this.res.json
  110. .calledWith({
  111. _id: this.doc_id,
  112. lines: this.doc.lines,
  113. rev: this.doc.rev,
  114. deleted: true,
  115. version: this.doc.version
  116. })
  117. .should.equal(true)
  118. })
  119. })
  120. })
  121. describe('getRawDoc', function () {
  122. beforeEach(function () {
  123. this.req.params = {
  124. project_id: this.project_id,
  125. doc_id: this.doc_id
  126. }
  127. this.DocManager.getDocLines = sinon.stub().callsArgWith(2, null, this.doc)
  128. return this.HttpController.getRawDoc(this.req, this.res, this.next)
  129. })
  130. it('should get the document without the version', function () {
  131. return this.DocManager.getDocLines
  132. .calledWith(this.project_id, this.doc_id)
  133. .should.equal(true)
  134. })
  135. it('should set the content type header', function () {
  136. return this.res.setHeader
  137. .calledWith('content-type', 'text/plain')
  138. .should.equal(true)
  139. })
  140. return it('should send the raw version of the doc', function () {
  141. return assert.deepEqual(
  142. this.res.send.args[0][0],
  143. `${this.doc.lines[0]}\n${this.doc.lines[1]}\n${this.doc.lines[2]}\n${this.doc.lines[3]}\n${this.doc.lines[4]}\n${this.doc.lines[5]}`
  144. )
  145. })
  146. })
  147. describe('getAllDocs', function () {
  148. describe('normally', function () {
  149. beforeEach(function () {
  150. this.req.params = { project_id: this.project_id }
  151. this.docs = [
  152. {
  153. _id: ObjectId(),
  154. lines: ['mock', 'lines', 'one'],
  155. rev: 2
  156. },
  157. {
  158. _id: ObjectId(),
  159. lines: ['mock', 'lines', 'two'],
  160. rev: 4
  161. }
  162. ]
  163. this.DocManager.getAllNonDeletedDocs = sinon
  164. .stub()
  165. .callsArgWith(2, null, this.docs)
  166. return this.HttpController.getAllDocs(this.req, this.res, this.next)
  167. })
  168. it('should get all the (non-deleted) docs', function () {
  169. return this.DocManager.getAllNonDeletedDocs
  170. .calledWith(this.project_id, { lines: true, rev: true })
  171. .should.equal(true)
  172. })
  173. return it('should return the doc as JSON', function () {
  174. return this.res.json
  175. .calledWith([
  176. {
  177. _id: this.docs[0]._id.toString(),
  178. lines: this.docs[0].lines,
  179. rev: this.docs[0].rev
  180. },
  181. {
  182. _id: this.docs[1]._id.toString(),
  183. lines: this.docs[1].lines,
  184. rev: this.docs[1].rev
  185. }
  186. ])
  187. .should.equal(true)
  188. })
  189. })
  190. return describe('with a null doc', function () {
  191. beforeEach(function () {
  192. this.req.params = { project_id: this.project_id }
  193. this.docs = [
  194. {
  195. _id: ObjectId(),
  196. lines: ['mock', 'lines', 'one'],
  197. rev: 2
  198. },
  199. null,
  200. {
  201. _id: ObjectId(),
  202. lines: ['mock', 'lines', 'two'],
  203. rev: 4
  204. }
  205. ]
  206. this.DocManager.getAllNonDeletedDocs = sinon
  207. .stub()
  208. .callsArgWith(2, null, this.docs)
  209. return this.HttpController.getAllDocs(this.req, this.res, this.next)
  210. })
  211. it('should return the non null docs as JSON', function () {
  212. return this.res.json
  213. .calledWith([
  214. {
  215. _id: this.docs[0]._id.toString(),
  216. lines: this.docs[0].lines,
  217. rev: this.docs[0].rev
  218. },
  219. {
  220. _id: this.docs[2]._id.toString(),
  221. lines: this.docs[2].lines,
  222. rev: this.docs[2].rev
  223. }
  224. ])
  225. .should.equal(true)
  226. })
  227. return it('should log out an error', function () {
  228. return this.logger.error
  229. .calledWith(
  230. {
  231. err: sinon.match.has('message', 'null doc'),
  232. project_id: this.project_id
  233. },
  234. 'encountered null doc'
  235. )
  236. .should.equal(true)
  237. })
  238. })
  239. })
  240. describe('getAllRanges', function () {
  241. return describe('normally', function () {
  242. beforeEach(function () {
  243. this.req.params = { project_id: this.project_id }
  244. this.docs = [
  245. {
  246. _id: ObjectId(),
  247. ranges: { mock_ranges: 'one' }
  248. },
  249. {
  250. _id: ObjectId(),
  251. ranges: { mock_ranges: 'two' }
  252. }
  253. ]
  254. this.DocManager.getAllNonDeletedDocs = sinon
  255. .stub()
  256. .callsArgWith(2, null, this.docs)
  257. return this.HttpController.getAllRanges(this.req, this.res, this.next)
  258. })
  259. it('should get all the (non-deleted) doc ranges', function () {
  260. return this.DocManager.getAllNonDeletedDocs
  261. .calledWith(this.project_id, { ranges: true })
  262. .should.equal(true)
  263. })
  264. return it('should return the doc as JSON', function () {
  265. return this.res.json
  266. .calledWith([
  267. {
  268. _id: this.docs[0]._id.toString(),
  269. ranges: this.docs[0].ranges
  270. },
  271. {
  272. _id: this.docs[1]._id.toString(),
  273. ranges: this.docs[1].ranges
  274. }
  275. ])
  276. .should.equal(true)
  277. })
  278. })
  279. })
  280. describe('updateDoc', function () {
  281. beforeEach(function () {
  282. return (this.req.params = {
  283. project_id: this.project_id,
  284. doc_id: this.doc_id
  285. })
  286. })
  287. describe('when the doc lines exist and were updated', function () {
  288. beforeEach(function () {
  289. this.req.body = {
  290. lines: (this.lines = ['hello', 'world']),
  291. version: (this.version = 42),
  292. ranges: (this.ranges = { changes: 'mock' })
  293. }
  294. this.DocManager.updateDoc = sinon
  295. .stub()
  296. .yields(null, true, (this.rev = 5))
  297. return this.HttpController.updateDoc(this.req, this.res, this.next)
  298. })
  299. it('should update the document', function () {
  300. return this.DocManager.updateDoc
  301. .calledWith(
  302. this.project_id,
  303. this.doc_id,
  304. this.lines,
  305. this.version,
  306. this.ranges
  307. )
  308. .should.equal(true)
  309. })
  310. return it('should return a modified status', function () {
  311. return this.res.json
  312. .calledWith({ modified: true, rev: this.rev })
  313. .should.equal(true)
  314. })
  315. })
  316. describe('when the doc lines exist and were not updated', function () {
  317. beforeEach(function () {
  318. this.req.body = {
  319. lines: (this.lines = ['hello', 'world']),
  320. version: (this.version = 42),
  321. ranges: {}
  322. }
  323. this.DocManager.updateDoc = sinon
  324. .stub()
  325. .yields(null, false, (this.rev = 5))
  326. return this.HttpController.updateDoc(this.req, this.res, this.next)
  327. })
  328. return it('should return a modified status', function () {
  329. return this.res.json
  330. .calledWith({ modified: false, rev: this.rev })
  331. .should.equal(true)
  332. })
  333. })
  334. describe('when the doc lines are not provided', function () {
  335. beforeEach(function () {
  336. this.req.body = { version: 42, ranges: {} }
  337. this.DocManager.updateDoc = sinon.stub().yields(null, false)
  338. return this.HttpController.updateDoc(this.req, this.res, this.next)
  339. })
  340. it('should not update the document', function () {
  341. return this.DocManager.updateDoc.called.should.equal(false)
  342. })
  343. return it('should return a 400 (bad request) response', function () {
  344. return this.res.sendStatus.calledWith(400).should.equal(true)
  345. })
  346. })
  347. describe('when the doc version are not provided', function () {
  348. beforeEach(function () {
  349. this.req.body = { version: 42, lines: ['hello world'] }
  350. this.DocManager.updateDoc = sinon.stub().yields(null, false)
  351. return this.HttpController.updateDoc(this.req, this.res, this.next)
  352. })
  353. it('should not update the document', function () {
  354. return this.DocManager.updateDoc.called.should.equal(false)
  355. })
  356. return it('should return a 400 (bad request) response', function () {
  357. return this.res.sendStatus.calledWith(400).should.equal(true)
  358. })
  359. })
  360. describe('when the doc ranges is not provided', function () {
  361. beforeEach(function () {
  362. this.req.body = { lines: ['foo'], version: 42 }
  363. this.DocManager.updateDoc = sinon.stub().yields(null, false)
  364. return this.HttpController.updateDoc(this.req, this.res, this.next)
  365. })
  366. it('should not update the document', function () {
  367. return this.DocManager.updateDoc.called.should.equal(false)
  368. })
  369. return it('should return a 400 (bad request) response', function () {
  370. return this.res.sendStatus.calledWith(400).should.equal(true)
  371. })
  372. })
  373. return describe('when the doc body is too large', function () {
  374. beforeEach(function () {
  375. this.req.body = {
  376. lines: (this.lines = Array(2049).fill('a'.repeat(1024))),
  377. version: (this.version = 42),
  378. ranges: (this.ranges = { changes: 'mock' })
  379. }
  380. return this.HttpController.updateDoc(this.req, this.res, this.next)
  381. })
  382. it('should return a 413 (too large) response', function () {
  383. return sinon.assert.calledWith(this.res.status, 413)
  384. })
  385. return it('should report that the document body is too large', function () {
  386. return sinon.assert.calledWith(this.res.send, 'document body too large')
  387. })
  388. })
  389. })
  390. describe('patchDoc', function () {
  391. beforeEach(function () {
  392. this.req.params = {
  393. project_id: this.project_id,
  394. doc_id: this.doc_id
  395. }
  396. this.req.body = { name: 'foo.tex' }
  397. this.DocManager.patchDoc = sinon.stub().yields(null)
  398. this.HttpController.patchDoc(this.req, this.res, this.next)
  399. })
  400. it('should delete the document', function () {
  401. expect(this.DocManager.patchDoc).to.have.been.calledWith(
  402. this.project_id,
  403. this.doc_id
  404. )
  405. })
  406. it('should return a 204 (No Content)', function () {
  407. expect(this.res.sendStatus).to.have.been.calledWith(204)
  408. })
  409. describe('with an invalid payload', function () {
  410. beforeEach(function () {
  411. this.req.body = { cannot: 'happen' }
  412. this.DocManager.patchDoc = sinon.stub().yields(null)
  413. this.HttpController.patchDoc(this.req, this.res, this.next)
  414. })
  415. it('should log a message', function () {
  416. expect(this.logger.fatal).to.have.been.calledWith(
  417. { field: 'cannot' },
  418. 'joi validation for pathDoc is broken'
  419. )
  420. })
  421. it('should not pass the invalid field along', function () {
  422. expect(this.DocManager.patchDoc).to.have.been.calledWith(
  423. this.project_id,
  424. this.doc_id,
  425. {}
  426. )
  427. })
  428. })
  429. })
  430. describe('archiveAllDocs', function () {
  431. beforeEach(function () {
  432. this.req.params = { project_id: this.project_id }
  433. this.DocArchiveManager.archiveAllDocs = sinon.stub().callsArg(1)
  434. return this.HttpController.archiveAllDocs(this.req, this.res, this.next)
  435. })
  436. it('should archive the project', function () {
  437. return this.DocArchiveManager.archiveAllDocs
  438. .calledWith(this.project_id)
  439. .should.equal(true)
  440. })
  441. return it('should return a 204 (No Content)', function () {
  442. return this.res.sendStatus.calledWith(204).should.equal(true)
  443. })
  444. })
  445. return describe('destroyAllDocs', function () {
  446. beforeEach(function () {
  447. this.req.params = { project_id: this.project_id }
  448. this.DocArchiveManager.destroyAllDocs = sinon.stub().callsArg(1)
  449. return this.HttpController.destroyAllDocs(this.req, this.res, this.next)
  450. })
  451. it('should destroy the docs', function () {
  452. return sinon.assert.calledWith(
  453. this.DocArchiveManager.destroyAllDocs,
  454. this.project_id
  455. )
  456. })
  457. return it('should return 204', function () {
  458. return sinon.assert.calledWith(this.res.sendStatus, 204)
  459. })
  460. })
  461. })