DeletingDocsTests.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515
  1. const { db, ObjectId } = require('../../../app/js/mongodb')
  2. const { expect } = require('chai')
  3. const DocstoreApp = require('./helpers/DocstoreApp')
  4. const Errors = require('../../../app/js/Errors')
  5. const Settings = require('@overleaf/settings')
  6. const DocstoreClient = require('./helpers/DocstoreClient')
  7. function deleteTestSuite(deleteDoc) {
  8. beforeEach(function (done) {
  9. this.project_id = ObjectId()
  10. this.doc_id = ObjectId()
  11. this.lines = ['original', 'lines']
  12. this.version = 42
  13. this.ranges = []
  14. DocstoreApp.ensureRunning(() => {
  15. DocstoreClient.createDoc(
  16. this.project_id,
  17. this.doc_id,
  18. this.lines,
  19. this.version,
  20. this.ranges,
  21. error => {
  22. if (error) {
  23. throw error
  24. }
  25. done()
  26. }
  27. )
  28. })
  29. })
  30. it('should show as not deleted on /deleted', function (done) {
  31. DocstoreClient.isDocDeleted(
  32. this.project_id,
  33. this.doc_id,
  34. (error, res, body) => {
  35. if (error) return done(error)
  36. expect(res.statusCode).to.equal(200)
  37. expect(body).to.have.property('deleted').to.equal(false)
  38. done()
  39. }
  40. )
  41. })
  42. describe('when the doc exists', function () {
  43. beforeEach(function (done) {
  44. deleteDoc(this.project_id, this.doc_id, (error, res, doc) => {
  45. if (error) return done(error)
  46. this.res = res
  47. done()
  48. })
  49. })
  50. afterEach(function (done) {
  51. db.docs.remove({ _id: this.doc_id }, done)
  52. })
  53. it('should mark the doc as deleted on /deleted', function (done) {
  54. DocstoreClient.isDocDeleted(
  55. this.project_id,
  56. this.doc_id,
  57. (error, res, body) => {
  58. if (error) return done(error)
  59. expect(res.statusCode).to.equal(200)
  60. expect(body).to.have.property('deleted').to.equal(true)
  61. done()
  62. }
  63. )
  64. })
  65. it('should insert a deleted doc into the docs collection', function (done) {
  66. db.docs.find({ _id: this.doc_id }).toArray((error, docs) => {
  67. if (error) return done(error)
  68. docs[0]._id.should.deep.equal(this.doc_id)
  69. docs[0].lines.should.deep.equal(this.lines)
  70. docs[0].deleted.should.equal(true)
  71. done()
  72. })
  73. })
  74. it('should not export the doc to s3', function (done) {
  75. setTimeout(() => {
  76. DocstoreClient.getS3Doc(this.project_id, this.doc_id, error => {
  77. expect(error).to.be.instanceOf(Errors.NotFoundError)
  78. done()
  79. })
  80. }, 1000)
  81. })
  82. })
  83. describe('when archiveOnSoftDelete is enabled', function () {
  84. let archiveOnSoftDelete
  85. beforeEach('overwrite settings', function () {
  86. archiveOnSoftDelete = Settings.docstore.archiveOnSoftDelete
  87. Settings.docstore.archiveOnSoftDelete = true
  88. })
  89. afterEach('restore settings', function () {
  90. Settings.docstore.archiveOnSoftDelete = archiveOnSoftDelete
  91. })
  92. beforeEach('delete Doc', function (done) {
  93. deleteDoc(this.project_id, this.doc_id, (error, res) => {
  94. if (error) return done(error)
  95. this.res = res
  96. done()
  97. })
  98. })
  99. beforeEach(function waitForBackgroundFlush(done) {
  100. setTimeout(done, 500)
  101. })
  102. afterEach(function cleanupDoc(done) {
  103. db.docs.remove({ _id: this.doc_id }, done)
  104. })
  105. it('should set the deleted flag in the doc', function (done) {
  106. db.docs.findOne({ _id: this.doc_id }, (error, doc) => {
  107. if (error) {
  108. return done(error)
  109. }
  110. expect(doc.deleted).to.equal(true)
  111. done()
  112. })
  113. })
  114. it('should set inS3 and unset lines and ranges in the doc', function (done) {
  115. db.docs.findOne({ _id: this.doc_id }, (error, doc) => {
  116. if (error) {
  117. return done(error)
  118. }
  119. expect(doc.lines).to.not.exist
  120. expect(doc.ranges).to.not.exist
  121. expect(doc.inS3).to.equal(true)
  122. done()
  123. })
  124. })
  125. it('should set the doc in s3 correctly', function (done) {
  126. DocstoreClient.getS3Doc(this.project_id, this.doc_id, (error, s3doc) => {
  127. if (error) {
  128. return done(error)
  129. }
  130. expect(s3doc.lines).to.deep.equal(this.lines)
  131. expect(s3doc.ranges).to.deep.equal(this.ranges)
  132. done()
  133. })
  134. })
  135. })
  136. describe('when the doc exists in another project', function () {
  137. const otherProjectId = ObjectId()
  138. it('should show as not existing on /deleted', function (done) {
  139. DocstoreClient.isDocDeleted(otherProjectId, this.doc_id, (error, res) => {
  140. if (error) return done(error)
  141. expect(res.statusCode).to.equal(404)
  142. done()
  143. })
  144. })
  145. it('should return a 404 when trying to delete', function (done) {
  146. deleteDoc(otherProjectId, this.doc_id, (error, res) => {
  147. if (error) return done(error)
  148. expect(res.statusCode).to.equal(404)
  149. done()
  150. })
  151. })
  152. })
  153. describe('when the doc does not exist', function () {
  154. it('should show as not existing on /deleted', function (done) {
  155. const missingDocId = ObjectId()
  156. DocstoreClient.isDocDeleted(
  157. this.project_id,
  158. missingDocId,
  159. (error, res) => {
  160. if (error) return done(error)
  161. expect(res.statusCode).to.equal(404)
  162. done()
  163. }
  164. )
  165. })
  166. it('should return a 404', function (done) {
  167. const missingDocId = ObjectId()
  168. deleteDoc(this.project_id, missingDocId, (error, res, doc) => {
  169. if (error) return done(error)
  170. res.statusCode.should.equal(404)
  171. done()
  172. })
  173. })
  174. })
  175. }
  176. describe('Delete via PATCH', function () {
  177. deleteTestSuite(DocstoreClient.deleteDoc)
  178. describe('when providing a custom doc name in the delete request', function () {
  179. beforeEach(function (done) {
  180. DocstoreClient.deleteDocWithName(
  181. this.project_id,
  182. this.doc_id,
  183. 'wombat.tex',
  184. done
  185. )
  186. })
  187. it('should insert the doc name into the docs collection', function (done) {
  188. db.docs.find({ _id: this.doc_id }).toArray((error, docs) => {
  189. if (error) return done(error)
  190. expect(docs[0].name).to.equal('wombat.tex')
  191. done()
  192. })
  193. })
  194. })
  195. describe('when providing a custom deletedAt date in the delete request', function () {
  196. beforeEach('record date and delay', function (done) {
  197. this.deletedAt = new Date()
  198. setTimeout(done, 5)
  199. })
  200. beforeEach('perform deletion with past date', function (done) {
  201. DocstoreClient.deleteDocWithDate(
  202. this.project_id,
  203. this.doc_id,
  204. this.deletedAt,
  205. done
  206. )
  207. })
  208. it('should insert the date into the docs collection', function (done) {
  209. db.docs.find({ _id: this.doc_id }).toArray((error, docs) => {
  210. if (error) return done(error)
  211. expect(docs[0].deletedAt.toISOString()).to.equal(
  212. this.deletedAt.toISOString()
  213. )
  214. done()
  215. })
  216. })
  217. })
  218. describe('when providing no doc name in the delete request', function () {
  219. beforeEach(function (done) {
  220. DocstoreClient.deleteDocWithName(
  221. this.project_id,
  222. this.doc_id,
  223. '',
  224. (error, res) => {
  225. this.res = res
  226. done(error)
  227. }
  228. )
  229. })
  230. it('should reject the request', function () {
  231. expect(this.res.statusCode).to.equal(400)
  232. })
  233. })
  234. describe('when providing no date in the delete request', function () {
  235. beforeEach(function (done) {
  236. DocstoreClient.deleteDocWithDate(
  237. this.project_id,
  238. this.doc_id,
  239. '',
  240. (error, res) => {
  241. this.res = res
  242. done(error)
  243. }
  244. )
  245. })
  246. it('should reject the request', function () {
  247. expect(this.res.statusCode).to.equal(400)
  248. })
  249. })
  250. describe('before deleting anything', function () {
  251. it('should show nothing in deleted docs response', function (done) {
  252. DocstoreClient.getAllDeletedDocs(
  253. this.project_id,
  254. (error, deletedDocs) => {
  255. if (error) return done(error)
  256. expect(deletedDocs).to.deep.equal([])
  257. done()
  258. }
  259. )
  260. })
  261. })
  262. describe('when the doc gets a name on delete', function () {
  263. beforeEach(function (done) {
  264. this.deletedAt = new Date()
  265. DocstoreClient.deleteDocWithDate(
  266. this.project_id,
  267. this.doc_id,
  268. this.deletedAt,
  269. done
  270. )
  271. })
  272. it('should show the doc in deleted docs response', function (done) {
  273. DocstoreClient.getAllDeletedDocs(
  274. this.project_id,
  275. (error, deletedDocs) => {
  276. if (error) return done(error)
  277. expect(deletedDocs).to.deep.equal([
  278. {
  279. _id: this.doc_id.toString(),
  280. name: 'main.tex',
  281. deletedAt: this.deletedAt.toISOString(),
  282. },
  283. ])
  284. done()
  285. }
  286. )
  287. })
  288. describe('after deleting multiple docs', function () {
  289. beforeEach('create doc2', function (done) {
  290. this.doc_id2 = ObjectId()
  291. DocstoreClient.createDoc(
  292. this.project_id,
  293. this.doc_id2,
  294. this.lines,
  295. this.version,
  296. this.ranges,
  297. done
  298. )
  299. })
  300. beforeEach('delete doc2', function (done) {
  301. this.deletedAt2 = new Date()
  302. DocstoreClient.deleteDocWithDateAndName(
  303. this.project_id,
  304. this.doc_id2,
  305. this.deletedAt2,
  306. 'two.tex',
  307. done
  308. )
  309. })
  310. beforeEach('create doc3', function (done) {
  311. this.doc_id3 = ObjectId()
  312. DocstoreClient.createDoc(
  313. this.project_id,
  314. this.doc_id3,
  315. this.lines,
  316. this.version,
  317. this.ranges,
  318. done
  319. )
  320. })
  321. beforeEach('delete doc3', function (done) {
  322. this.deletedAt3 = new Date()
  323. DocstoreClient.deleteDocWithDateAndName(
  324. this.project_id,
  325. this.doc_id3,
  326. this.deletedAt3,
  327. 'three.tex',
  328. done
  329. )
  330. })
  331. it('should show all the docs as deleted', function (done) {
  332. DocstoreClient.getAllDeletedDocs(
  333. this.project_id,
  334. (error, deletedDocs) => {
  335. if (error) return done(error)
  336. expect(deletedDocs).to.deep.equal([
  337. {
  338. _id: this.doc_id3.toString(),
  339. name: 'three.tex',
  340. deletedAt: this.deletedAt3.toISOString(),
  341. },
  342. {
  343. _id: this.doc_id2.toString(),
  344. name: 'two.tex',
  345. deletedAt: this.deletedAt2.toISOString(),
  346. },
  347. {
  348. _id: this.doc_id.toString(),
  349. name: 'main.tex',
  350. deletedAt: this.deletedAt.toISOString(),
  351. },
  352. ])
  353. done()
  354. }
  355. )
  356. })
  357. describe('with one more than max_deleted_docs permits', function () {
  358. let maxDeletedDocsBefore
  359. beforeEach(function () {
  360. maxDeletedDocsBefore = Settings.max_deleted_docs
  361. Settings.max_deleted_docs = 2
  362. })
  363. afterEach(function () {
  364. Settings.max_deleted_docs = maxDeletedDocsBefore
  365. })
  366. it('should omit the first deleted doc', function (done) {
  367. DocstoreClient.getAllDeletedDocs(
  368. this.project_id,
  369. (error, deletedDocs) => {
  370. if (error) return done(error)
  371. expect(deletedDocs).to.deep.equal([
  372. {
  373. _id: this.doc_id3.toString(),
  374. name: 'three.tex',
  375. deletedAt: this.deletedAt3.toISOString(),
  376. },
  377. {
  378. _id: this.doc_id2.toString(),
  379. name: 'two.tex',
  380. deletedAt: this.deletedAt2.toISOString(),
  381. },
  382. // dropped main.tex
  383. ])
  384. done()
  385. }
  386. )
  387. })
  388. })
  389. })
  390. })
  391. })
  392. describe("Destroying a project's documents", function () {
  393. beforeEach(function (done) {
  394. this.project_id = ObjectId()
  395. this.doc_id = ObjectId()
  396. this.lines = ['original', 'lines']
  397. this.version = 42
  398. this.ranges = []
  399. DocstoreApp.ensureRunning(() => {
  400. DocstoreClient.createDoc(
  401. this.project_id,
  402. this.doc_id,
  403. this.lines,
  404. this.version,
  405. this.ranges,
  406. error => {
  407. if (error) {
  408. throw error
  409. }
  410. done()
  411. }
  412. )
  413. })
  414. })
  415. describe('when the doc exists', function () {
  416. beforeEach(function (done) {
  417. db.docOps.insert({ doc_id: ObjectId(this.doc_id), version: 1 }, err => {
  418. if (err) {
  419. return done(err)
  420. }
  421. DocstoreClient.destroyAllDoc(this.project_id, done)
  422. })
  423. })
  424. it('should remove the doc from the docs collection', function (done) {
  425. db.docs.find({ _id: this.doc_id }).toArray((err, docs) => {
  426. expect(err).not.to.exist
  427. expect(docs).to.deep.equal([])
  428. done()
  429. })
  430. })
  431. it('should remove the docOps from the docOps collection', function (done) {
  432. db.docOps.find({ doc_id: this.doc_id }).toArray((err, docOps) => {
  433. expect(err).not.to.exist
  434. expect(docOps).to.deep.equal([])
  435. done()
  436. })
  437. })
  438. })
  439. describe('when the doc is archived', function () {
  440. beforeEach(function (done) {
  441. DocstoreClient.archiveAllDoc(this.project_id, err => {
  442. if (err) {
  443. return done(err)
  444. }
  445. DocstoreClient.destroyAllDoc(this.project_id, done)
  446. })
  447. })
  448. it('should remove the doc from the docs collection', function (done) {
  449. db.docs.find({ _id: this.doc_id }).toArray((err, docs) => {
  450. expect(err).not.to.exist
  451. expect(docs).to.deep.equal([])
  452. done()
  453. })
  454. })
  455. it('should remove the docOps from the docOps collection', function (done) {
  456. db.docOps.find({ doc_id: this.doc_id }).toArray((err, docOps) => {
  457. expect(err).not.to.exist
  458. expect(docOps).to.deep.equal([])
  459. done()
  460. })
  461. })
  462. it('should remove the doc contents from s3', function (done) {
  463. DocstoreClient.getS3Doc(this.project_id, this.doc_id, error => {
  464. expect(error).to.be.instanceOf(Errors.NotFoundError)
  465. done()
  466. })
  467. })
  468. })
  469. })