DocManagerTests.js 23 KB

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