HttpControllerTests.js 27 KB

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