DeletingDocsTests.js 14 KB

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