HttpControllerTests.js 27 KB

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