HttpControllerTests.coffee 17 KB

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