HttpControllerTests.coffee 14 KB

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