| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532 |
- const { db, ObjectId } = require('../../../app/js/mongodb')
- const { expect } = require('chai')
- const DocstoreApp = require('./helpers/DocstoreApp')
- const Errors = require('../../../app/js/Errors')
- const Settings = require('@overleaf/settings')
- const { Storage } = require('@google-cloud/storage')
- const DocstoreClient = require('./helpers/DocstoreClient')
- function deleteTestSuite(deleteDoc) {
- before(async function () {
- // Create buckets needed by the archiving part of these tests
- const storage = new Storage(Settings.docstore.gcs.endpoint)
- await storage.createBucket(Settings.docstore.bucket)
- await storage.createBucket(`${Settings.docstore.bucket}-deleted`)
- })
- after(async function () {
- // Tear down the buckets created above
- const storage = new Storage(Settings.docstore.gcs.endpoint)
- await storage.bucket(Settings.docstore.bucket).deleteFiles()
- await storage.bucket(Settings.docstore.bucket).delete()
- await storage.bucket(`${Settings.docstore.bucket}-deleted`).deleteFiles()
- await storage.bucket(`${Settings.docstore.bucket}-deleted`).delete()
- })
- beforeEach(function (done) {
- this.project_id = ObjectId()
- this.doc_id = ObjectId()
- this.lines = ['original', 'lines']
- this.version = 42
- this.ranges = []
- DocstoreApp.ensureRunning(() => {
- DocstoreClient.createDoc(
- this.project_id,
- this.doc_id,
- this.lines,
- this.version,
- this.ranges,
- error => {
- if (error) {
- throw error
- }
- done()
- }
- )
- })
- })
- it('should show as not deleted on /deleted', function (done) {
- DocstoreClient.isDocDeleted(
- this.project_id,
- this.doc_id,
- (error, res, body) => {
- if (error) return done(error)
- expect(res.statusCode).to.equal(200)
- expect(body).to.have.property('deleted').to.equal(false)
- done()
- }
- )
- })
- describe('when the doc exists', function () {
- beforeEach(function (done) {
- deleteDoc(this.project_id, this.doc_id, (error, res, doc) => {
- if (error) return done(error)
- this.res = res
- done()
- })
- })
- afterEach(function (done) {
- db.docs.remove({ _id: this.doc_id }, done)
- })
- it('should mark the doc as deleted on /deleted', function (done) {
- DocstoreClient.isDocDeleted(
- this.project_id,
- this.doc_id,
- (error, res, body) => {
- if (error) return done(error)
- expect(res.statusCode).to.equal(200)
- expect(body).to.have.property('deleted').to.equal(true)
- done()
- }
- )
- })
- it('should insert a deleted doc into the docs collection', function (done) {
- db.docs.find({ _id: this.doc_id }).toArray((error, docs) => {
- if (error) return done(error)
- docs[0]._id.should.deep.equal(this.doc_id)
- docs[0].lines.should.deep.equal(this.lines)
- docs[0].deleted.should.equal(true)
- done()
- })
- })
- it('should not export the doc to s3', function (done) {
- setTimeout(() => {
- DocstoreClient.getS3Doc(this.project_id, this.doc_id, error => {
- expect(error).to.be.instanceOf(Errors.NotFoundError)
- done()
- })
- }, 1000)
- })
- })
- describe('when archiveOnSoftDelete is enabled', function () {
- let archiveOnSoftDelete
- beforeEach('overwrite settings', function () {
- archiveOnSoftDelete = Settings.docstore.archiveOnSoftDelete
- Settings.docstore.archiveOnSoftDelete = true
- })
- afterEach('restore settings', function () {
- Settings.docstore.archiveOnSoftDelete = archiveOnSoftDelete
- })
- beforeEach('delete Doc', function (done) {
- deleteDoc(this.project_id, this.doc_id, (error, res) => {
- if (error) return done(error)
- this.res = res
- done()
- })
- })
- beforeEach(function waitForBackgroundFlush(done) {
- setTimeout(done, 500)
- })
- afterEach(function cleanupDoc(done) {
- db.docs.remove({ _id: this.doc_id }, done)
- })
- it('should set the deleted flag in the doc', function (done) {
- db.docs.findOne({ _id: this.doc_id }, (error, doc) => {
- if (error) {
- return done(error)
- }
- expect(doc.deleted).to.equal(true)
- done()
- })
- })
- it('should set inS3 and unset lines and ranges in the doc', function (done) {
- db.docs.findOne({ _id: this.doc_id }, (error, doc) => {
- if (error) {
- return done(error)
- }
- expect(doc.lines).to.not.exist
- expect(doc.ranges).to.not.exist
- expect(doc.inS3).to.equal(true)
- done()
- })
- })
- it('should set the doc in s3 correctly', function (done) {
- DocstoreClient.getS3Doc(this.project_id, this.doc_id, (error, s3doc) => {
- if (error) {
- return done(error)
- }
- expect(s3doc.lines).to.deep.equal(this.lines)
- expect(s3doc.ranges).to.deep.equal(this.ranges)
- done()
- })
- })
- })
- describe('when the doc exists in another project', function () {
- const otherProjectId = ObjectId()
- it('should show as not existing on /deleted', function (done) {
- DocstoreClient.isDocDeleted(otherProjectId, this.doc_id, (error, res) => {
- if (error) return done(error)
- expect(res.statusCode).to.equal(404)
- done()
- })
- })
- it('should return a 404 when trying to delete', function (done) {
- deleteDoc(otherProjectId, this.doc_id, (error, res) => {
- if (error) return done(error)
- expect(res.statusCode).to.equal(404)
- done()
- })
- })
- })
- describe('when the doc does not exist', function () {
- it('should show as not existing on /deleted', function (done) {
- const missingDocId = ObjectId()
- DocstoreClient.isDocDeleted(
- this.project_id,
- missingDocId,
- (error, res) => {
- if (error) return done(error)
- expect(res.statusCode).to.equal(404)
- done()
- }
- )
- })
- it('should return a 404', function (done) {
- const missingDocId = ObjectId()
- deleteDoc(this.project_id, missingDocId, (error, res, doc) => {
- if (error) return done(error)
- res.statusCode.should.equal(404)
- done()
- })
- })
- })
- }
- describe('Delete via PATCH', function () {
- deleteTestSuite(DocstoreClient.deleteDoc)
- describe('when providing a custom doc name in the delete request', function () {
- beforeEach(function (done) {
- DocstoreClient.deleteDocWithName(
- this.project_id,
- this.doc_id,
- 'wombat.tex',
- done
- )
- })
- it('should insert the doc name into the docs collection', function (done) {
- db.docs.find({ _id: this.doc_id }).toArray((error, docs) => {
- if (error) return done(error)
- expect(docs[0].name).to.equal('wombat.tex')
- done()
- })
- })
- })
- describe('when providing a custom deletedAt date in the delete request', function () {
- beforeEach('record date and delay', function (done) {
- this.deletedAt = new Date()
- setTimeout(done, 5)
- })
- beforeEach('perform deletion with past date', function (done) {
- DocstoreClient.deleteDocWithDate(
- this.project_id,
- this.doc_id,
- this.deletedAt,
- done
- )
- })
- it('should insert the date into the docs collection', function (done) {
- db.docs.find({ _id: this.doc_id }).toArray((error, docs) => {
- if (error) return done(error)
- expect(docs[0].deletedAt.toISOString()).to.equal(
- this.deletedAt.toISOString()
- )
- done()
- })
- })
- })
- describe('when providing no doc name in the delete request', function () {
- beforeEach(function (done) {
- DocstoreClient.deleteDocWithName(
- this.project_id,
- this.doc_id,
- '',
- (error, res) => {
- this.res = res
- done(error)
- }
- )
- })
- it('should reject the request', function () {
- expect(this.res.statusCode).to.equal(400)
- })
- })
- describe('when providing no date in the delete request', function () {
- beforeEach(function (done) {
- DocstoreClient.deleteDocWithDate(
- this.project_id,
- this.doc_id,
- '',
- (error, res) => {
- this.res = res
- done(error)
- }
- )
- })
- it('should reject the request', function () {
- expect(this.res.statusCode).to.equal(400)
- })
- })
- describe('before deleting anything', function () {
- it('should show nothing in deleted docs response', function (done) {
- DocstoreClient.getAllDeletedDocs(
- this.project_id,
- (error, deletedDocs) => {
- if (error) return done(error)
- expect(deletedDocs).to.deep.equal([])
- done()
- }
- )
- })
- })
- describe('when the doc gets a name on delete', function () {
- beforeEach(function (done) {
- this.deletedAt = new Date()
- DocstoreClient.deleteDocWithDate(
- this.project_id,
- this.doc_id,
- this.deletedAt,
- done
- )
- })
- it('should show the doc in deleted docs response', function (done) {
- DocstoreClient.getAllDeletedDocs(
- this.project_id,
- (error, deletedDocs) => {
- if (error) return done(error)
- expect(deletedDocs).to.deep.equal([
- {
- _id: this.doc_id.toString(),
- name: 'main.tex',
- deletedAt: this.deletedAt.toISOString(),
- },
- ])
- done()
- }
- )
- })
- describe('after deleting multiple docs', function () {
- beforeEach('create doc2', function (done) {
- this.doc_id2 = ObjectId()
- DocstoreClient.createDoc(
- this.project_id,
- this.doc_id2,
- this.lines,
- this.version,
- this.ranges,
- done
- )
- })
- beforeEach('delete doc2', function (done) {
- this.deletedAt2 = new Date()
- DocstoreClient.deleteDocWithDateAndName(
- this.project_id,
- this.doc_id2,
- this.deletedAt2,
- 'two.tex',
- done
- )
- })
- beforeEach('create doc3', function (done) {
- this.doc_id3 = ObjectId()
- DocstoreClient.createDoc(
- this.project_id,
- this.doc_id3,
- this.lines,
- this.version,
- this.ranges,
- done
- )
- })
- beforeEach('delete doc3', function (done) {
- this.deletedAt3 = new Date()
- DocstoreClient.deleteDocWithDateAndName(
- this.project_id,
- this.doc_id3,
- this.deletedAt3,
- 'three.tex',
- done
- )
- })
- it('should show all the docs as deleted', function (done) {
- DocstoreClient.getAllDeletedDocs(
- this.project_id,
- (error, deletedDocs) => {
- if (error) return done(error)
- expect(deletedDocs).to.deep.equal([
- {
- _id: this.doc_id3.toString(),
- name: 'three.tex',
- deletedAt: this.deletedAt3.toISOString(),
- },
- {
- _id: this.doc_id2.toString(),
- name: 'two.tex',
- deletedAt: this.deletedAt2.toISOString(),
- },
- {
- _id: this.doc_id.toString(),
- name: 'main.tex',
- deletedAt: this.deletedAt.toISOString(),
- },
- ])
- done()
- }
- )
- })
- describe('with one more than max_deleted_docs permits', function () {
- let maxDeletedDocsBefore
- beforeEach(function () {
- maxDeletedDocsBefore = Settings.max_deleted_docs
- Settings.max_deleted_docs = 2
- })
- afterEach(function () {
- Settings.max_deleted_docs = maxDeletedDocsBefore
- })
- it('should omit the first deleted doc', function (done) {
- DocstoreClient.getAllDeletedDocs(
- this.project_id,
- (error, deletedDocs) => {
- if (error) return done(error)
- expect(deletedDocs).to.deep.equal([
- {
- _id: this.doc_id3.toString(),
- name: 'three.tex',
- deletedAt: this.deletedAt3.toISOString(),
- },
- {
- _id: this.doc_id2.toString(),
- name: 'two.tex',
- deletedAt: this.deletedAt2.toISOString(),
- },
- // dropped main.tex
- ])
- done()
- }
- )
- })
- })
- })
- })
- })
- describe("Destroying a project's documents", function () {
- beforeEach(function (done) {
- this.project_id = ObjectId()
- this.doc_id = ObjectId()
- this.lines = ['original', 'lines']
- this.version = 42
- this.ranges = []
- DocstoreApp.ensureRunning(() => {
- DocstoreClient.createDoc(
- this.project_id,
- this.doc_id,
- this.lines,
- this.version,
- this.ranges,
- error => {
- if (error) {
- throw error
- }
- done()
- }
- )
- })
- })
- describe('when the doc exists', function () {
- beforeEach(function (done) {
- db.docOps.insert({ doc_id: ObjectId(this.doc_id), version: 1 }, err => {
- if (err) {
- return done(err)
- }
- DocstoreClient.destroyAllDoc(this.project_id, done)
- })
- })
- it('should remove the doc from the docs collection', function (done) {
- db.docs.find({ _id: this.doc_id }).toArray((err, docs) => {
- expect(err).not.to.exist
- expect(docs).to.deep.equal([])
- done()
- })
- })
- it('should remove the docOps from the docOps collection', function (done) {
- db.docOps.find({ doc_id: this.doc_id }).toArray((err, docOps) => {
- expect(err).not.to.exist
- expect(docOps).to.deep.equal([])
- done()
- })
- })
- })
- describe('when the doc is archived', function () {
- beforeEach(function (done) {
- DocstoreClient.archiveAllDoc(this.project_id, err => {
- if (err) {
- return done(err)
- }
- DocstoreClient.destroyAllDoc(this.project_id, done)
- })
- })
- it('should remove the doc from the docs collection', function (done) {
- db.docs.find({ _id: this.doc_id }).toArray((err, docs) => {
- expect(err).not.to.exist
- expect(docs).to.deep.equal([])
- done()
- })
- })
- it('should remove the docOps from the docOps collection', function (done) {
- db.docOps.find({ doc_id: this.doc_id }).toArray((err, docOps) => {
- expect(err).not.to.exist
- expect(docOps).to.deep.equal([])
- done()
- })
- })
- it('should remove the doc contents from s3', function (done) {
- DocstoreClient.getS3Doc(this.project_id, this.doc_id, error => {
- expect(error).to.be.instanceOf(Errors.NotFoundError)
- done()
- })
- })
- })
- })
|