HttpControllerTests.js 13 KB

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