HttpControllerTests.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809
  1. /*
  2. * decaffeinate suggestions:
  3. * DS102: Remove unnecessary code created because of implicit returns
  4. * DS206: Consider reworking classes to avoid initClass
  5. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  6. */
  7. const sinon = require('sinon');
  8. const chai = require('chai');
  9. const should = chai.should();
  10. const modulePath = "../../../../app/js/HttpController.js";
  11. const SandboxedModule = require('sandboxed-module');
  12. const Errors = require("../../../../app/js/Errors.js");
  13. describe("HttpController", function() {
  14. beforeEach(function() {
  15. let Timer;
  16. this.HttpController = SandboxedModule.require(modulePath, { requires: {
  17. "./DocumentManager": (this.DocumentManager = {}),
  18. "./HistoryManager": (this.HistoryManager =
  19. {flushProjectChangesAsync: sinon.stub()}),
  20. "./ProjectManager": (this.ProjectManager = {}),
  21. "logger-sharelatex" : (this.logger = { log: sinon.stub() }),
  22. "./ProjectFlusher": {flushAllProjects() {}},
  23. "./DeleteQueueManager": (this.DeleteQueueManager = {}),
  24. "./Metrics": (this.Metrics = {}),
  25. "./Errors" : Errors
  26. }
  27. }
  28. );
  29. this.Metrics.Timer = (Timer = (function() {
  30. Timer = class Timer {
  31. static initClass() {
  32. this.prototype.done = sinon.stub();
  33. }
  34. };
  35. Timer.initClass();
  36. return Timer;
  37. })());
  38. this.project_id = "project-id-123";
  39. this.doc_id = "doc-id-123";
  40. this.next = sinon.stub();
  41. return this.res = {
  42. send: sinon.stub(),
  43. sendStatus: sinon.stub(),
  44. json: sinon.stub()
  45. };
  46. });
  47. describe("getDoc", function() {
  48. beforeEach(function() {
  49. this.lines = ["one", "two", "three"];
  50. this.ops = ["mock-op-1", "mock-op-2"];
  51. this.version = 42;
  52. this.fromVersion = 42;
  53. this.ranges = { changes: "mock", comments: "mock" };
  54. this.pathname = '/a/b/c';
  55. return this.req = {
  56. params: {
  57. project_id: this.project_id,
  58. doc_id: this.doc_id
  59. }
  60. };
  61. });
  62. describe("when the document exists and no recent ops are requested", function() {
  63. beforeEach(function() {
  64. this.DocumentManager.getDocAndRecentOpsWithLock = sinon.stub().callsArgWith(3, null, this.lines, this.version, [], this.ranges, this.pathname);
  65. return this.HttpController.getDoc(this.req, this.res, this.next);
  66. });
  67. it("should get the doc", function() {
  68. return this.DocumentManager.getDocAndRecentOpsWithLock
  69. .calledWith(this.project_id, this.doc_id, -1)
  70. .should.equal(true);
  71. });
  72. it("should return the doc as JSON", function() {
  73. return this.res.json
  74. .calledWith({
  75. id: this.doc_id,
  76. lines: this.lines,
  77. version: this.version,
  78. ops: [],
  79. ranges: this.ranges,
  80. pathname: this.pathname
  81. })
  82. .should.equal(true);
  83. });
  84. it("should log the request", function() {
  85. return this.logger.log
  86. .calledWith({doc_id: this.doc_id, project_id: this.project_id}, "getting doc via http")
  87. .should.equal(true);
  88. });
  89. return it("should time the request", function() {
  90. return this.Metrics.Timer.prototype.done.called.should.equal(true);
  91. });
  92. });
  93. describe("when recent ops are requested", function() {
  94. beforeEach(function() {
  95. this.DocumentManager.getDocAndRecentOpsWithLock = sinon.stub().callsArgWith(3, null, this.lines, this.version, this.ops, this.ranges, this.pathname);
  96. this.req.query = {fromVersion: `${this.fromVersion}`};
  97. return this.HttpController.getDoc(this.req, this.res, this.next);
  98. });
  99. it("should get the doc", function() {
  100. return this.DocumentManager.getDocAndRecentOpsWithLock
  101. .calledWith(this.project_id, this.doc_id, this.fromVersion)
  102. .should.equal(true);
  103. });
  104. it("should return the doc as JSON", function() {
  105. return this.res.json
  106. .calledWith({
  107. id: this.doc_id,
  108. lines: this.lines,
  109. version: this.version,
  110. ops: this.ops,
  111. ranges: this.ranges,
  112. pathname: this.pathname
  113. })
  114. .should.equal(true);
  115. });
  116. it("should log the request", function() {
  117. return this.logger.log
  118. .calledWith({doc_id: this.doc_id, project_id: this.project_id}, "getting doc via http")
  119. .should.equal(true);
  120. });
  121. return it("should time the request", function() {
  122. return this.Metrics.Timer.prototype.done.called.should.equal(true);
  123. });
  124. });
  125. describe("when the document does not exist", function() {
  126. beforeEach(function() {
  127. this.DocumentManager.getDocAndRecentOpsWithLock = sinon.stub().callsArgWith(3, null, null, null);
  128. return this.HttpController.getDoc(this.req, this.res, this.next);
  129. });
  130. return it("should call next with NotFoundError", function() {
  131. return this.next
  132. .calledWith(new Errors.NotFoundError("not found"))
  133. .should.equal(true);
  134. });
  135. });
  136. return describe("when an errors occurs", function() {
  137. beforeEach(function() {
  138. this.DocumentManager.getDocAndRecentOpsWithLock = sinon.stub().callsArgWith(3, new Error("oops"), null, null);
  139. return this.HttpController.getDoc(this.req, this.res, this.next);
  140. });
  141. return it("should call next with the error", function() {
  142. return this.next
  143. .calledWith(new Error("oops"))
  144. .should.equal(true);
  145. });
  146. });
  147. });
  148. describe("setDoc", function() {
  149. beforeEach(function() {
  150. this.lines = ["one", "two", "three"];
  151. this.source = "dropbox";
  152. this.user_id = "user-id-123";
  153. return this.req = {
  154. headers: {},
  155. params: {
  156. project_id: this.project_id,
  157. doc_id: this.doc_id
  158. },
  159. body: {
  160. lines: this.lines,
  161. source: this.source,
  162. user_id: this.user_id,
  163. undoing: (this.undoing = true)
  164. }
  165. };
  166. });
  167. describe("successfully", function() {
  168. beforeEach(function() {
  169. this.DocumentManager.setDocWithLock = sinon.stub().callsArgWith(6);
  170. return this.HttpController.setDoc(this.req, this.res, this.next);
  171. });
  172. it("should set the doc", function() {
  173. return this.DocumentManager.setDocWithLock
  174. .calledWith(this.project_id, this.doc_id, this.lines, this.source, this.user_id, this.undoing)
  175. .should.equal(true);
  176. });
  177. it("should return a successful No Content response", function() {
  178. return this.res.sendStatus
  179. .calledWith(204)
  180. .should.equal(true);
  181. });
  182. it("should log the request", function() {
  183. return this.logger.log
  184. .calledWith({doc_id: this.doc_id, project_id: this.project_id, lines: this.lines, source: this.source, user_id: this.user_id, undoing: this.undoing}, "setting doc via http")
  185. .should.equal(true);
  186. });
  187. return it("should time the request", function() {
  188. return this.Metrics.Timer.prototype.done.called.should.equal(true);
  189. });
  190. });
  191. describe("when an errors occurs", function() {
  192. beforeEach(function() {
  193. this.DocumentManager.setDocWithLock = sinon.stub().callsArgWith(6, new Error("oops"));
  194. return this.HttpController.setDoc(this.req, this.res, this.next);
  195. });
  196. return it("should call next with the error", function() {
  197. return this.next
  198. .calledWith(new Error("oops"))
  199. .should.equal(true);
  200. });
  201. });
  202. return describe("when the payload is too large", function() {
  203. beforeEach(function() {
  204. const lines = [];
  205. for (let _ = 0; _ <= 200000; _++) {
  206. lines.push("test test test");
  207. }
  208. this.req.body.lines = lines;
  209. this.DocumentManager.setDocWithLock = sinon.stub().callsArgWith(6);
  210. return this.HttpController.setDoc(this.req, this.res, this.next);
  211. });
  212. it('should send back a 406 response', function() {
  213. return this.res.sendStatus.calledWith(406).should.equal(true);
  214. });
  215. return it('should not call setDocWithLock', function() {
  216. return this.DocumentManager.setDocWithLock.callCount.should.equal(0);
  217. });
  218. });
  219. });
  220. describe("flushProject", function() {
  221. beforeEach(function() {
  222. return this.req = {
  223. params: {
  224. project_id: this.project_id
  225. }
  226. };
  227. });
  228. describe("successfully", function() {
  229. beforeEach(function() {
  230. this.ProjectManager.flushProjectWithLocks = sinon.stub().callsArgWith(1);
  231. return this.HttpController.flushProject(this.req, this.res, this.next);
  232. });
  233. it("should flush the project", function() {
  234. return this.ProjectManager.flushProjectWithLocks
  235. .calledWith(this.project_id)
  236. .should.equal(true);
  237. });
  238. it("should return a successful No Content response", function() {
  239. return this.res.sendStatus
  240. .calledWith(204)
  241. .should.equal(true);
  242. });
  243. it("should log the request", function() {
  244. return this.logger.log
  245. .calledWith({project_id: this.project_id}, "flushing project via http")
  246. .should.equal(true);
  247. });
  248. return it("should time the request", function() {
  249. return this.Metrics.Timer.prototype.done.called.should.equal(true);
  250. });
  251. });
  252. return describe("when an errors occurs", function() {
  253. beforeEach(function() {
  254. this.ProjectManager.flushProjectWithLocks = sinon.stub().callsArgWith(1, new Error("oops"));
  255. return this.HttpController.flushProject(this.req, this.res, this.next);
  256. });
  257. return it("should call next with the error", function() {
  258. return this.next
  259. .calledWith(new Error("oops"))
  260. .should.equal(true);
  261. });
  262. });
  263. });
  264. describe("flushDocIfLoaded", function() {
  265. beforeEach(function() {
  266. this.lines = ["one", "two", "three"];
  267. this.version = 42;
  268. return this.req = {
  269. params: {
  270. project_id: this.project_id,
  271. doc_id: this.doc_id
  272. }
  273. };
  274. });
  275. describe("successfully", function() {
  276. beforeEach(function() {
  277. this.DocumentManager.flushDocIfLoadedWithLock = sinon.stub().callsArgWith(2);
  278. return this.HttpController.flushDocIfLoaded(this.req, this.res, this.next);
  279. });
  280. it("should flush the doc", function() {
  281. return this.DocumentManager.flushDocIfLoadedWithLock
  282. .calledWith(this.project_id, this.doc_id)
  283. .should.equal(true);
  284. });
  285. it("should return a successful No Content response", function() {
  286. return this.res.sendStatus
  287. .calledWith(204)
  288. .should.equal(true);
  289. });
  290. it("should log the request", function() {
  291. return this.logger.log
  292. .calledWith({doc_id: this.doc_id, project_id: this.project_id}, "flushing doc via http")
  293. .should.equal(true);
  294. });
  295. return it("should time the request", function() {
  296. return this.Metrics.Timer.prototype.done.called.should.equal(true);
  297. });
  298. });
  299. return describe("when an errors occurs", function() {
  300. beforeEach(function() {
  301. this.DocumentManager.flushDocIfLoadedWithLock = sinon.stub().callsArgWith(2, new Error("oops"));
  302. return this.HttpController.flushDocIfLoaded(this.req, this.res, this.next);
  303. });
  304. return it("should call next with the error", function() {
  305. return this.next
  306. .calledWith(new Error("oops"))
  307. .should.equal(true);
  308. });
  309. });
  310. });
  311. describe("deleteDoc", function() {
  312. beforeEach(function() {
  313. return this.req = {
  314. params: {
  315. project_id: this.project_id,
  316. doc_id: this.doc_id
  317. },
  318. query: {}
  319. };});
  320. describe("successfully", function() {
  321. beforeEach(function() {
  322. this.DocumentManager.flushAndDeleteDocWithLock = sinon.stub().callsArgWith(3);
  323. return this.HttpController.deleteDoc(this.req, this.res, this.next);
  324. });
  325. it("should flush and delete the doc", function() {
  326. return this.DocumentManager.flushAndDeleteDocWithLock
  327. .calledWith(this.project_id, this.doc_id, { ignoreFlushErrors: false })
  328. .should.equal(true);
  329. });
  330. it("should flush project history", function() {
  331. return this.HistoryManager.flushProjectChangesAsync
  332. .calledWithExactly(this.project_id)
  333. .should.equal(true);
  334. });
  335. it("should return a successful No Content response", function() {
  336. return this.res.sendStatus
  337. .calledWith(204)
  338. .should.equal(true);
  339. });
  340. it("should log the request", function() {
  341. return this.logger.log
  342. .calledWith({doc_id: this.doc_id, project_id: this.project_id}, "deleting doc via http")
  343. .should.equal(true);
  344. });
  345. return it("should time the request", function() {
  346. return this.Metrics.Timer.prototype.done.called.should.equal(true);
  347. });
  348. });
  349. describe("ignoring errors", function() {
  350. beforeEach(function() {
  351. this.req.query.ignore_flush_errors = 'true';
  352. this.DocumentManager.flushAndDeleteDocWithLock = sinon.stub().yields();
  353. return this.HttpController.deleteDoc(this.req, this.res, this.next);
  354. });
  355. it("should delete the doc", function() {
  356. return this.DocumentManager.flushAndDeleteDocWithLock
  357. .calledWith(this.project_id, this.doc_id, { ignoreFlushErrors: true })
  358. .should.equal(true);
  359. });
  360. return it("should return a successful No Content response", function() {
  361. return this.res.sendStatus.calledWith(204).should.equal(true);
  362. });
  363. });
  364. return describe("when an errors occurs", function() {
  365. beforeEach(function() {
  366. this.DocumentManager.flushAndDeleteDocWithLock = sinon.stub().callsArgWith(3, new Error("oops"));
  367. return this.HttpController.deleteDoc(this.req, this.res, this.next);
  368. });
  369. it("should flush project history", function() {
  370. return this.HistoryManager.flushProjectChangesAsync
  371. .calledWithExactly(this.project_id)
  372. .should.equal(true);
  373. });
  374. return it("should call next with the error", function() {
  375. return this.next
  376. .calledWith(new Error("oops"))
  377. .should.equal(true);
  378. });
  379. });
  380. });
  381. describe("deleteProject", function() {
  382. beforeEach(function() {
  383. return this.req = {
  384. params: {
  385. project_id: this.project_id
  386. }
  387. };
  388. });
  389. describe("successfully", function() {
  390. beforeEach(function() {
  391. this.ProjectManager.flushAndDeleteProjectWithLocks = sinon.stub().callsArgWith(2);
  392. return this.HttpController.deleteProject(this.req, this.res, this.next);
  393. });
  394. it("should delete the project", function() {
  395. return this.ProjectManager.flushAndDeleteProjectWithLocks
  396. .calledWith(this.project_id)
  397. .should.equal(true);
  398. });
  399. it("should return a successful No Content response", function() {
  400. return this.res.sendStatus
  401. .calledWith(204)
  402. .should.equal(true);
  403. });
  404. it("should log the request", function() {
  405. return this.logger.log
  406. .calledWith({project_id: this.project_id}, "deleting project via http")
  407. .should.equal(true);
  408. });
  409. return it("should time the request", function() {
  410. return this.Metrics.Timer.prototype.done.called.should.equal(true);
  411. });
  412. });
  413. describe("with the background=true option from realtime", function() {
  414. beforeEach(function() {
  415. this.ProjectManager.queueFlushAndDeleteProject = sinon.stub().callsArgWith(1);
  416. this.req.query = {background:true, shutdown:true};
  417. return this.HttpController.deleteProject(this.req, this.res, this.next);
  418. });
  419. return it("should queue the flush and delete", function() {
  420. return this.ProjectManager.queueFlushAndDeleteProject
  421. .calledWith(this.project_id)
  422. .should.equal(true);
  423. });
  424. });
  425. return describe("when an errors occurs", function() {
  426. beforeEach(function() {
  427. this.ProjectManager.flushAndDeleteProjectWithLocks = sinon.stub().callsArgWith(2, new Error("oops"));
  428. return this.HttpController.deleteProject(this.req, this.res, this.next);
  429. });
  430. return it("should call next with the error", function() {
  431. return this.next
  432. .calledWith(new Error("oops"))
  433. .should.equal(true);
  434. });
  435. });
  436. });
  437. describe("acceptChanges", function() {
  438. beforeEach(function() {
  439. return this.req = {
  440. params: {
  441. project_id: this.project_id,
  442. doc_id: this.doc_id,
  443. change_id: (this.change_id = "mock-change-od-1")
  444. }
  445. };
  446. });
  447. describe("successfully with a single change", function() {
  448. beforeEach(function() {
  449. this.DocumentManager.acceptChangesWithLock = sinon.stub().callsArgWith(3);
  450. return this.HttpController.acceptChanges(this.req, this.res, this.next);
  451. });
  452. it("should accept the change", function() {
  453. return this.DocumentManager.acceptChangesWithLock
  454. .calledWith(this.project_id, this.doc_id, [ this.change_id ])
  455. .should.equal(true);
  456. });
  457. it("should return a successful No Content response", function() {
  458. return this.res.sendStatus
  459. .calledWith(204)
  460. .should.equal(true);
  461. });
  462. it("should log the request", function() {
  463. return this.logger.log
  464. .calledWith({project_id: this.project_id, doc_id: this.doc_id}, "accepting 1 changes via http")
  465. .should.equal(true);
  466. });
  467. return it("should time the request", function() {
  468. return this.Metrics.Timer.prototype.done.called.should.equal(true);
  469. });
  470. });
  471. describe("succesfully with with multiple changes", function() {
  472. beforeEach(function() {
  473. this.change_ids = [ "mock-change-od-1", "mock-change-od-2", "mock-change-od-3", "mock-change-od-4" ];
  474. this.req.body =
  475. {change_ids: this.change_ids};
  476. this.DocumentManager.acceptChangesWithLock = sinon.stub().callsArgWith(3);
  477. return this.HttpController.acceptChanges(this.req, this.res, this.next);
  478. });
  479. it("should accept the changes in the body payload", function() {
  480. return this.DocumentManager.acceptChangesWithLock
  481. .calledWith(this.project_id, this.doc_id, this.change_ids)
  482. .should.equal(true);
  483. });
  484. return it("should log the request with the correct number of changes", function() {
  485. return this.logger.log
  486. .calledWith({project_id: this.project_id, doc_id: this.doc_id}, `accepting ${ this.change_ids.length } changes via http`)
  487. .should.equal(true);
  488. });
  489. });
  490. return describe("when an errors occurs", function() {
  491. beforeEach(function() {
  492. this.DocumentManager.acceptChangesWithLock = sinon.stub().callsArgWith(3, new Error("oops"));
  493. return this.HttpController.acceptChanges(this.req, this.res, this.next);
  494. });
  495. return it("should call next with the error", function() {
  496. return this.next
  497. .calledWith(new Error("oops"))
  498. .should.equal(true);
  499. });
  500. });
  501. });
  502. describe("deleteComment", function() {
  503. beforeEach(function() {
  504. return this.req = {
  505. params: {
  506. project_id: this.project_id,
  507. doc_id: this.doc_id,
  508. comment_id: (this.comment_id = "mock-comment-id")
  509. }
  510. };
  511. });
  512. describe("successfully", function() {
  513. beforeEach(function() {
  514. this.DocumentManager.deleteCommentWithLock = sinon.stub().callsArgWith(3);
  515. return this.HttpController.deleteComment(this.req, this.res, this.next);
  516. });
  517. it("should accept the change", function() {
  518. return this.DocumentManager.deleteCommentWithLock
  519. .calledWith(this.project_id, this.doc_id, this.comment_id)
  520. .should.equal(true);
  521. });
  522. it("should return a successful No Content response", function() {
  523. return this.res.sendStatus
  524. .calledWith(204)
  525. .should.equal(true);
  526. });
  527. it("should log the request", function() {
  528. return this.logger.log
  529. .calledWith({project_id: this.project_id, doc_id: this.doc_id, comment_id: this.comment_id}, "deleting comment via http")
  530. .should.equal(true);
  531. });
  532. return it("should time the request", function() {
  533. return this.Metrics.Timer.prototype.done.called.should.equal(true);
  534. });
  535. });
  536. return describe("when an errors occurs", function() {
  537. beforeEach(function() {
  538. this.DocumentManager.deleteCommentWithLock = sinon.stub().callsArgWith(3, new Error("oops"));
  539. return this.HttpController.deleteComment(this.req, this.res, this.next);
  540. });
  541. return it("should call next with the error", function() {
  542. return this.next
  543. .calledWith(new Error("oops"))
  544. .should.equal(true);
  545. });
  546. });
  547. });
  548. describe("getProjectDocsAndFlushIfOld", function() {
  549. beforeEach(function() {
  550. this.state = "01234567890abcdef";
  551. this.docs = [{_id: "1234", lines: "hello", v: 23}, {_id: "4567", lines: "world", v: 45}];
  552. return this.req = {
  553. params: {
  554. project_id: this.project_id
  555. },
  556. query: {
  557. state: this.state
  558. }
  559. };
  560. });
  561. describe("successfully", function() {
  562. beforeEach(function() {
  563. this.ProjectManager.getProjectDocsAndFlushIfOld = sinon.stub().callsArgWith(3,null, this.docs);
  564. return this.HttpController.getProjectDocsAndFlushIfOld(this.req, this.res, this.next);
  565. });
  566. it("should get docs from the project manager", function() {
  567. return this.ProjectManager.getProjectDocsAndFlushIfOld
  568. .calledWith(this.project_id, this.state, {})
  569. .should.equal(true);
  570. });
  571. it("should return a successful response", function() {
  572. return this.res.send
  573. .calledWith(this.docs)
  574. .should.equal(true);
  575. });
  576. it("should log the request", function() {
  577. return this.logger.log
  578. .calledWith({project_id: this.project_id, exclude: []}, "getting docs via http")
  579. .should.equal(true);
  580. });
  581. it("should log the response", function() {
  582. return this.logger.log
  583. .calledWith({project_id: this.project_id, result: ["1234:23", "4567:45"]}, "got docs via http")
  584. .should.equal(true);
  585. });
  586. return it("should time the request", function() {
  587. return this.Metrics.Timer.prototype.done.called.should.equal(true);
  588. });
  589. });
  590. describe("when there is a conflict", function() {
  591. beforeEach(function() {
  592. this.ProjectManager.getProjectDocsAndFlushIfOld = sinon.stub().callsArgWith(3, new Errors.ProjectStateChangedError("project state changed"));
  593. return this.HttpController.getProjectDocsAndFlushIfOld(this.req, this.res, this.next);
  594. });
  595. return it("should return an HTTP 409 Conflict response", function() {
  596. return this.res.sendStatus
  597. .calledWith(409)
  598. .should.equal(true);
  599. });
  600. });
  601. return describe("when an error occurs", function() {
  602. beforeEach(function() {
  603. this.ProjectManager.getProjectDocsAndFlushIfOld = sinon.stub().callsArgWith(3, new Error("oops"));
  604. return this.HttpController.getProjectDocsAndFlushIfOld(this.req, this.res, this.next);
  605. });
  606. return it("should call next with the error", function() {
  607. return this.next
  608. .calledWith(new Error("oops"))
  609. .should.equal(true);
  610. });
  611. });
  612. });
  613. describe("updateProject", function() {
  614. beforeEach(function() {
  615. this.projectHistoryId = "history-id-123";
  616. this.userId = "user-id-123";
  617. this.docUpdates = sinon.stub();
  618. this.fileUpdates = sinon.stub();
  619. this.version = 1234567;
  620. return this.req = {
  621. body: {projectHistoryId: this.projectHistoryId, userId: this.userId, docUpdates: this.docUpdates, fileUpdates: this.fileUpdates, version: this.version},
  622. params: {
  623. project_id: this.project_id
  624. }
  625. };
  626. });
  627. describe("successfully", function() {
  628. beforeEach(function() {
  629. this.ProjectManager.updateProjectWithLocks = sinon.stub().callsArgWith(6);
  630. return this.HttpController.updateProject(this.req, this.res, this.next);
  631. });
  632. it("should accept the change", function() {
  633. return this.ProjectManager.updateProjectWithLocks
  634. .calledWith(this.project_id, this.projectHistoryId, this.userId, this.docUpdates, this.fileUpdates, this.version)
  635. .should.equal(true);
  636. });
  637. it("should return a successful No Content response", function() {
  638. return this.res.sendStatus
  639. .calledWith(204)
  640. .should.equal(true);
  641. });
  642. return it("should time the request", function() {
  643. return this.Metrics.Timer.prototype.done.called.should.equal(true);
  644. });
  645. });
  646. return describe("when an errors occurs", function() {
  647. beforeEach(function() {
  648. this.ProjectManager.updateProjectWithLocks = sinon.stub().callsArgWith(6, new Error("oops"));
  649. return this.HttpController.updateProject(this.req, this.res, this.next);
  650. });
  651. return it("should call next with the error", function() {
  652. return this.next
  653. .calledWith(new Error("oops"))
  654. .should.equal(true);
  655. });
  656. });
  657. });
  658. return describe("resyncProjectHistory", function() {
  659. beforeEach(function() {
  660. this.projectHistoryId = "history-id-123";
  661. this.docs = sinon.stub();
  662. this.files = sinon.stub();
  663. this.fileUpdates = sinon.stub();
  664. return this.req = {
  665. body:
  666. {projectHistoryId: this.projectHistoryId, docs: this.docs, files: this.files},
  667. params: {
  668. project_id: this.project_id
  669. }
  670. };
  671. });
  672. describe("successfully", function() {
  673. beforeEach(function() {
  674. this.HistoryManager.resyncProjectHistory = sinon.stub().callsArgWith(4);
  675. return this.HttpController.resyncProjectHistory(this.req, this.res, this.next);
  676. });
  677. it("should accept the change", function() {
  678. return this.HistoryManager.resyncProjectHistory
  679. .calledWith(this.project_id, this.projectHistoryId, this.docs, this.files)
  680. .should.equal(true);
  681. });
  682. return it("should return a successful No Content response", function() {
  683. return this.res.sendStatus
  684. .calledWith(204)
  685. .should.equal(true);
  686. });
  687. });
  688. return describe("when an errors occurs", function() {
  689. beforeEach(function() {
  690. this.HistoryManager.resyncProjectHistory = sinon.stub().callsArgWith(4, new Error("oops"));
  691. return this.HttpController.resyncProjectHistory(this.req, this.res, this.next);
  692. });
  693. return it("should call next with the error", function() {
  694. return this.next
  695. .calledWith(new Error("oops"))
  696. .should.equal(true);
  697. });
  698. });
  699. });
  700. });