HttpControllerTests.js 15 KB

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