HttpControllerTests.coffee 16 KB

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