HttpControllerTests.js 16 KB

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