DocManagerTests.js 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029
  1. /* eslint-disable
  2. camelcase,
  3. handle-callback-err,
  4. no-dupe-keys,
  5. no-return-assign,
  6. no-unused-vars,
  7. */
  8. // TODO: This file was created by bulk-decaffeinate.
  9. // Fix any style issues and re-enable lint.
  10. /*
  11. * decaffeinate suggestions:
  12. * DS102: Remove unnecessary code created because of implicit returns
  13. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  14. */
  15. const SandboxedModule = require('sandboxed-module')
  16. const sinon = require('sinon')
  17. const { assert, expect } = require('chai')
  18. const modulePath = require('path').join(__dirname, '../../../app/js/DocManager')
  19. const { ObjectId } = require('mongodb')
  20. const Errors = require('../../../app/js/Errors')
  21. describe('DocManager', function () {
  22. beforeEach(function () {
  23. this.DocManager = SandboxedModule.require(modulePath, {
  24. requires: {
  25. './MongoManager': (this.MongoManager = {}),
  26. './DocArchiveManager': (this.DocArchiveManager = {}),
  27. './RangeManager': (this.RangeManager = {
  28. jsonRangesToMongo(r) {
  29. return r
  30. },
  31. shouldUpdateRanges: sinon.stub().returns(false)
  32. }),
  33. 'settings-sharelatex': (this.settings = { docstore: {} }),
  34. './Errors': Errors
  35. }
  36. })
  37. this.doc_id = ObjectId().toString()
  38. this.project_id = ObjectId().toString()
  39. this.another_project_id = ObjectId().toString()
  40. this.callback = sinon.stub()
  41. return (this.stubbedError = new Error('blew up'))
  42. })
  43. describe('checkDocExists', function () {
  44. beforeEach(function () {
  45. return (this.DocManager._getDoc = sinon.stub())
  46. })
  47. it('should call get doc with a quick filter', function (done) {
  48. this.DocManager._getDoc.callsArgWith(3, null, { _id: this.doc_id })
  49. return this.DocManager.checkDocExists(
  50. this.project_id,
  51. this.doc_id,
  52. (err, exist) => {
  53. exist.should.equal(true)
  54. this.DocManager._getDoc
  55. .calledWith(this.project_id, this.doc_id, { _id: 1, inS3: true })
  56. .should.equal(true)
  57. return done()
  58. }
  59. )
  60. })
  61. it('should return false when doc is not there', function (done) {
  62. this.DocManager._getDoc.callsArgWith(3, null)
  63. return this.DocManager.checkDocExists(
  64. this.project_id,
  65. this.doc_id,
  66. (err, exist) => {
  67. exist.should.equal(false)
  68. return done()
  69. }
  70. )
  71. })
  72. return it('should return error when get doc errors', function (done) {
  73. this.DocManager._getDoc.callsArgWith(3, 'error')
  74. return this.DocManager.checkDocExists(
  75. this.project_id,
  76. this.doc_id,
  77. (err, exist) => {
  78. err.should.equal('error')
  79. return done()
  80. }
  81. )
  82. })
  83. })
  84. describe('getFullDoc', function () {
  85. beforeEach(function () {
  86. this.DocManager._getDoc = sinon.stub()
  87. return (this.doc = {
  88. _id: this.doc_id,
  89. lines: ['2134']
  90. })
  91. })
  92. it('should call get doc with a quick filter', function (done) {
  93. this.DocManager._getDoc.callsArgWith(3, null, this.doc)
  94. return this.DocManager.getFullDoc(
  95. this.project_id,
  96. this.doc_id,
  97. (err, doc) => {
  98. doc.should.equal(this.doc)
  99. this.DocManager._getDoc
  100. .calledWith(this.project_id, this.doc_id, {
  101. lines: true,
  102. rev: true,
  103. deleted: true,
  104. version: true,
  105. ranges: true,
  106. inS3: true
  107. })
  108. .should.equal(true)
  109. return done()
  110. }
  111. )
  112. })
  113. return it('should return error when get doc errors', function (done) {
  114. this.DocManager._getDoc.callsArgWith(3, 'error')
  115. return this.DocManager.getFullDoc(
  116. this.project_id,
  117. this.doc_id,
  118. (err, exist) => {
  119. err.should.equal('error')
  120. return done()
  121. }
  122. )
  123. })
  124. })
  125. describe('getRawDoc', function () {
  126. beforeEach(function () {
  127. this.DocManager._getDoc = sinon.stub()
  128. return (this.doc = { lines: ['2134'] })
  129. })
  130. it('should call get doc with a quick filter', function (done) {
  131. this.DocManager._getDoc.callsArgWith(3, null, this.doc)
  132. return this.DocManager.getDocLines(
  133. this.project_id,
  134. this.doc_id,
  135. (err, doc) => {
  136. doc.should.equal(this.doc)
  137. this.DocManager._getDoc
  138. .calledWith(this.project_id, this.doc_id, {
  139. lines: true,
  140. inS3: true
  141. })
  142. .should.equal(true)
  143. return done()
  144. }
  145. )
  146. })
  147. return it('should return error when get doc errors', function (done) {
  148. this.DocManager._getDoc.callsArgWith(3, 'error')
  149. return this.DocManager.getDocLines(
  150. this.project_id,
  151. this.doc_id,
  152. (err, exist) => {
  153. err.should.equal('error')
  154. return done()
  155. }
  156. )
  157. })
  158. })
  159. describe('getDoc', function () {
  160. beforeEach(function () {
  161. this.project = { name: 'mock-project' }
  162. this.doc = {
  163. _id: this.doc_id,
  164. project_id: this.project_id,
  165. lines: ['mock-lines']
  166. }
  167. this.version = 42
  168. this.MongoManager.findDoc = sinon.stub()
  169. return (this.MongoManager.getDocVersion = sinon
  170. .stub()
  171. .yields(null, this.version))
  172. })
  173. describe('when using a filter', function () {
  174. beforeEach(function () {
  175. return this.MongoManager.findDoc.yields(null, this.doc)
  176. })
  177. it('should error if inS3 is not set to true', function (done) {
  178. return this.DocManager._getDoc(
  179. this.project_id,
  180. this.doc_id,
  181. { inS3: false },
  182. (err) => {
  183. expect(err).to.exist
  184. return done()
  185. }
  186. )
  187. })
  188. it('should always get inS3 even when no filter is passed', function (done) {
  189. return this.DocManager._getDoc(
  190. this.project_id,
  191. this.doc_id,
  192. undefined,
  193. (err) => {
  194. this.MongoManager.findDoc.called.should.equal(false)
  195. expect(err).to.exist
  196. return done()
  197. }
  198. )
  199. })
  200. return it('should not error if inS3 is set to true', function (done) {
  201. return this.DocManager._getDoc(
  202. this.project_id,
  203. this.doc_id,
  204. { inS3: true },
  205. (err) => {
  206. expect(err).to.not.exist
  207. return done()
  208. }
  209. )
  210. })
  211. })
  212. describe('when the doc is in the doc collection', function () {
  213. beforeEach(function () {
  214. this.MongoManager.findDoc.yields(null, this.doc)
  215. return this.DocManager._getDoc(
  216. this.project_id,
  217. this.doc_id,
  218. { version: true, inS3: true },
  219. this.callback
  220. )
  221. })
  222. it('should get the doc from the doc collection', function () {
  223. return this.MongoManager.findDoc
  224. .calledWith(this.project_id, this.doc_id)
  225. .should.equal(true)
  226. })
  227. it('should get the doc version from the docOps collection', function () {
  228. return this.MongoManager.getDocVersion
  229. .calledWith(this.doc_id)
  230. .should.equal(true)
  231. })
  232. return it('should return the callback with the doc with the version', function () {
  233. this.callback.called.should.equal(true)
  234. const doc = this.callback.args[0][1]
  235. doc.lines.should.equal(this.doc.lines)
  236. return doc.version.should.equal(this.version)
  237. })
  238. })
  239. describe('without the version filter', function () {
  240. beforeEach(function () {
  241. this.MongoManager.findDoc.yields(null, this.doc)
  242. return this.DocManager._getDoc(
  243. this.project_id,
  244. this.doc_id,
  245. { version: false, inS3: true },
  246. this.callback
  247. )
  248. })
  249. return it('should not get the doc version from the docOps collection', function () {
  250. return this.MongoManager.getDocVersion.called.should.equal(false)
  251. })
  252. })
  253. describe('when MongoManager.findDoc errors', function () {
  254. beforeEach(function () {
  255. this.MongoManager.findDoc.yields(this.stubbedError)
  256. return this.DocManager._getDoc(
  257. this.project_id,
  258. this.doc_id,
  259. { version: true, inS3: true },
  260. this.callback
  261. )
  262. })
  263. return it('should return the error', function () {
  264. return this.callback.calledWith(this.stubbedError).should.equal(true)
  265. })
  266. })
  267. describe('when the doc is archived', function () {
  268. beforeEach(function () {
  269. this.doc = {
  270. _id: this.doc_id,
  271. project_id: this.project_id,
  272. lines: ['mock-lines'],
  273. inS3: true
  274. }
  275. this.MongoManager.findDoc.yields(null, this.doc)
  276. this.DocArchiveManager.unarchiveDoc = (
  277. project_id,
  278. doc_id,
  279. callback
  280. ) => {
  281. this.doc.inS3 = false
  282. return callback()
  283. }
  284. sinon.spy(this.DocArchiveManager, 'unarchiveDoc')
  285. return this.DocManager._getDoc(
  286. this.project_id,
  287. this.doc_id,
  288. { version: true, inS3: true },
  289. this.callback
  290. )
  291. })
  292. it('should call the DocArchive to unarchive the doc', function () {
  293. return this.DocArchiveManager.unarchiveDoc
  294. .calledWith(this.project_id, this.doc_id)
  295. .should.equal(true)
  296. })
  297. it('should look up the doc twice', function () {
  298. return this.MongoManager.findDoc.calledTwice.should.equal(true)
  299. })
  300. return it('should return the doc', function () {
  301. return this.callback.calledWith(null, this.doc).should.equal(true)
  302. })
  303. })
  304. return describe('when the doc does not exist in the docs collection', function () {
  305. beforeEach(function () {
  306. this.MongoManager.findDoc = sinon.stub().yields(null, null)
  307. return this.DocManager._getDoc(
  308. this.project_id,
  309. this.doc_id,
  310. { version: true, inS3: true },
  311. this.callback
  312. )
  313. })
  314. return it('should return a NotFoundError', function () {
  315. return this.callback
  316. .calledWith(
  317. sinon.match.has(
  318. 'message',
  319. `No such doc: ${this.doc_id} in project ${this.project_id}`
  320. )
  321. )
  322. .should.equal(true)
  323. })
  324. })
  325. })
  326. describe('getAllNonDeletedDocs', function () {
  327. describe('when the project exists', function () {
  328. beforeEach(function () {
  329. this.docs = [
  330. {
  331. _id: this.doc_id,
  332. project_id: this.project_id,
  333. lines: ['mock-lines']
  334. }
  335. ]
  336. this.MongoManager.getProjectsDocs = sinon
  337. .stub()
  338. .callsArgWith(3, null, this.docs)
  339. this.DocArchiveManager.unArchiveAllDocs = sinon
  340. .stub()
  341. .callsArgWith(1, null, this.docs)
  342. this.filter = { lines: true }
  343. return this.DocManager.getAllNonDeletedDocs(
  344. this.project_id,
  345. this.filter,
  346. this.callback
  347. )
  348. })
  349. it('should get the project from the database', function () {
  350. return this.MongoManager.getProjectsDocs
  351. .calledWith(this.project_id, { include_deleted: false }, this.filter)
  352. .should.equal(true)
  353. })
  354. return it('should return the docs', function () {
  355. return this.callback.calledWith(null, this.docs).should.equal(true)
  356. })
  357. })
  358. return describe('when there are no docs for the project', function () {
  359. beforeEach(function () {
  360. this.MongoManager.getProjectsDocs = sinon
  361. .stub()
  362. .callsArgWith(3, null, null)
  363. this.DocArchiveManager.unArchiveAllDocs = sinon
  364. .stub()
  365. .callsArgWith(1, null)
  366. return this.DocManager.getAllNonDeletedDocs(
  367. this.project_id,
  368. this.filter,
  369. this.callback
  370. )
  371. })
  372. return it('should return a NotFoundError', function () {
  373. return this.callback
  374. .calledWith(
  375. sinon.match.has('message', `No docs for project ${this.project_id}`)
  376. )
  377. .should.equal(true)
  378. })
  379. })
  380. })
  381. describe('deleteDoc', function () {
  382. describe('when the doc exists', function () {
  383. beforeEach(function (done) {
  384. this.callback = sinon.stub().callsFake(done)
  385. this.lines = ['mock', 'doc', 'lines']
  386. this.rev = 77
  387. this.DocManager.checkDocExists = sinon
  388. .stub()
  389. .callsArgWith(2, null, true)
  390. this.MongoManager.markDocAsDeleted = sinon.stub().yields(null)
  391. this.DocArchiveManager.archiveDocById = sinon.stub().yields(null)
  392. return this.DocManager.deleteDoc(
  393. this.project_id,
  394. this.doc_id,
  395. this.callback
  396. )
  397. })
  398. it('should get the doc', function () {
  399. return this.DocManager.checkDocExists
  400. .calledWith(this.project_id, this.doc_id)
  401. .should.equal(true)
  402. })
  403. it('should mark doc as deleted', function () {
  404. return this.MongoManager.markDocAsDeleted
  405. .calledWith(this.project_id, this.doc_id)
  406. .should.equal(true)
  407. })
  408. it('should return the callback', function () {
  409. return this.callback.called.should.equal(true)
  410. })
  411. describe('background flush disabled', function () {
  412. beforeEach(function () {
  413. this.settings.docstore.archiveOnSoftDelete = false
  414. })
  415. it('should not flush the doc out of mongo', function () {
  416. this.DocArchiveManager.archiveDocById.should.not.have.been.called
  417. })
  418. })
  419. describe('background flush enabled', function () {
  420. beforeEach(function (done) {
  421. this.settings.docstore.archiveOnSoftDelete = true
  422. this.callback = sinon.stub().callsFake(done)
  423. this.DocManager.deleteDoc(this.project_id, this.doc_id, this.callback)
  424. })
  425. it('should not fail the delete process', function () {
  426. this.callback.should.have.been.calledWith(null)
  427. })
  428. it('should flush the doc out of mongo', function () {
  429. this.DocArchiveManager.archiveDocById.should.have.been.calledWith(
  430. this.project_id,
  431. this.doc_id
  432. )
  433. })
  434. describe('when the background flush fails', function () {
  435. beforeEach(function (done) {
  436. this.err = new Error('foo')
  437. this.DocManager.checkDocExists = sinon.stub().yields(null, true)
  438. this.MongoManager.markDocAsDeleted = sinon.stub().yields(null)
  439. this.DocArchiveManager.archiveDocById = sinon
  440. .stub()
  441. .yields(this.err)
  442. this.callback = sinon.stub().callsFake(done)
  443. this.DocManager.deleteDoc(
  444. this.project_id,
  445. this.doc_id,
  446. this.callback
  447. )
  448. })
  449. it('should log a warning', function () {
  450. this.logger.warn.should.have.been.calledWith(
  451. sinon.match({
  452. project_id: this.project_id,
  453. doc_id: this.doc_id,
  454. err: this.err
  455. }),
  456. 'archiving a single doc in the background failed'
  457. )
  458. })
  459. it('should not fail the delete process', function () {
  460. this.callback.should.have.been.calledWith(null)
  461. })
  462. })
  463. })
  464. })
  465. return describe('when the doc does not exist', function () {
  466. beforeEach(function () {
  467. this.DocManager.checkDocExists = sinon
  468. .stub()
  469. .callsArgWith(2, null, false)
  470. return this.DocManager.deleteDoc(
  471. this.project_id,
  472. this.doc_id,
  473. this.callback
  474. )
  475. })
  476. return it('should return a NotFoundError', function () {
  477. return this.callback
  478. .calledWith(
  479. sinon.match.has(
  480. 'message',
  481. `No such project/doc to delete: ${this.project_id}/${this.doc_id}`
  482. )
  483. )
  484. .should.equal(true)
  485. })
  486. })
  487. })
  488. describe('patchDoc', function () {
  489. describe('when the doc exists', function () {
  490. beforeEach(function () {
  491. this.lines = ['mock', 'doc', 'lines']
  492. this.rev = 77
  493. this.MongoManager.findDoc = sinon
  494. .stub()
  495. .yields(null, { _id: ObjectId(this.doc_id) })
  496. this.MongoManager.patchDoc = sinon.stub().yields(null)
  497. this.DocArchiveManager.archiveDocById = sinon.stub().yields(null)
  498. this.meta = {}
  499. })
  500. describe('standard path', function () {
  501. beforeEach(function (done) {
  502. this.callback = sinon.stub().callsFake(done)
  503. this.DocManager.patchDoc(
  504. this.project_id,
  505. this.doc_id,
  506. this.meta,
  507. this.callback
  508. )
  509. })
  510. it('should get the doc', function () {
  511. expect(this.MongoManager.findDoc).to.have.been.calledWith(
  512. this.project_id,
  513. this.doc_id
  514. )
  515. })
  516. it('should persist the meta', function () {
  517. expect(this.MongoManager.patchDoc).to.have.been.calledWith(
  518. this.project_id,
  519. this.doc_id,
  520. this.meta
  521. )
  522. })
  523. it('should return the callback', function () {
  524. expect(this.callback).to.have.been.calledWith(null)
  525. })
  526. })
  527. describe('background flush disabled and deleting a doc', function () {
  528. beforeEach(function (done) {
  529. this.settings.docstore.archiveOnSoftDelete = false
  530. this.meta.deleted = true
  531. this.callback = sinon.stub().callsFake(done)
  532. this.DocManager.patchDoc(
  533. this.project_id,
  534. this.doc_id,
  535. this.meta,
  536. this.callback
  537. )
  538. })
  539. it('should not flush the doc out of mongo', function () {
  540. expect(this.DocArchiveManager.archiveDocById).to.not.have.been.called
  541. })
  542. })
  543. describe('background flush enabled and not deleting a doc', function () {
  544. beforeEach(function (done) {
  545. this.settings.docstore.archiveOnSoftDelete = false
  546. this.meta.deleted = false
  547. this.callback = sinon.stub().callsFake(done)
  548. this.DocManager.patchDoc(
  549. this.project_id,
  550. this.doc_id,
  551. this.meta,
  552. this.callback
  553. )
  554. })
  555. it('should not flush the doc out of mongo', function () {
  556. expect(this.DocArchiveManager.archiveDocById).to.not.have.been.called
  557. })
  558. })
  559. describe('background flush enabled and deleting a doc', function () {
  560. beforeEach(function () {
  561. this.settings.docstore.archiveOnSoftDelete = true
  562. this.meta.deleted = true
  563. })
  564. describe('when the background flush succeeds', function () {
  565. beforeEach(function (done) {
  566. this.DocArchiveManager.archiveDocById = sinon.stub().yields(null)
  567. this.callback = sinon.stub().callsFake(done)
  568. this.DocManager.patchDoc(
  569. this.project_id,
  570. this.doc_id,
  571. this.meta,
  572. this.callback
  573. )
  574. })
  575. it('should not log a warning', function () {
  576. expect(this.logger.warn).to.not.have.been.called
  577. })
  578. it('should flush the doc out of mongo', function () {
  579. expect(
  580. this.DocArchiveManager.archiveDocById
  581. ).to.have.been.calledWith(this.project_id, this.doc_id)
  582. })
  583. })
  584. describe('when the background flush fails', function () {
  585. beforeEach(function (done) {
  586. this.err = new Error('foo')
  587. this.DocArchiveManager.archiveDocById = sinon
  588. .stub()
  589. .yields(this.err)
  590. this.callback = sinon.stub().callsFake(done)
  591. this.DocManager.patchDoc(
  592. this.project_id,
  593. this.doc_id,
  594. this.meta,
  595. this.callback
  596. )
  597. })
  598. it('should log a warning', function () {
  599. expect(this.logger.warn).to.have.been.calledWith(
  600. sinon.match({
  601. project_id: this.project_id,
  602. doc_id: this.doc_id,
  603. err: this.err
  604. }),
  605. 'archiving a single doc in the background failed'
  606. )
  607. })
  608. it('should not fail the delete process', function () {
  609. expect(this.callback).to.have.been.calledWith(null)
  610. })
  611. })
  612. })
  613. })
  614. describe('when the doc does not exist', function () {
  615. beforeEach(function () {
  616. this.MongoManager.findDoc = sinon.stub().yields(null)
  617. this.DocManager.patchDoc(
  618. this.project_id,
  619. this.doc_id,
  620. {},
  621. this.callback
  622. )
  623. })
  624. it('should return a NotFoundError', function () {
  625. expect(this.callback).to.have.been.calledWith(
  626. sinon.match.has(
  627. 'message',
  628. `No such project/doc to delete: ${this.project_id}/${this.doc_id}`
  629. )
  630. )
  631. })
  632. })
  633. })
  634. return describe('updateDoc', function () {
  635. beforeEach(function () {
  636. this.oldDocLines = ['old', 'doc', 'lines']
  637. this.newDocLines = ['new', 'doc', 'lines']
  638. this.originalRanges = {
  639. changes: [
  640. {
  641. id: ObjectId().toString(),
  642. op: { i: 'foo', p: 3 },
  643. meta: {
  644. user_id: ObjectId().toString(),
  645. ts: new Date().toString()
  646. }
  647. }
  648. ]
  649. }
  650. this.newRanges = {
  651. changes: [
  652. {
  653. id: ObjectId().toString(),
  654. op: { i: 'bar', p: 6 },
  655. meta: {
  656. user_id: ObjectId().toString(),
  657. ts: new Date().toString()
  658. }
  659. }
  660. ]
  661. }
  662. this.version = 42
  663. this.doc = {
  664. _id: this.doc_id,
  665. project_id: this.project_id,
  666. lines: this.oldDocLines,
  667. rev: (this.rev = 5),
  668. version: this.version,
  669. ranges: this.originalRanges
  670. }
  671. this.MongoManager.upsertIntoDocCollection = sinon.stub().callsArg(3)
  672. this.MongoManager.setDocVersion = sinon.stub().yields()
  673. return (this.DocManager._getDoc = sinon.stub())
  674. })
  675. describe('when only the doc lines have changed', function () {
  676. beforeEach(function () {
  677. this.DocManager._getDoc = sinon.stub().callsArgWith(3, null, this.doc)
  678. return this.DocManager.updateDoc(
  679. this.project_id,
  680. this.doc_id,
  681. this.newDocLines,
  682. this.version,
  683. this.originalRanges,
  684. this.callback
  685. )
  686. })
  687. it('should get the existing doc', function () {
  688. return this.DocManager._getDoc
  689. .calledWith(this.project_id, this.doc_id, {
  690. version: true,
  691. rev: true,
  692. lines: true,
  693. version: true,
  694. ranges: true,
  695. inS3: true
  696. })
  697. .should.equal(true)
  698. })
  699. it('should upsert the document to the doc collection', function () {
  700. return this.MongoManager.upsertIntoDocCollection
  701. .calledWith(this.project_id, this.doc_id, { lines: this.newDocLines })
  702. .should.equal(true)
  703. })
  704. it('should not update the version', function () {
  705. return this.MongoManager.setDocVersion.called.should.equal(false)
  706. })
  707. return it('should return the callback with the new rev', function () {
  708. return this.callback
  709. .calledWith(null, true, this.rev + 1)
  710. .should.equal(true)
  711. })
  712. })
  713. describe('when the doc ranges have changed', function () {
  714. beforeEach(function () {
  715. this.DocManager._getDoc = sinon.stub().callsArgWith(3, null, this.doc)
  716. this.RangeManager.shouldUpdateRanges.returns(true)
  717. return this.DocManager.updateDoc(
  718. this.project_id,
  719. this.doc_id,
  720. this.oldDocLines,
  721. this.version,
  722. this.newRanges,
  723. this.callback
  724. )
  725. })
  726. it('should upsert the ranges', function () {
  727. return this.MongoManager.upsertIntoDocCollection
  728. .calledWith(this.project_id, this.doc_id, { ranges: this.newRanges })
  729. .should.equal(true)
  730. })
  731. it('should not update the version', function () {
  732. return this.MongoManager.setDocVersion.called.should.equal(false)
  733. })
  734. return it('should return the callback with the new rev', function () {
  735. return this.callback
  736. .calledWith(null, true, this.rev + 1)
  737. .should.equal(true)
  738. })
  739. })
  740. describe('when only the version has changed', function () {
  741. beforeEach(function () {
  742. this.DocManager._getDoc = sinon.stub().callsArgWith(3, null, this.doc)
  743. return this.DocManager.updateDoc(
  744. this.project_id,
  745. this.doc_id,
  746. this.oldDocLines,
  747. this.version + 1,
  748. this.originalRanges,
  749. this.callback
  750. )
  751. })
  752. it('should not change the lines or ranges', function () {
  753. return this.MongoManager.upsertIntoDocCollection.called.should.equal(
  754. false
  755. )
  756. })
  757. it('should update the version', function () {
  758. return this.MongoManager.setDocVersion
  759. .calledWith(this.doc_id, this.version + 1)
  760. .should.equal(true)
  761. })
  762. return it('should return the callback with the old rev', function () {
  763. return this.callback.calledWith(null, true, this.rev).should.equal(true)
  764. })
  765. })
  766. describe('when the doc has not changed at all', function () {
  767. beforeEach(function () {
  768. this.DocManager._getDoc = sinon.stub().callsArgWith(3, null, this.doc)
  769. return this.DocManager.updateDoc(
  770. this.project_id,
  771. this.doc_id,
  772. this.oldDocLines,
  773. this.version,
  774. this.originalRanges,
  775. this.callback
  776. )
  777. })
  778. it('should not update the ranges or lines', function () {
  779. return this.MongoManager.upsertIntoDocCollection.called.should.equal(
  780. false
  781. )
  782. })
  783. it('should not update the version', function () {
  784. return this.MongoManager.setDocVersion.called.should.equal(false)
  785. })
  786. return it('should return the callback with the old rev and modified == false', function () {
  787. return this.callback
  788. .calledWith(null, false, this.rev)
  789. .should.equal(true)
  790. })
  791. })
  792. describe('when the version is null', function () {
  793. beforeEach(function () {
  794. return this.DocManager.updateDoc(
  795. this.project_id,
  796. this.doc_id,
  797. this.newDocLines,
  798. null,
  799. this.originalRanges,
  800. this.callback
  801. )
  802. })
  803. return it('should return an error', function () {
  804. return this.callback
  805. .calledWith(
  806. sinon.match.has('message', 'no lines, version or ranges provided')
  807. )
  808. .should.equal(true)
  809. })
  810. })
  811. describe('when the lines are null', function () {
  812. beforeEach(function () {
  813. return this.DocManager.updateDoc(
  814. this.project_id,
  815. this.doc_id,
  816. null,
  817. this.version,
  818. this.originalRanges,
  819. this.callback
  820. )
  821. })
  822. return it('should return an error', function () {
  823. return this.callback
  824. .calledWith(
  825. sinon.match.has('message', 'no lines, version or ranges provided')
  826. )
  827. .should.equal(true)
  828. })
  829. })
  830. describe('when the ranges are null', function () {
  831. beforeEach(function () {
  832. return this.DocManager.updateDoc(
  833. this.project_id,
  834. this.doc_id,
  835. this.newDocLines,
  836. this.version,
  837. null,
  838. this.callback
  839. )
  840. })
  841. return it('should return an error', function () {
  842. return this.callback
  843. .calledWith(
  844. sinon.match.has('message', 'no lines, version or ranges provided')
  845. )
  846. .should.equal(true)
  847. })
  848. })
  849. describe('when there is a generic error getting the doc', function () {
  850. beforeEach(function () {
  851. this.error = new Error('doc could not be found')
  852. this.DocManager._getDoc = sinon
  853. .stub()
  854. .callsArgWith(3, this.error, null, null)
  855. return this.DocManager.updateDoc(
  856. this.project_id,
  857. this.doc_id,
  858. this.newDocLines,
  859. this.version,
  860. this.originalRanges,
  861. this.callback
  862. )
  863. })
  864. it('should not upsert the document to the doc collection', function () {
  865. return this.MongoManager.upsertIntoDocCollection.called.should.equal(
  866. false
  867. )
  868. })
  869. return it('should return the callback with the error', function () {
  870. return this.callback.calledWith(this.error).should.equal(true)
  871. })
  872. })
  873. describe('when the doc lines have not changed', function () {
  874. beforeEach(function () {
  875. this.DocManager._getDoc = sinon.stub().callsArgWith(3, null, this.doc)
  876. return this.DocManager.updateDoc(
  877. this.project_id,
  878. this.doc_id,
  879. this.oldDocLines.slice(),
  880. this.version,
  881. this.originalRanges,
  882. this.callback
  883. )
  884. })
  885. it('should not update the doc', function () {
  886. return this.MongoManager.upsertIntoDocCollection.called.should.equal(
  887. false
  888. )
  889. })
  890. return it('should return the callback with the existing rev', function () {
  891. return this.callback
  892. .calledWith(null, false, this.rev)
  893. .should.equal(true)
  894. })
  895. })
  896. return describe('when the doc does not exist', function () {
  897. beforeEach(function () {
  898. this.DocManager._getDoc = sinon.stub().callsArgWith(3, null, null, null)
  899. return this.DocManager.updateDoc(
  900. this.project_id,
  901. this.doc_id,
  902. this.newDocLines,
  903. this.version,
  904. this.originalRanges,
  905. this.callback
  906. )
  907. })
  908. it('should upsert the document to the doc collection', function () {
  909. return this.MongoManager.upsertIntoDocCollection
  910. .calledWith(this.project_id, this.doc_id, {
  911. lines: this.newDocLines,
  912. ranges: this.originalRanges
  913. })
  914. .should.equal(true)
  915. })
  916. it('should set the version', function () {
  917. return this.MongoManager.setDocVersion
  918. .calledWith(this.doc_id, this.version)
  919. .should.equal(true)
  920. })
  921. return it('should return the callback with the new rev', function () {
  922. return this.callback.calledWith(null, true, 1).should.equal(true)
  923. })
  924. })
  925. })
  926. })