HttpControllerTests.js 15 KB

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