DocManagerTests.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791
  1. const SandboxedModule = require('sandboxed-module')
  2. const sinon = require('sinon')
  3. const { expect } = require('chai')
  4. const modulePath = require('path').join(__dirname, '../../../app/js/DocManager')
  5. const { ObjectId } = require('mongodb')
  6. const Errors = require('../../../app/js/Errors')
  7. describe('DocManager', function () {
  8. beforeEach(function () {
  9. this.MongoManager = {
  10. promises: {
  11. findDoc: sinon.stub(),
  12. getDocVersion: sinon.stub(),
  13. getProjectsDocs: sinon.stub(),
  14. patchDoc: sinon.stub().resolves(),
  15. upsertIntoDocCollection: sinon.stub().resolves(),
  16. setDocVersion: sinon.stub().resolves(),
  17. },
  18. }
  19. this.DocArchiveManager = {
  20. promises: {
  21. unarchiveDoc: sinon.stub(),
  22. unArchiveAllDocs: sinon.stub(),
  23. archiveDoc: sinon.stub().resolves(),
  24. },
  25. }
  26. this.RangeManager = {
  27. jsonRangesToMongo(r) {
  28. return r
  29. },
  30. shouldUpdateRanges: sinon.stub().returns(false),
  31. }
  32. this.settings = { docstore: {} }
  33. this.DocManager = SandboxedModule.require(modulePath, {
  34. requires: {
  35. './MongoManager': this.MongoManager,
  36. './DocArchiveManager': this.DocArchiveManager,
  37. './RangeManager': this.RangeManager,
  38. '@overleaf/settings': this.settings,
  39. './Errors': Errors,
  40. },
  41. })
  42. this.doc_id = ObjectId().toString()
  43. this.project_id = ObjectId().toString()
  44. this.another_project_id = ObjectId().toString()
  45. this.stubbedError = new Error('blew up')
  46. })
  47. describe('getFullDoc', function () {
  48. beforeEach(function () {
  49. this.DocManager.promises._getDoc = sinon.stub()
  50. this.doc = {
  51. _id: this.doc_id,
  52. lines: ['2134'],
  53. }
  54. })
  55. it('should call get doc with a quick filter', async function () {
  56. this.DocManager.promises._getDoc.resolves(this.doc)
  57. const doc = await this.DocManager.promises.getFullDoc(
  58. this.project_id,
  59. this.doc_id
  60. )
  61. doc.should.equal(this.doc)
  62. this.DocManager.promises._getDoc
  63. .calledWith(this.project_id, this.doc_id, {
  64. lines: true,
  65. rev: true,
  66. deleted: true,
  67. version: true,
  68. ranges: true,
  69. inS3: true,
  70. })
  71. .should.equal(true)
  72. })
  73. it('should return error when get doc errors', async function () {
  74. this.DocManager.promises._getDoc.rejects(this.stubbedError)
  75. await expect(
  76. this.DocManager.promises.getFullDoc(this.project_id, this.doc_id)
  77. ).to.be.rejectedWith(this.stubbedError)
  78. })
  79. })
  80. describe('getRawDoc', function () {
  81. beforeEach(function () {
  82. this.DocManager.promises._getDoc = sinon.stub()
  83. this.doc = { lines: ['2134'] }
  84. })
  85. it('should call get doc with a quick filter', async function () {
  86. this.DocManager.promises._getDoc.resolves(this.doc)
  87. const doc = await this.DocManager.promises.getDocLines(
  88. this.project_id,
  89. this.doc_id
  90. )
  91. doc.should.equal(this.doc)
  92. this.DocManager.promises._getDoc
  93. .calledWith(this.project_id, this.doc_id, {
  94. lines: true,
  95. inS3: true,
  96. })
  97. .should.equal(true)
  98. })
  99. it('should return error when get doc errors', async function () {
  100. this.DocManager.promises._getDoc.rejects(this.stubbedError)
  101. await expect(
  102. this.DocManager.promises.getDocLines(this.project_id, this.doc_id)
  103. ).to.be.rejectedWith(this.stubbedError)
  104. })
  105. })
  106. describe('getDoc', function () {
  107. beforeEach(function () {
  108. this.project = { name: 'mock-project' }
  109. this.doc = {
  110. _id: this.doc_id,
  111. project_id: this.project_id,
  112. lines: ['mock-lines'],
  113. }
  114. this.version = 42
  115. this.MongoManager.promises.getDocVersion.resolves(this.version)
  116. })
  117. describe('when using a filter', function () {
  118. beforeEach(function () {
  119. this.MongoManager.promises.findDoc.resolves(this.doc)
  120. })
  121. it('should error if inS3 is not set to true', async function () {
  122. await expect(
  123. this.DocManager.promises._getDoc(this.project_id, this.doc_id, {
  124. inS3: false,
  125. })
  126. ).to.be.rejected
  127. })
  128. it('should always get inS3 even when no filter is passed', async function () {
  129. await expect(
  130. this.DocManager.promises._getDoc(this.project_id, this.doc_id)
  131. ).to.be.rejected
  132. this.MongoManager.promises.findDoc.called.should.equal(false)
  133. })
  134. it('should not error if inS3 is set to true', async function () {
  135. await this.DocManager.promises._getDoc(this.project_id, this.doc_id, {
  136. inS3: true,
  137. })
  138. })
  139. })
  140. describe('when the doc is in the doc collection', function () {
  141. beforeEach(async function () {
  142. this.MongoManager.promises.findDoc.resolves(this.doc)
  143. this.result = await this.DocManager.promises._getDoc(
  144. this.project_id,
  145. this.doc_id,
  146. { version: true, inS3: true }
  147. )
  148. })
  149. it('should get the doc from the doc collection', function () {
  150. this.MongoManager.promises.findDoc
  151. .calledWith(this.project_id, this.doc_id)
  152. .should.equal(true)
  153. })
  154. it('should get the doc version from the docOps collection', function () {
  155. this.MongoManager.promises.getDocVersion
  156. .calledWith(this.doc_id)
  157. .should.equal(true)
  158. })
  159. it('should return the callback with the doc with the version', function () {
  160. this.result.lines.should.equal(this.doc.lines)
  161. this.result.version.should.equal(this.version)
  162. })
  163. })
  164. describe('without the version filter', function () {
  165. beforeEach(async function () {
  166. this.MongoManager.promises.findDoc.resolves(this.doc)
  167. await this.DocManager.promises._getDoc(this.project_id, this.doc_id, {
  168. version: false,
  169. inS3: true,
  170. })
  171. })
  172. it('should not get the doc version from the docOps collection', function () {
  173. this.MongoManager.promises.getDocVersion.called.should.equal(false)
  174. })
  175. })
  176. describe('when MongoManager.findDoc errors', function () {
  177. it('should return the error', async function () {
  178. this.MongoManager.promises.findDoc.rejects(this.stubbedError)
  179. await expect(
  180. this.DocManager.promises._getDoc(this.project_id, this.doc_id, {
  181. version: true,
  182. inS3: true,
  183. })
  184. ).to.be.rejectedWith(this.stubbedError)
  185. })
  186. })
  187. describe('when the doc is archived', function () {
  188. beforeEach(async function () {
  189. this.doc = {
  190. _id: this.doc_id,
  191. project_id: this.project_id,
  192. inS3: true,
  193. }
  194. this.unarchivedDoc = {
  195. _id: this.doc_id,
  196. project_id: this.project_id,
  197. lines: ['mock-lines'],
  198. inS3: false,
  199. }
  200. this.MongoManager.promises.findDoc.resolves(this.doc)
  201. this.DocArchiveManager.promises.unarchiveDoc.callsFake(
  202. async (projectId, docId) => {
  203. this.MongoManager.promises.findDoc.resolves(this.unarchivedDoc)
  204. }
  205. )
  206. this.result = await this.DocManager.promises._getDoc(
  207. this.project_id,
  208. this.doc_id,
  209. {
  210. version: true,
  211. inS3: true,
  212. }
  213. )
  214. })
  215. it('should call the DocArchive to unarchive the doc', function () {
  216. this.DocArchiveManager.promises.unarchiveDoc
  217. .calledWith(this.project_id, this.doc_id)
  218. .should.equal(true)
  219. })
  220. it('should look up the doc twice', function () {
  221. this.MongoManager.promises.findDoc.calledTwice.should.equal(true)
  222. })
  223. it('should return the doc', function () {
  224. expect(this.result).to.deep.equal(this.unarchivedDoc)
  225. })
  226. })
  227. describe('when the doc does not exist in the docs collection', function () {
  228. it('should return a NotFoundError', async function () {
  229. this.MongoManager.promises.findDoc.resolves(null)
  230. await expect(
  231. this.DocManager.promises._getDoc(this.project_id, this.doc_id, {
  232. version: true,
  233. inS3: true,
  234. })
  235. ).to.be.rejectedWith(
  236. `No such doc: ${this.doc_id} in project ${this.project_id}`
  237. )
  238. })
  239. })
  240. })
  241. describe('getAllNonDeletedDocs', function () {
  242. describe('when the project exists', function () {
  243. beforeEach(async function () {
  244. this.docs = [
  245. {
  246. _id: this.doc_id,
  247. project_id: this.project_id,
  248. lines: ['mock-lines'],
  249. },
  250. ]
  251. this.MongoManager.promises.getProjectsDocs.resolves(this.docs)
  252. this.DocArchiveManager.promises.unArchiveAllDocs.resolves(this.docs)
  253. this.filter = { lines: true }
  254. this.result = await this.DocManager.promises.getAllNonDeletedDocs(
  255. this.project_id,
  256. this.filter,
  257. this.callback
  258. )
  259. })
  260. it('should get the project from the database', function () {
  261. this.MongoManager.promises.getProjectsDocs.should.have.been.calledWith(
  262. this.project_id,
  263. { include_deleted: false },
  264. this.filter
  265. )
  266. })
  267. it('should return the docs', function () {
  268. expect(this.result).to.deep.equal(this.docs)
  269. })
  270. })
  271. describe('when there are no docs for the project', function () {
  272. it('should return a NotFoundError', async function () {
  273. this.MongoManager.promises.getProjectsDocs.resolves(null)
  274. this.DocArchiveManager.promises.unArchiveAllDocs.resolves(null)
  275. await expect(
  276. this.DocManager.promises.getAllNonDeletedDocs(
  277. this.project_id,
  278. this.filter
  279. )
  280. ).to.be.rejectedWith(`No docs for project ${this.project_id}`)
  281. })
  282. })
  283. })
  284. describe('patchDoc', function () {
  285. describe('when the doc exists', function () {
  286. beforeEach(function () {
  287. this.lines = ['mock', 'doc', 'lines']
  288. this.rev = 77
  289. this.MongoManager.promises.findDoc.resolves({
  290. _id: ObjectId(this.doc_id),
  291. })
  292. this.meta = {}
  293. })
  294. describe('standard path', function () {
  295. beforeEach(async function () {
  296. await this.DocManager.promises.patchDoc(
  297. this.project_id,
  298. this.doc_id,
  299. this.meta
  300. )
  301. })
  302. it('should get the doc', function () {
  303. expect(this.MongoManager.promises.findDoc).to.have.been.calledWith(
  304. this.project_id,
  305. this.doc_id
  306. )
  307. })
  308. it('should persist the meta', function () {
  309. expect(this.MongoManager.promises.patchDoc).to.have.been.calledWith(
  310. this.project_id,
  311. this.doc_id,
  312. this.meta
  313. )
  314. })
  315. })
  316. describe('background flush disabled and deleting a doc', function () {
  317. beforeEach(async function () {
  318. this.settings.docstore.archiveOnSoftDelete = false
  319. this.meta.deleted = true
  320. await this.DocManager.promises.patchDoc(
  321. this.project_id,
  322. this.doc_id,
  323. this.meta
  324. )
  325. })
  326. it('should not flush the doc out of mongo', function () {
  327. expect(this.DocArchiveManager.promises.archiveDoc).to.not.have.been
  328. .called
  329. })
  330. })
  331. describe('background flush enabled and not deleting a doc', function () {
  332. beforeEach(async function () {
  333. this.settings.docstore.archiveOnSoftDelete = false
  334. this.meta.deleted = false
  335. await this.DocManager.promises.patchDoc(
  336. this.project_id,
  337. this.doc_id,
  338. this.meta
  339. )
  340. })
  341. it('should not flush the doc out of mongo', function () {
  342. expect(this.DocArchiveManager.promises.archiveDoc).to.not.have.been
  343. .called
  344. })
  345. })
  346. describe('background flush enabled and deleting a doc', function () {
  347. beforeEach(function () {
  348. this.settings.docstore.archiveOnSoftDelete = true
  349. this.meta.deleted = true
  350. })
  351. describe('when the background flush succeeds', function () {
  352. beforeEach(async function () {
  353. await this.DocManager.promises.patchDoc(
  354. this.project_id,
  355. this.doc_id,
  356. this.meta
  357. )
  358. })
  359. it('should not log a warning', function () {
  360. expect(this.logger.warn).to.not.have.been.called
  361. })
  362. it('should flush the doc out of mongo', function () {
  363. expect(
  364. this.DocArchiveManager.promises.archiveDoc
  365. ).to.have.been.calledWith(this.project_id, this.doc_id)
  366. })
  367. })
  368. describe('when the background flush fails', function () {
  369. beforeEach(async function () {
  370. this.err = new Error('foo')
  371. this.DocArchiveManager.promises.archiveDoc.rejects(this.err)
  372. await this.DocManager.promises.patchDoc(
  373. this.project_id,
  374. this.doc_id,
  375. this.meta
  376. )
  377. })
  378. it('should log a warning', function () {
  379. expect(this.logger.warn).to.have.been.calledWith(
  380. sinon.match({
  381. projectId: this.project_id,
  382. docId: this.doc_id,
  383. err: this.err,
  384. }),
  385. 'archiving a single doc in the background failed'
  386. )
  387. })
  388. })
  389. })
  390. })
  391. describe('when the doc does not exist', function () {
  392. it('should return a NotFoundError', async function () {
  393. this.MongoManager.promises.findDoc.resolves(null)
  394. await expect(
  395. this.DocManager.promises.patchDoc(this.project_id, this.doc_id, {})
  396. ).to.be.rejectedWith(
  397. `No such project/doc to delete: ${this.project_id}/${this.doc_id}`
  398. )
  399. })
  400. })
  401. })
  402. describe('updateDoc', function () {
  403. beforeEach(function () {
  404. this.oldDocLines = ['old', 'doc', 'lines']
  405. this.newDocLines = ['new', 'doc', 'lines']
  406. this.originalRanges = {
  407. changes: [
  408. {
  409. id: ObjectId().toString(),
  410. op: { i: 'foo', p: 3 },
  411. meta: {
  412. user_id: ObjectId().toString(),
  413. ts: new Date().toString(),
  414. },
  415. },
  416. ],
  417. }
  418. this.newRanges = {
  419. changes: [
  420. {
  421. id: ObjectId().toString(),
  422. op: { i: 'bar', p: 6 },
  423. meta: {
  424. user_id: ObjectId().toString(),
  425. ts: new Date().toString(),
  426. },
  427. },
  428. ],
  429. }
  430. this.version = 42
  431. this.doc = {
  432. _id: this.doc_id,
  433. project_id: this.project_id,
  434. lines: this.oldDocLines,
  435. rev: (this.rev = 5),
  436. version: this.version,
  437. ranges: this.originalRanges,
  438. }
  439. this.DocManager.promises._getDoc = sinon.stub()
  440. })
  441. describe('when only the doc lines have changed', function () {
  442. beforeEach(async function () {
  443. this.DocManager.promises._getDoc = sinon.stub().resolves(this.doc)
  444. this.result = await this.DocManager.promises.updateDoc(
  445. this.project_id,
  446. this.doc_id,
  447. this.newDocLines,
  448. this.version,
  449. this.originalRanges
  450. )
  451. })
  452. it('should get the existing doc', function () {
  453. this.DocManager.promises._getDoc
  454. .calledWith(this.project_id, this.doc_id, {
  455. version: true,
  456. rev: true,
  457. lines: true,
  458. ranges: true,
  459. inS3: true,
  460. })
  461. .should.equal(true)
  462. })
  463. it('should upsert the document to the doc collection', function () {
  464. this.MongoManager.promises.upsertIntoDocCollection
  465. .calledWith(this.project_id, this.doc_id, this.rev, {
  466. lines: this.newDocLines,
  467. })
  468. .should.equal(true)
  469. })
  470. it('should not update the version', function () {
  471. this.MongoManager.promises.setDocVersion.called.should.equal(false)
  472. })
  473. it('should return the new rev', function () {
  474. expect(this.result).to.deep.equal({ modified: true, rev: this.rev + 1 })
  475. })
  476. })
  477. describe('when the doc ranges have changed', function () {
  478. beforeEach(async function () {
  479. this.DocManager.promises._getDoc = sinon.stub().resolves(this.doc)
  480. this.RangeManager.shouldUpdateRanges.returns(true)
  481. this.result = await this.DocManager.promises.updateDoc(
  482. this.project_id,
  483. this.doc_id,
  484. this.oldDocLines,
  485. this.version,
  486. this.newRanges
  487. )
  488. })
  489. it('should upsert the ranges', function () {
  490. this.MongoManager.promises.upsertIntoDocCollection
  491. .calledWith(this.project_id, this.doc_id, this.rev, {
  492. ranges: this.newRanges,
  493. })
  494. .should.equal(true)
  495. })
  496. it('should not update the version', function () {
  497. this.MongoManager.promises.setDocVersion.called.should.equal(false)
  498. })
  499. it('should return the new rev', function () {
  500. expect(this.result).to.deep.equal({ modified: true, rev: this.rev + 1 })
  501. })
  502. })
  503. describe('when only the version has changed', function () {
  504. beforeEach(async function () {
  505. this.DocManager.promises._getDoc = sinon.stub().resolves(this.doc)
  506. this.result = await this.DocManager.promises.updateDoc(
  507. this.project_id,
  508. this.doc_id,
  509. this.oldDocLines,
  510. this.version + 1,
  511. this.originalRanges
  512. )
  513. })
  514. it('should not change the lines or ranges', function () {
  515. this.MongoManager.promises.upsertIntoDocCollection.called.should.equal(
  516. false
  517. )
  518. })
  519. it('should update the version', function () {
  520. this.MongoManager.promises.setDocVersion
  521. .calledWith(this.doc_id, this.version + 1)
  522. .should.equal(true)
  523. })
  524. it('should return the callback with the old rev', function () {
  525. expect(this.result).to.deep.equal({ modified: true, rev: this.rev })
  526. })
  527. })
  528. describe('when the doc has not changed at all', function () {
  529. beforeEach(async function () {
  530. this.DocManager.promises._getDoc = sinon.stub().resolves(this.doc)
  531. this.result = await this.DocManager.promises.updateDoc(
  532. this.project_id,
  533. this.doc_id,
  534. this.oldDocLines,
  535. this.version,
  536. this.originalRanges
  537. )
  538. })
  539. it('should not update the ranges or lines', function () {
  540. this.MongoManager.promises.upsertIntoDocCollection.called.should.equal(
  541. false
  542. )
  543. })
  544. it('should not update the version', function () {
  545. this.MongoManager.promises.setDocVersion.called.should.equal(false)
  546. })
  547. it('should return the callback with the old rev and modified == false', function () {
  548. expect(this.result).to.deep.equal({ modified: false, rev: this.rev })
  549. })
  550. })
  551. describe('when the version is null', function () {
  552. it('should return an error', async function () {
  553. await expect(
  554. this.DocManager.promises.updateDoc(
  555. this.project_id,
  556. this.doc_id,
  557. this.newDocLines,
  558. null,
  559. this.originalRanges
  560. )
  561. ).to.be.rejectedWith('no lines, version or ranges provided')
  562. })
  563. })
  564. describe('when the lines are null', function () {
  565. it('should return an error', async function () {
  566. await expect(
  567. this.DocManager.promises.updateDoc(
  568. this.project_id,
  569. this.doc_id,
  570. null,
  571. this.version,
  572. this.originalRanges,
  573. this.callback
  574. )
  575. ).to.be.rejectedWith('no lines, version or ranges provided')
  576. })
  577. })
  578. describe('when the ranges are null', function () {
  579. it('should return an error', async function () {
  580. await expect(
  581. this.DocManager.promises.updateDoc(
  582. this.project_id,
  583. this.doc_id,
  584. this.newDocLines,
  585. this.version,
  586. null
  587. )
  588. ).to.be.rejectedWith('no lines, version or ranges provided')
  589. })
  590. })
  591. describe('when there is a generic error getting the doc', function () {
  592. beforeEach(async function () {
  593. this.error = new Error('doc could not be found')
  594. this.DocManager.promises._getDoc = sinon.stub().rejects(this.error)
  595. await expect(
  596. this.DocManager.promises.updateDoc(
  597. this.project_id,
  598. this.doc_id,
  599. this.newDocLines,
  600. this.version,
  601. this.originalRanges
  602. )
  603. ).to.be.rejectedWith(this.error)
  604. })
  605. it('should not upsert the document to the doc collection', function () {
  606. this.MongoManager.promises.upsertIntoDocCollection.should.not.have.been
  607. .called
  608. })
  609. })
  610. describe('when the version was decremented', function () {
  611. it('should return an error', async function () {
  612. this.DocManager.promises._getDoc = sinon.stub().resolves(this.doc)
  613. await expect(
  614. this.DocManager.promises.updateDoc(
  615. this.project_id,
  616. this.doc_id,
  617. this.newDocLines,
  618. this.version - 1,
  619. this.originalRanges
  620. )
  621. ).to.be.rejectedWith(Errors.DocVersionDecrementedError)
  622. })
  623. })
  624. describe('when the doc lines have not changed', function () {
  625. beforeEach(async function () {
  626. this.DocManager.promises._getDoc = sinon.stub().resolves(this.doc)
  627. this.result = await this.DocManager.promises.updateDoc(
  628. this.project_id,
  629. this.doc_id,
  630. this.oldDocLines.slice(),
  631. this.version,
  632. this.originalRanges,
  633. this.callback
  634. )
  635. })
  636. it('should not update the doc', function () {
  637. this.MongoManager.promises.upsertIntoDocCollection.called.should.equal(
  638. false
  639. )
  640. })
  641. it('should return the callback with the existing rev', function () {
  642. expect(this.result).to.deep.equal({ modified: false, rev: this.rev })
  643. })
  644. })
  645. describe('when the doc does not exist', function () {
  646. beforeEach(async function () {
  647. this.DocManager.promises._getDoc = sinon.stub().resolves(null)
  648. this.result = await this.DocManager.promises.updateDoc(
  649. this.project_id,
  650. this.doc_id,
  651. this.newDocLines,
  652. this.version,
  653. this.originalRanges
  654. )
  655. })
  656. it('should upsert the document to the doc collection', function () {
  657. this.MongoManager.promises.upsertIntoDocCollection
  658. .calledWith(this.project_id, this.doc_id, undefined, {
  659. lines: this.newDocLines,
  660. ranges: this.originalRanges,
  661. })
  662. .should.equal(true)
  663. })
  664. it('should set the version', function () {
  665. this.MongoManager.promises.setDocVersion
  666. .calledWith(this.doc_id, this.version)
  667. .should.equal(true)
  668. })
  669. it('should return the callback with the new rev', function () {
  670. expect(this.result).to.deep.equal({ modified: true, rev: 1 })
  671. })
  672. })
  673. describe('when another update is racing', function () {
  674. beforeEach(async function () {
  675. this.DocManager.promises._getDoc = sinon.stub().resolves(this.doc)
  676. this.MongoManager.promises.upsertIntoDocCollection
  677. .onFirstCall()
  678. .rejects(new Errors.DocRevValueError())
  679. this.RangeManager.shouldUpdateRanges.returns(true)
  680. this.result = await this.DocManager.promises.updateDoc(
  681. this.project_id,
  682. this.doc_id,
  683. this.newDocLines,
  684. this.version + 1,
  685. this.newRanges
  686. )
  687. })
  688. it('should upsert the doc twice', function () {
  689. this.MongoManager.promises.upsertIntoDocCollection.should.have.been.calledWith(
  690. this.project_id,
  691. this.doc_id,
  692. this.rev,
  693. {
  694. ranges: this.newRanges,
  695. lines: this.newDocLines,
  696. }
  697. )
  698. this.MongoManager.promises.upsertIntoDocCollection.should.have.been
  699. .calledTwice
  700. })
  701. it('should update the version once', function () {
  702. this.MongoManager.promises.setDocVersion.should.have.been.calledOnce
  703. })
  704. it('should return the callback with the new rev', function () {
  705. expect(this.result).to.deep.equal({ modified: true, rev: this.rev + 1 })
  706. })
  707. })
  708. })
  709. })