HttpControllerTests.coffee 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588
  1. sinon = require('sinon')
  2. chai = require('chai')
  3. should = chai.should()
  4. modulePath = "../../../../app/js/HttpController.js"
  5. SandboxedModule = require('sandboxed-module')
  6. Errors = require "../../../../app/js/Errors.js"
  7. describe "HttpController", ->
  8. beforeEach ->
  9. @HttpController = SandboxedModule.require modulePath, requires:
  10. "./DocumentManager": @DocumentManager = {}
  11. "./HistoryManager": @HistoryManager =
  12. flushProjectChangesAsync: sinon.stub()
  13. "./ProjectManager": @ProjectManager = {}
  14. "logger-sharelatex" : @logger = { log: sinon.stub() }
  15. "./Metrics": @Metrics = {}
  16. "./Errors" : Errors
  17. @Metrics.Timer = class Timer
  18. done: sinon.stub()
  19. @project_id = "project-id-123"
  20. @doc_id = "doc-id-123"
  21. @next = sinon.stub()
  22. @res =
  23. send: sinon.stub()
  24. json: sinon.stub()
  25. describe "getDoc", ->
  26. beforeEach ->
  27. @lines = ["one", "two", "three"]
  28. @ops = ["mock-op-1", "mock-op-2"]
  29. @version = 42
  30. @fromVersion = 42
  31. @ranges = { changes: "mock", comments: "mock" }
  32. @pathname = '/a/b/c'
  33. @req =
  34. params:
  35. project_id: @project_id
  36. doc_id: @doc_id
  37. describe "when the document exists and no recent ops are requested", ->
  38. beforeEach ->
  39. @DocumentManager.getDocAndRecentOpsWithLock = sinon.stub().callsArgWith(3, null, @lines, @version, [], @ranges, @pathname)
  40. @HttpController.getDoc(@req, @res, @next)
  41. it "should get the doc", ->
  42. @DocumentManager.getDocAndRecentOpsWithLock
  43. .calledWith(@project_id, @doc_id, -1)
  44. .should.equal true
  45. it "should return the doc as JSON", ->
  46. @res.json
  47. .calledWith({
  48. id: @doc_id
  49. lines: @lines
  50. version: @version
  51. ops: []
  52. ranges: @ranges
  53. pathname: @pathname
  54. })
  55. .should.equal true
  56. it "should log the request", ->
  57. @logger.log
  58. .calledWith(doc_id: @doc_id, project_id: @project_id, "getting doc via http")
  59. .should.equal true
  60. it "should time the request", ->
  61. @Metrics.Timer::done.called.should.equal true
  62. describe "when recent ops are requested", ->
  63. beforeEach ->
  64. @DocumentManager.getDocAndRecentOpsWithLock = sinon.stub().callsArgWith(3, null, @lines, @version, @ops, @ranges, @pathname)
  65. @req.query = fromVersion: "#{@fromVersion}"
  66. @HttpController.getDoc(@req, @res, @next)
  67. it "should get the doc", ->
  68. @DocumentManager.getDocAndRecentOpsWithLock
  69. .calledWith(@project_id, @doc_id, @fromVersion)
  70. .should.equal true
  71. it "should return the doc as JSON", ->
  72. @res.json
  73. .calledWith({
  74. id: @doc_id
  75. lines: @lines
  76. version: @version
  77. ops: @ops
  78. ranges: @ranges
  79. pathname: @pathname
  80. })
  81. .should.equal true
  82. it "should log the request", ->
  83. @logger.log
  84. .calledWith(doc_id: @doc_id, project_id: @project_id, "getting doc via http")
  85. .should.equal true
  86. it "should time the request", ->
  87. @Metrics.Timer::done.called.should.equal true
  88. describe "when the document does not exist", ->
  89. beforeEach ->
  90. @DocumentManager.getDocAndRecentOpsWithLock = sinon.stub().callsArgWith(3, null, null, null)
  91. @HttpController.getDoc(@req, @res, @next)
  92. it "should call next with NotFoundError", ->
  93. @next
  94. .calledWith(new Errors.NotFoundError("not found"))
  95. .should.equal true
  96. describe "when an errors occurs", ->
  97. beforeEach ->
  98. @DocumentManager.getDocAndRecentOpsWithLock = sinon.stub().callsArgWith(3, new Error("oops"), null, null)
  99. @HttpController.getDoc(@req, @res, @next)
  100. it "should call next with the error", ->
  101. @next
  102. .calledWith(new Error("oops"))
  103. .should.equal true
  104. describe "setDoc", ->
  105. beforeEach ->
  106. @lines = ["one", "two", "three"]
  107. @source = "dropbox"
  108. @user_id = "user-id-123"
  109. @req =
  110. headers: {}
  111. params:
  112. project_id: @project_id
  113. doc_id: @doc_id
  114. body:
  115. lines: @lines
  116. source: @source
  117. user_id: @user_id
  118. undoing: @undoing = true
  119. describe "successfully", ->
  120. beforeEach ->
  121. @DocumentManager.setDocWithLock = sinon.stub().callsArgWith(6)
  122. @HttpController.setDoc(@req, @res, @next)
  123. it "should set the doc", ->
  124. @DocumentManager.setDocWithLock
  125. .calledWith(@project_id, @doc_id, @lines, @source, @user_id, @undoing)
  126. .should.equal true
  127. it "should return a successful No Content response", ->
  128. @res.send
  129. .calledWith(204)
  130. .should.equal true
  131. it "should log the request", ->
  132. @logger.log
  133. .calledWith(doc_id: @doc_id, project_id: @project_id, lines: @lines, source: @source, user_id: @user_id, undoing: @undoing, "setting doc via http")
  134. .should.equal true
  135. it "should time the request", ->
  136. @Metrics.Timer::done.called.should.equal true
  137. describe "when an errors occurs", ->
  138. beforeEach ->
  139. @DocumentManager.setDocWithLock = sinon.stub().callsArgWith(6, new Error("oops"))
  140. @HttpController.setDoc(@req, @res, @next)
  141. it "should call next with the error", ->
  142. @next
  143. .calledWith(new Error("oops"))
  144. .should.equal true
  145. describe "when the payload is too large", ->
  146. beforeEach ->
  147. lines = []
  148. for _ in [0..200000]
  149. lines.push "test test test"
  150. @req.body.lines = lines
  151. @DocumentManager.setDocWithLock = sinon.stub().callsArgWith(6)
  152. @HttpController.setDoc(@req, @res, @next)
  153. it 'should send back a 406 response', ->
  154. @res.send.calledWith(406).should.equal true
  155. it 'should not call setDocWithLock', ->
  156. @DocumentManager.setDocWithLock.callCount.should.equal 0
  157. describe "flushProject", ->
  158. beforeEach ->
  159. @req =
  160. params:
  161. project_id: @project_id
  162. describe "successfully", ->
  163. beforeEach ->
  164. @ProjectManager.flushProjectWithLocks = sinon.stub().callsArgWith(1)
  165. @HttpController.flushProject(@req, @res, @next)
  166. it "should flush the project", ->
  167. @ProjectManager.flushProjectWithLocks
  168. .calledWith(@project_id)
  169. .should.equal true
  170. it "should return a successful No Content response", ->
  171. @res.send
  172. .calledWith(204)
  173. .should.equal true
  174. it "should log the request", ->
  175. @logger.log
  176. .calledWith(project_id: @project_id, "flushing project via http")
  177. .should.equal true
  178. it "should time the request", ->
  179. @Metrics.Timer::done.called.should.equal true
  180. describe "when an errors occurs", ->
  181. beforeEach ->
  182. @ProjectManager.flushProjectWithLocks = sinon.stub().callsArgWith(1, new Error("oops"))
  183. @HttpController.flushProject(@req, @res, @next)
  184. it "should call next with the error", ->
  185. @next
  186. .calledWith(new Error("oops"))
  187. .should.equal true
  188. describe "flushDocIfLoaded", ->
  189. beforeEach ->
  190. @lines = ["one", "two", "three"]
  191. @version = 42
  192. @req =
  193. params:
  194. project_id: @project_id
  195. doc_id: @doc_id
  196. describe "successfully", ->
  197. beforeEach ->
  198. @DocumentManager.flushDocIfLoadedWithLock = sinon.stub().callsArgWith(2)
  199. @HttpController.flushDocIfLoaded(@req, @res, @next)
  200. it "should flush the doc", ->
  201. @DocumentManager.flushDocIfLoadedWithLock
  202. .calledWith(@project_id, @doc_id)
  203. .should.equal true
  204. it "should return a successful No Content response", ->
  205. @res.send
  206. .calledWith(204)
  207. .should.equal true
  208. it "should log the request", ->
  209. @logger.log
  210. .calledWith(doc_id: @doc_id, project_id: @project_id, "flushing doc via http")
  211. .should.equal true
  212. it "should time the request", ->
  213. @Metrics.Timer::done.called.should.equal true
  214. describe "when an errors occurs", ->
  215. beforeEach ->
  216. @DocumentManager.flushDocIfLoadedWithLock = sinon.stub().callsArgWith(2, new Error("oops"))
  217. @HttpController.flushDocIfLoaded(@req, @res, @next)
  218. it "should call next with the error", ->
  219. @next
  220. .calledWith(new Error("oops"))
  221. .should.equal true
  222. describe "flushAndDeleteDoc", ->
  223. beforeEach ->
  224. @req =
  225. params:
  226. project_id: @project_id
  227. doc_id: @doc_id
  228. describe "successfully", ->
  229. beforeEach ->
  230. @DocumentManager.flushAndDeleteDocWithLock = sinon.stub().callsArgWith(2)
  231. @HttpController.flushAndDeleteDoc(@req, @res, @next)
  232. it "should flush and delete the doc", ->
  233. @DocumentManager.flushAndDeleteDocWithLock
  234. .calledWith(@project_id, @doc_id)
  235. .should.equal true
  236. it "should flush project history", ->
  237. @HistoryManager.flushProjectChangesAsync
  238. .calledWithExactly(@project_id)
  239. .should.equal true
  240. it "should return a successful No Content response", ->
  241. @res.send
  242. .calledWith(204)
  243. .should.equal true
  244. it "should log the request", ->
  245. @logger.log
  246. .calledWith(doc_id: @doc_id, project_id: @project_id, "deleting doc via http")
  247. .should.equal true
  248. it "should time the request", ->
  249. @Metrics.Timer::done.called.should.equal true
  250. describe "when an errors occurs", ->
  251. beforeEach ->
  252. @DocumentManager.flushAndDeleteDocWithLock = sinon.stub().callsArgWith(2, new Error("oops"))
  253. @HttpController.flushAndDeleteDoc(@req, @res, @next)
  254. it "should flush project history", ->
  255. @HistoryManager.flushProjectChangesAsync
  256. .calledWithExactly(@project_id)
  257. .should.equal true
  258. it "should call next with the error", ->
  259. @next
  260. .calledWith(new Error("oops"))
  261. .should.equal true
  262. describe "deleteProject", ->
  263. beforeEach ->
  264. @req =
  265. params:
  266. project_id: @project_id
  267. describe "successfully", ->
  268. beforeEach ->
  269. @ProjectManager.flushAndDeleteProjectWithLocks = sinon.stub().callsArgWith(1)
  270. @HttpController.deleteProject(@req, @res, @next)
  271. it "should delete the project", ->
  272. @ProjectManager.flushAndDeleteProjectWithLocks
  273. .calledWith(@project_id)
  274. .should.equal true
  275. it "should return a successful No Content response", ->
  276. @res.send
  277. .calledWith(204)
  278. .should.equal true
  279. it "should log the request", ->
  280. @logger.log
  281. .calledWith(project_id: @project_id, "deleting project via http")
  282. .should.equal true
  283. it "should time the request", ->
  284. @Metrics.Timer::done.called.should.equal true
  285. describe "when an errors occurs", ->
  286. beforeEach ->
  287. @ProjectManager.flushAndDeleteProjectWithLocks = sinon.stub().callsArgWith(1, new Error("oops"))
  288. @HttpController.deleteProject(@req, @res, @next)
  289. it "should call next with the error", ->
  290. @next
  291. .calledWith(new Error("oops"))
  292. .should.equal true
  293. describe "acceptChanges", ->
  294. beforeEach ->
  295. @req =
  296. params:
  297. project_id: @project_id
  298. doc_id: @doc_id
  299. change_id: @change_id = "mock-change-od-1"
  300. describe "successfully with a single change", ->
  301. beforeEach ->
  302. @DocumentManager.acceptChangesWithLock = sinon.stub().callsArgWith(3)
  303. @HttpController.acceptChanges(@req, @res, @next)
  304. it "should accept the change", ->
  305. @DocumentManager.acceptChangesWithLock
  306. .calledWith(@project_id, @doc_id, [ @change_id ])
  307. .should.equal true
  308. it "should return a successful No Content response", ->
  309. @res.send
  310. .calledWith(204)
  311. .should.equal true
  312. it "should log the request", ->
  313. @logger.log
  314. .calledWith({@project_id, @doc_id}, "accepting 1 changes via http")
  315. .should.equal true
  316. it "should time the request", ->
  317. @Metrics.Timer::done.called.should.equal true
  318. describe "succesfully with with multiple changes", ->
  319. beforeEach ->
  320. @change_ids = [ "mock-change-od-1", "mock-change-od-2", "mock-change-od-3", "mock-change-od-4" ]
  321. @req.body =
  322. change_ids: @change_ids
  323. @DocumentManager.acceptChangesWithLock = sinon.stub().callsArgWith(3)
  324. @HttpController.acceptChanges(@req, @res, @next)
  325. it "should accept the changes in the body payload", ->
  326. @DocumentManager.acceptChangesWithLock
  327. .calledWith(@project_id, @doc_id, @change_ids)
  328. .should.equal true
  329. it "should log the request with the correct number of changes", ->
  330. @logger.log
  331. .calledWith({@project_id, @doc_id}, "accepting #{ @change_ids.length } changes via http")
  332. .should.equal true
  333. describe "when an errors occurs", ->
  334. beforeEach ->
  335. @DocumentManager.acceptChangesWithLock = sinon.stub().callsArgWith(3, new Error("oops"))
  336. @HttpController.acceptChanges(@req, @res, @next)
  337. it "should call next with the error", ->
  338. @next
  339. .calledWith(new Error("oops"))
  340. .should.equal true
  341. describe "deleteComment", ->
  342. beforeEach ->
  343. @req =
  344. params:
  345. project_id: @project_id
  346. doc_id: @doc_id
  347. comment_id: @comment_id = "mock-comment-id"
  348. describe "successfully", ->
  349. beforeEach ->
  350. @DocumentManager.deleteCommentWithLock = sinon.stub().callsArgWith(3)
  351. @HttpController.deleteComment(@req, @res, @next)
  352. it "should accept the change", ->
  353. @DocumentManager.deleteCommentWithLock
  354. .calledWith(@project_id, @doc_id, @comment_id)
  355. .should.equal true
  356. it "should return a successful No Content response", ->
  357. @res.send
  358. .calledWith(204)
  359. .should.equal true
  360. it "should log the request", ->
  361. @logger.log
  362. .calledWith({@project_id, @doc_id, @comment_id}, "deleting comment via http")
  363. .should.equal true
  364. it "should time the request", ->
  365. @Metrics.Timer::done.called.should.equal true
  366. describe "when an errors occurs", ->
  367. beforeEach ->
  368. @DocumentManager.deleteCommentWithLock = sinon.stub().callsArgWith(3, new Error("oops"))
  369. @HttpController.deleteComment(@req, @res, @next)
  370. it "should call next with the error", ->
  371. @next
  372. .calledWith(new Error("oops"))
  373. .should.equal true
  374. describe "getProjectDocsAndFlushIfOld", ->
  375. beforeEach ->
  376. @state = "01234567890abcdef"
  377. @docs = [{_id: "1234", lines: "hello", v: 23}, {_id: "4567", lines: "world", v: 45}]
  378. @req =
  379. params:
  380. project_id: @project_id
  381. query:
  382. state: @state
  383. describe "successfully", ->
  384. beforeEach ->
  385. @ProjectManager.getProjectDocsAndFlushIfOld = sinon.stub().callsArgWith(3,null, @docs)
  386. @HttpController.getProjectDocsAndFlushIfOld(@req, @res, @next)
  387. it "should get docs from the project manager", ->
  388. @ProjectManager.getProjectDocsAndFlushIfOld
  389. .calledWith(@project_id, @state, {})
  390. .should.equal true
  391. it "should return a successful response", ->
  392. @res.send
  393. .calledWith(@docs)
  394. .should.equal true
  395. it "should log the request", ->
  396. @logger.log
  397. .calledWith({project_id: @project_id, exclude: []}, "getting docs via http")
  398. .should.equal true
  399. it "should log the response", ->
  400. @logger.log
  401. .calledWith({project_id: @project_id, result: ["1234:23", "4567:45"]}, "got docs via http")
  402. .should.equal true
  403. it "should time the request", ->
  404. @Metrics.Timer::done.called.should.equal true
  405. describe "when there is a conflict", ->
  406. beforeEach ->
  407. @ProjectManager.getProjectDocsAndFlushIfOld = sinon.stub().callsArgWith(3, new Errors.ProjectStateChangedError("project state changed"))
  408. @HttpController.getProjectDocsAndFlushIfOld(@req, @res, @next)
  409. it "should return an HTTP 409 Conflict response", ->
  410. @res.send
  411. .calledWith(409)
  412. .should.equal true
  413. describe "when an error occurs", ->
  414. beforeEach ->
  415. @ProjectManager.getProjectDocsAndFlushIfOld = sinon.stub().callsArgWith(3, new Error("oops"))
  416. @HttpController.getProjectDocsAndFlushIfOld(@req, @res, @next)
  417. it "should call next with the error", ->
  418. @next
  419. .calledWith(new Error("oops"))
  420. .should.equal true
  421. describe "updateProject", ->
  422. beforeEach ->
  423. @projectHistoryId = "history-id-123"
  424. @userId = "user-id-123"
  425. @docUpdates = sinon.stub()
  426. @fileUpdates = sinon.stub()
  427. @version = 1234567
  428. @req =
  429. body: {@projectHistoryId, @userId, @docUpdates, @fileUpdates, @version}
  430. params:
  431. project_id: @project_id
  432. describe "successfully", ->
  433. beforeEach ->
  434. @ProjectManager.updateProjectWithLocks = sinon.stub().callsArgWith(6)
  435. @HttpController.updateProject(@req, @res, @next)
  436. it "should accept the change", ->
  437. @ProjectManager.updateProjectWithLocks
  438. .calledWith(@project_id, @projectHistoryId, @userId, @docUpdates, @fileUpdates, @version)
  439. .should.equal true
  440. it "should return a successful No Content response", ->
  441. @res.send
  442. .calledWith(204)
  443. .should.equal true
  444. it "should time the request", ->
  445. @Metrics.Timer::done.called.should.equal true
  446. describe "when an errors occurs", ->
  447. beforeEach ->
  448. @ProjectManager.updateProjectWithLocks = sinon.stub().callsArgWith(6, new Error("oops"))
  449. @HttpController.updateProject(@req, @res, @next)
  450. it "should call next with the error", ->
  451. @next
  452. .calledWith(new Error("oops"))
  453. .should.equal true
  454. describe "resyncProjectHistory", ->
  455. beforeEach ->
  456. @projectHistoryId = "history-id-123"
  457. @docs = sinon.stub()
  458. @files = sinon.stub()
  459. @fileUpdates = sinon.stub()
  460. @req =
  461. body:
  462. {@projectHistoryId, @docs, @files}
  463. params:
  464. project_id: @project_id
  465. describe "successfully", ->
  466. beforeEach ->
  467. @HistoryManager.resyncProjectHistory = sinon.stub().callsArgWith(4)
  468. @HttpController.resyncProjectHistory(@req, @res, @next)
  469. it "should accept the change", ->
  470. @HistoryManager.resyncProjectHistory
  471. .calledWith(@project_id, @projectHistoryId, @docs, @files)
  472. .should.equal true
  473. it "should return a successful No Content response", ->
  474. @res.send
  475. .calledWith(204)
  476. .should.equal true
  477. describe "when an errors occurs", ->
  478. beforeEach ->
  479. @HistoryManager.resyncProjectHistory = sinon.stub().callsArgWith(4, new Error("oops"))
  480. @HttpController.resyncProjectHistory(@req, @res, @next)
  481. it "should call next with the error", ->
  482. @next
  483. .calledWith(new Error("oops"))
  484. .should.equal true