|
|
@@ -1,47 +1,59 @@
|
|
|
-/* eslint-disable
|
|
|
- camelcase,
|
|
|
- handle-callback-err,
|
|
|
- no-return-assign,
|
|
|
- no-unused-vars,
|
|
|
-*/
|
|
|
-// TODO: This file was created by bulk-decaffeinate.
|
|
|
-// Fix any style issues and re-enable lint.
|
|
|
-/*
|
|
|
- * decaffeinate suggestions:
|
|
|
- * DS101: Remove unnecessary use of Array.from
|
|
|
- * DS102: Remove unnecessary code created because of implicit returns
|
|
|
- * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
|
|
- */
|
|
|
-const { assert } = require('chai')
|
|
|
const sinon = require('sinon')
|
|
|
const chai = require('chai')
|
|
|
-const should = chai.should()
|
|
|
const { expect } = chai
|
|
|
const modulePath = '../../../app/js/DocArchiveManager.js'
|
|
|
const SandboxedModule = require('sandboxed-module')
|
|
|
const { ObjectId } = require('mongojs')
|
|
|
const Errors = require('../../../app/js/Errors')
|
|
|
-const crypto = require('crypto')
|
|
|
+
|
|
|
+chai.use(require('chai-as-promised'))
|
|
|
+chai.use(require('sinon-chai'))
|
|
|
|
|
|
describe('DocArchiveManager', function () {
|
|
|
+ let DocArchiveManager,
|
|
|
+ PersistorManager,
|
|
|
+ MongoManager,
|
|
|
+ RangeManager,
|
|
|
+ Settings,
|
|
|
+ Logger,
|
|
|
+ Crypto,
|
|
|
+ Streamifier,
|
|
|
+ HashDigest,
|
|
|
+ HashUpdate,
|
|
|
+ archivedDocs,
|
|
|
+ mongoDocs,
|
|
|
+ docJson,
|
|
|
+ md5Sum,
|
|
|
+ projectId,
|
|
|
+ readStream,
|
|
|
+ stream
|
|
|
+
|
|
|
beforeEach(function () {
|
|
|
- this.settings = {
|
|
|
+ md5Sum = 'decafbad'
|
|
|
+
|
|
|
+ RangeManager = {
|
|
|
+ jsonRangesToMongo: sinon.stub().returns({ mongo: 'ranges' })
|
|
|
+ }
|
|
|
+ Settings = {
|
|
|
docstore: {
|
|
|
- s3: {
|
|
|
- secret: 'secret',
|
|
|
- key: 'this_key',
|
|
|
- bucket: 'doc-archive-unit-test'
|
|
|
- }
|
|
|
+ bucket: 'wombat'
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
- this.request = {
|
|
|
- put: {},
|
|
|
- get: {},
|
|
|
- del: {}
|
|
|
+ Logger = {
|
|
|
+ log: sinon.stub(),
|
|
|
+ err: sinon.stub()
|
|
|
+ }
|
|
|
+ HashDigest = sinon.stub().returns(md5Sum)
|
|
|
+ HashUpdate = sinon.stub().returns({ digest: HashDigest })
|
|
|
+ Crypto = {
|
|
|
+ createHash: sinon.stub().returns({ update: HashUpdate })
|
|
|
+ }
|
|
|
+ Streamifier = {
|
|
|
+ createReadStream: sinon.stub().returns({ stream: 'readStream' })
|
|
|
}
|
|
|
|
|
|
- this.archivedDocs = [
|
|
|
+ projectId = ObjectId()
|
|
|
+ archivedDocs = [
|
|
|
{
|
|
|
_id: ObjectId(),
|
|
|
inS3: true,
|
|
|
@@ -58,8 +70,7 @@ describe('DocArchiveManager', function () {
|
|
|
rev: 6
|
|
|
}
|
|
|
]
|
|
|
-
|
|
|
- this.mongoDocs = [
|
|
|
+ mongoDocs = [
|
|
|
{
|
|
|
_id: ObjectId(),
|
|
|
lines: ['one', 'two', 'three'],
|
|
|
@@ -87,488 +98,427 @@ describe('DocArchiveManager', function () {
|
|
|
}
|
|
|
]
|
|
|
|
|
|
- this.unarchivedDocs = [
|
|
|
- {
|
|
|
- _id: ObjectId(),
|
|
|
- lines: ['wombat', 'potato', 'banana'],
|
|
|
- rev: 2
|
|
|
- },
|
|
|
- {
|
|
|
- _id: ObjectId(),
|
|
|
- lines: ['llama', 'turnip', 'apple'],
|
|
|
- rev: 4
|
|
|
- },
|
|
|
- {
|
|
|
- _id: ObjectId(),
|
|
|
- lines: ['elephant', 'swede', 'nectarine'],
|
|
|
- rev: 6
|
|
|
- }
|
|
|
- ]
|
|
|
+ docJson = JSON.stringify({
|
|
|
+ lines: mongoDocs[0].lines,
|
|
|
+ ranges: mongoDocs[0].ranges,
|
|
|
+ schema_v: 1
|
|
|
+ })
|
|
|
|
|
|
- this.mixedDocs = this.archivedDocs.concat(this.unarchivedDocs)
|
|
|
+ stream = {
|
|
|
+ on: sinon.stub(),
|
|
|
+ resume: sinon.stub()
|
|
|
+ }
|
|
|
+ stream.on.withArgs('data').yields(Buffer.from(docJson, 'utf8'))
|
|
|
+ stream.on.withArgs('end').yields()
|
|
|
+
|
|
|
+ readStream = {
|
|
|
+ stream: 'readStream'
|
|
|
+ }
|
|
|
|
|
|
- this.MongoManager = {
|
|
|
- markDocAsArchived: sinon.stub().callsArgWith(2, null),
|
|
|
- upsertIntoDocCollection: sinon.stub().callsArgWith(3, null),
|
|
|
- getProjectsDocs: sinon.stub().callsArgWith(3, null, this.mongoDocs),
|
|
|
- getArchivedProjectDocs: sinon.stub().callsArgWith(2, null, this.mongoDocs)
|
|
|
+ PersistorManager = {
|
|
|
+ getObjectStream: sinon.stub().resolves(stream),
|
|
|
+ sendStream: sinon.stub().resolves(),
|
|
|
+ getObjectMd5Hash: sinon.stub().resolves(md5Sum),
|
|
|
+ deleteObject: sinon.stub().resolves()
|
|
|
}
|
|
|
|
|
|
- this.requires = {
|
|
|
- 'settings-sharelatex': this.settings,
|
|
|
- './MongoManager': this.MongoManager,
|
|
|
- request: this.request,
|
|
|
- './RangeManager': (this.RangeManager = {}),
|
|
|
- 'logger-sharelatex': {
|
|
|
- log() {},
|
|
|
- err() {}
|
|
|
+ MongoManager = {
|
|
|
+ promises: {
|
|
|
+ markDocAsArchived: sinon.stub().resolves(),
|
|
|
+ upsertIntoDocCollection: sinon.stub().resolves(),
|
|
|
+ getProjectsDocs: sinon.stub().resolves(mongoDocs),
|
|
|
+ getArchivedProjectDocs: sinon.stub().resolves(archivedDocs),
|
|
|
+ findDoc: sinon.stub().resolves(),
|
|
|
+ destroyDoc: sinon.stub().resolves()
|
|
|
}
|
|
|
}
|
|
|
- this.globals = { JSON }
|
|
|
-
|
|
|
- this.error = 'my errror'
|
|
|
- this.project_id = ObjectId().toString()
|
|
|
- this.stubbedError = new Errors.NotFoundError('Error in S3 request')
|
|
|
- return (this.DocArchiveManager = SandboxedModule.require(modulePath, {
|
|
|
- requires: this.requires,
|
|
|
- globals: this.globals
|
|
|
- }))
|
|
|
+ for (const mongoDoc of mongoDocs) {
|
|
|
+ MongoManager.promises.findDoc
|
|
|
+ .withArgs(projectId, mongoDoc._id)
|
|
|
+ .resolves(mongoDoc)
|
|
|
+ }
|
|
|
+
|
|
|
+ DocArchiveManager = SandboxedModule.require(modulePath, {
|
|
|
+ requires: {
|
|
|
+ 'settings-sharelatex': Settings,
|
|
|
+ 'logger-sharelatex': Logger,
|
|
|
+ crypto: Crypto,
|
|
|
+ streamifier: Streamifier,
|
|
|
+ './MongoManager': MongoManager,
|
|
|
+ './RangeManager': RangeManager,
|
|
|
+ './PersistorManager': PersistorManager,
|
|
|
+ './Errors': Errors
|
|
|
+ },
|
|
|
+ globals: {
|
|
|
+ console,
|
|
|
+ JSON
|
|
|
+ }
|
|
|
+ })
|
|
|
})
|
|
|
|
|
|
describe('archiveDoc', function () {
|
|
|
- it('should use correct options', function (done) {
|
|
|
- this.request.put = sinon
|
|
|
- .stub()
|
|
|
- .callsArgWith(1, null, { statusCode: 200, headers: { etag: '' } })
|
|
|
- return this.DocArchiveManager.archiveDoc(
|
|
|
- this.project_id,
|
|
|
- this.mongoDocs[0],
|
|
|
- (err) => {
|
|
|
- const opts = this.request.put.args[0][0]
|
|
|
- assert.deepEqual(opts.aws, {
|
|
|
- key: this.settings.docstore.s3.key,
|
|
|
- secret: this.settings.docstore.s3.secret,
|
|
|
- bucket: this.settings.docstore.s3.bucket
|
|
|
- })
|
|
|
- opts.body.should.equal(
|
|
|
- JSON.stringify({
|
|
|
- lines: this.mongoDocs[0].lines,
|
|
|
- ranges: this.mongoDocs[0].ranges,
|
|
|
- schema_v: 1
|
|
|
- })
|
|
|
- )
|
|
|
- opts.timeout.should.equal(30 * 1000)
|
|
|
- opts.uri.should.equal(
|
|
|
- `https://${this.settings.docstore.s3.bucket}.s3.amazonaws.com/${this.project_id}/${this.mongoDocs[0]._id}`
|
|
|
- )
|
|
|
- return done()
|
|
|
- }
|
|
|
+ it('should resolve when passed a valid document', async function () {
|
|
|
+ await expect(
|
|
|
+ DocArchiveManager.promises.archiveDoc(projectId, mongoDocs[0])
|
|
|
+ ).to.eventually.be.fulfilled
|
|
|
+ })
|
|
|
+
|
|
|
+ it('should throw an error if the doc has no lines', async function () {
|
|
|
+ const doc = mongoDocs[0]
|
|
|
+ doc.lines = null
|
|
|
+
|
|
|
+ await expect(
|
|
|
+ DocArchiveManager.promises.archiveDoc(projectId, doc)
|
|
|
+ ).to.eventually.be.rejectedWith('doc has no lines')
|
|
|
+ })
|
|
|
+
|
|
|
+ it('should add the schema version', async function () {
|
|
|
+ await DocArchiveManager.promises.archiveDoc(projectId, mongoDocs[1])
|
|
|
+ expect(Streamifier.createReadStream).to.have.been.calledWith(
|
|
|
+ sinon.match(/"schema_v":1/)
|
|
|
)
|
|
|
})
|
|
|
|
|
|
- it('should return no md5 error', function (done) {
|
|
|
- const data = JSON.stringify({
|
|
|
- lines: this.mongoDocs[0].lines,
|
|
|
- ranges: this.mongoDocs[0].ranges,
|
|
|
+ it('should calculate the hex md5 sum of the content', async function () {
|
|
|
+ const json = JSON.stringify({
|
|
|
+ lines: mongoDocs[0].lines,
|
|
|
+ ranges: mongoDocs[0].ranges,
|
|
|
schema_v: 1
|
|
|
})
|
|
|
- this.md5 = crypto.createHash('md5').update(data).digest('hex')
|
|
|
- this.request.put = sinon
|
|
|
- .stub()
|
|
|
- .callsArgWith(1, null, { statusCode: 200, headers: { etag: this.md5 } })
|
|
|
- return this.DocArchiveManager.archiveDoc(
|
|
|
- this.project_id,
|
|
|
- this.mongoDocs[0],
|
|
|
- (err) => {
|
|
|
- should.not.exist(err)
|
|
|
- return done()
|
|
|
- }
|
|
|
- )
|
|
|
+
|
|
|
+ await DocArchiveManager.promises.archiveDoc(projectId, mongoDocs[0])
|
|
|
+
|
|
|
+ expect(Crypto.createHash).to.have.been.calledWith('md5')
|
|
|
+ expect(HashUpdate).to.have.been.calledWith(json)
|
|
|
+ expect(HashDigest).to.have.been.calledWith('hex')
|
|
|
})
|
|
|
|
|
|
- return it('should return the error', function (done) {
|
|
|
- this.request.put = sinon.stub().callsArgWith(1, this.stubbedError, {
|
|
|
- statusCode: 400,
|
|
|
- headers: { etag: '' }
|
|
|
- })
|
|
|
- return this.DocArchiveManager.archiveDoc(
|
|
|
- this.project_id,
|
|
|
- this.mongoDocs[0],
|
|
|
- (err) => {
|
|
|
- should.exist(err)
|
|
|
- return done()
|
|
|
- }
|
|
|
+ it('should pass the md5 hash to the object persistor for verification', async function () {
|
|
|
+ await DocArchiveManager.promises.archiveDoc(projectId, mongoDocs[0])
|
|
|
+
|
|
|
+ expect(PersistorManager.sendStream).to.have.been.calledWith(
|
|
|
+ sinon.match.any,
|
|
|
+ sinon.match.any,
|
|
|
+ sinon.match.any,
|
|
|
+ { sourceMd5: md5Sum }
|
|
|
)
|
|
|
})
|
|
|
- })
|
|
|
|
|
|
- describe('unarchiveDoc', function () {
|
|
|
- it('should use correct options', function (done) {
|
|
|
- this.request.get = sinon
|
|
|
- .stub()
|
|
|
- .callsArgWith(1, null, { statusCode: 200 }, this.mongoDocs[0].lines)
|
|
|
- this.request.del = sinon
|
|
|
- .stub()
|
|
|
- .callsArgWith(1, null, { statusCode: 204 }, {})
|
|
|
- return this.DocArchiveManager.unarchiveDoc(
|
|
|
- this.project_id,
|
|
|
- this.mongoDocs[0]._id,
|
|
|
- (err) => {
|
|
|
- const opts = this.request.get.args[0][0]
|
|
|
- assert.deepEqual(opts.aws, {
|
|
|
- key: this.settings.docstore.s3.key,
|
|
|
- secret: this.settings.docstore.s3.secret,
|
|
|
- bucket: this.settings.docstore.s3.bucket
|
|
|
- })
|
|
|
- opts.json.should.equal(true)
|
|
|
- opts.timeout.should.equal(30 * 1000)
|
|
|
- opts.uri.should.equal(
|
|
|
- `https://${this.settings.docstore.s3.bucket}.s3.amazonaws.com/${this.project_id}/${this.mongoDocs[0]._id}`
|
|
|
- )
|
|
|
- return done()
|
|
|
- }
|
|
|
+ it('should pass the correct bucket and key to the persistor', async function () {
|
|
|
+ await DocArchiveManager.promises.archiveDoc(projectId, mongoDocs[0])
|
|
|
+
|
|
|
+ expect(PersistorManager.sendStream).to.have.been.calledWith(
|
|
|
+ Settings.docstore.bucket,
|
|
|
+ `${projectId}/${mongoDocs[0]._id}`
|
|
|
)
|
|
|
})
|
|
|
|
|
|
- it('should return the error', function (done) {
|
|
|
- this.request.get = sinon.stub().callsArgWith(1, this.stubbedError, {}, {})
|
|
|
- return this.DocArchiveManager.unarchiveDoc(
|
|
|
- this.project_id,
|
|
|
- this.mongoDocs[0],
|
|
|
- (err) => {
|
|
|
- should.exist(err)
|
|
|
- return done()
|
|
|
- }
|
|
|
+ it('should create a stream from the encoded json and send it', async function () {
|
|
|
+ await DocArchiveManager.promises.archiveDoc(projectId, mongoDocs[0])
|
|
|
+ expect(Streamifier.createReadStream).to.have.been.calledWith(docJson)
|
|
|
+ expect(PersistorManager.sendStream).to.have.been.calledWith(
|
|
|
+ sinon.match.any,
|
|
|
+ sinon.match.any,
|
|
|
+ readStream
|
|
|
)
|
|
|
})
|
|
|
|
|
|
- return it('should error if the doc lines are a string not an array', function (done) {
|
|
|
- this.request.get = sinon
|
|
|
- .stub()
|
|
|
- .callsArgWith(1, null, { statusCode: 200 }, 'this is a string')
|
|
|
- this.request.del = sinon.stub()
|
|
|
- return this.DocArchiveManager.unarchiveDoc(
|
|
|
- this.project_id,
|
|
|
- this.mongoDocs[0],
|
|
|
- (err) => {
|
|
|
- should.exist(err)
|
|
|
- this.request.del.called.should.equal(false)
|
|
|
- return done()
|
|
|
- }
|
|
|
+ it('should mark the doc as archived', async function () {
|
|
|
+ await DocArchiveManager.promises.archiveDoc(projectId, mongoDocs[0])
|
|
|
+ expect(MongoManager.promises.markDocAsArchived).to.have.been.calledWith(
|
|
|
+ mongoDocs[0]._id,
|
|
|
+ mongoDocs[0].rev
|
|
|
)
|
|
|
})
|
|
|
- })
|
|
|
|
|
|
- describe('archiveAllDocs', function () {
|
|
|
- it('should archive all project docs which are not in s3', function (done) {
|
|
|
- this.MongoManager.getProjectsDocs = sinon
|
|
|
- .stub()
|
|
|
- .callsArgWith(3, null, this.mongoDocs)
|
|
|
- this.DocArchiveManager.archiveDoc = sinon.stub().callsArgWith(2, null)
|
|
|
-
|
|
|
- return this.DocArchiveManager.archiveAllDocs(this.project_id, (err) => {
|
|
|
- this.DocArchiveManager.archiveDoc
|
|
|
- .calledWith(this.project_id, this.mongoDocs[0])
|
|
|
- .should.equal(true)
|
|
|
- this.DocArchiveManager.archiveDoc
|
|
|
- .calledWith(this.project_id, this.mongoDocs[1])
|
|
|
- .should.equal(true)
|
|
|
- this.DocArchiveManager.archiveDoc
|
|
|
- .calledWith(this.project_id, this.mongoDocs[4])
|
|
|
- .should.equal(true)
|
|
|
-
|
|
|
- this.DocArchiveManager.archiveDoc
|
|
|
- .calledWith(this.project_id, this.mongoDocs[2])
|
|
|
- .should.equal(false)
|
|
|
- this.DocArchiveManager.archiveDoc
|
|
|
- .calledWith(this.project_id, this.mongoDocs[3])
|
|
|
- .should.equal(false)
|
|
|
-
|
|
|
- should.not.exist(err)
|
|
|
- return done()
|
|
|
+ describe('with null bytes in the result', function () {
|
|
|
+ const _stringify = JSON.stringify
|
|
|
+
|
|
|
+ beforeEach(function () {
|
|
|
+ JSON.stringify = sinon.stub().returns('{"bad": "\u0000"}')
|
|
|
})
|
|
|
- })
|
|
|
|
|
|
- it('should return error if have no docs', function (done) {
|
|
|
- this.MongoManager.getProjectsDocs = sinon
|
|
|
- .stub()
|
|
|
- .callsArgWith(3, null, null)
|
|
|
+ afterEach(function () {
|
|
|
+ JSON.stringify = _stringify
|
|
|
+ })
|
|
|
|
|
|
- return this.DocArchiveManager.archiveAllDocs(this.project_id, (err) => {
|
|
|
- should.exist(err)
|
|
|
- return done()
|
|
|
+ it('should return an error', async function () {
|
|
|
+ await expect(
|
|
|
+ DocArchiveManager.promises.archiveDoc(projectId, mongoDocs[0])
|
|
|
+ ).to.eventually.be.rejectedWith('null bytes detected')
|
|
|
})
|
|
|
})
|
|
|
+ })
|
|
|
|
|
|
- it('should return the error', function (done) {
|
|
|
- this.MongoManager.getProjectsDocs = sinon
|
|
|
- .stub()
|
|
|
- .callsArgWith(3, this.error, null)
|
|
|
+ describe('unarchiveDoc', function () {
|
|
|
+ let docId
|
|
|
|
|
|
- return this.DocArchiveManager.archiveAllDocs(this.project_id, (err) => {
|
|
|
- err.should.equal(this.error)
|
|
|
- return done()
|
|
|
- })
|
|
|
+ beforeEach(function () {
|
|
|
+ docId = mongoDocs[0]._id
|
|
|
})
|
|
|
|
|
|
- return describe('when most have been already put in s3', function () {
|
|
|
- beforeEach(function () {
|
|
|
- let numberOfDocs = 10 * 1000
|
|
|
- this.mongoDocs = []
|
|
|
- while (--numberOfDocs !== 0) {
|
|
|
- this.mongoDocs.push({ inS3: true, _id: ObjectId() })
|
|
|
- }
|
|
|
-
|
|
|
- this.MongoManager.getProjectsDocs = sinon
|
|
|
- .stub()
|
|
|
- .callsArgWith(3, null, this.mongoDocs)
|
|
|
- return (this.DocArchiveManager.archiveDoc = sinon
|
|
|
- .stub()
|
|
|
- .callsArgWith(2, null))
|
|
|
- })
|
|
|
+ it('should resolve when passed a valid document', async function () {
|
|
|
+ await expect(DocArchiveManager.promises.unarchiveDoc(projectId, docId)).to
|
|
|
+ .eventually.be.fulfilled
|
|
|
+ })
|
|
|
+
|
|
|
+ it('should throw an error if the md5 does not match', async function () {
|
|
|
+ PersistorManager.getObjectMd5Hash.resolves('badf00d')
|
|
|
+ await expect(
|
|
|
+ DocArchiveManager.promises.unarchiveDoc(projectId, docId)
|
|
|
+ ).to.eventually.be.rejected.and.be.instanceof(Errors.Md5MismatchError)
|
|
|
+ })
|
|
|
+
|
|
|
+ it('should update the doc lines in mongo', async function () {
|
|
|
+ await DocArchiveManager.promises.unarchiveDoc(projectId, docId)
|
|
|
+ expect(
|
|
|
+ MongoManager.promises.upsertIntoDocCollection
|
|
|
+ ).to.have.been.calledWith(projectId, docId, { lines: mongoDocs[0].lines })
|
|
|
+ })
|
|
|
+
|
|
|
+ it('should delete the doc in s3', async function () {
|
|
|
+ await DocArchiveManager.promises.unarchiveDoc(projectId, docId)
|
|
|
+ expect(PersistorManager.deleteObject).to.have.been.calledWith(
|
|
|
+ Settings.docstore.bucket,
|
|
|
+ `${projectId}/${docId}`
|
|
|
+ )
|
|
|
+ })
|
|
|
+
|
|
|
+ describe('doc contents', function () {
|
|
|
+ let mongoDoc, s3Doc
|
|
|
|
|
|
- return it('should not throw and error', function (done) {
|
|
|
- return this.DocArchiveManager.archiveAllDocs(this.project_id, (err) => {
|
|
|
- should.not.exist(err)
|
|
|
- return done()
|
|
|
+ describe('when the doc has the old schema', function () {
|
|
|
+ beforeEach(function () {
|
|
|
+ mongoDoc = {
|
|
|
+ lines: ['doc', 'lines']
|
|
|
+ }
|
|
|
+ s3Doc = ['doc', 'lines']
|
|
|
+ docJson = JSON.stringify(s3Doc)
|
|
|
+ stream.on.withArgs('data').yields(Buffer.from(docJson, 'utf8'))
|
|
|
+ })
|
|
|
+
|
|
|
+ it('should return the docs lines', async function () {
|
|
|
+ await DocArchiveManager.promises.unarchiveDoc(projectId, docId)
|
|
|
+ expect(
|
|
|
+ MongoManager.promises.upsertIntoDocCollection
|
|
|
+ ).to.have.been.calledWith(projectId, docId, mongoDoc)
|
|
|
})
|
|
|
})
|
|
|
- })
|
|
|
- })
|
|
|
|
|
|
- describe('unArchiveAllDocs', function () {
|
|
|
- it('should unarchive all inS3 docs', function (done) {
|
|
|
- this.MongoManager.getArchivedProjectDocs = sinon
|
|
|
- .stub()
|
|
|
- .callsArgWith(1, null, this.archivedDocs)
|
|
|
- this.DocArchiveManager.unarchiveDoc = sinon.stub().callsArgWith(2, null)
|
|
|
- return this.DocArchiveManager.unArchiveAllDocs(this.project_id, (err) => {
|
|
|
- for (const doc of Array.from(this.archivedDocs)) {
|
|
|
- this.DocArchiveManager.unarchiveDoc
|
|
|
- .calledWith(this.project_id, doc._id)
|
|
|
- .should.equal(true)
|
|
|
- }
|
|
|
- should.not.exist(err)
|
|
|
- return done()
|
|
|
+ describe('with the new schema and ranges', function () {
|
|
|
+ beforeEach(function () {
|
|
|
+ s3Doc = {
|
|
|
+ lines: ['doc', 'lines'],
|
|
|
+ ranges: { json: 'ranges' },
|
|
|
+ schema_v: 1
|
|
|
+ }
|
|
|
+ mongoDoc = {
|
|
|
+ lines: ['doc', 'lines'],
|
|
|
+ ranges: { mongo: 'ranges' }
|
|
|
+ }
|
|
|
+ docJson = JSON.stringify(s3Doc)
|
|
|
+ stream.on.withArgs('data').yields(Buffer.from(docJson, 'utf8'))
|
|
|
+ })
|
|
|
+
|
|
|
+ it('should return the doc lines and ranges', async function () {
|
|
|
+ await DocArchiveManager.promises.unarchiveDoc(projectId, docId)
|
|
|
+ expect(
|
|
|
+ MongoManager.promises.upsertIntoDocCollection
|
|
|
+ ).to.have.been.calledWith(projectId, docId, mongoDoc)
|
|
|
+ })
|
|
|
})
|
|
|
- })
|
|
|
|
|
|
- it('should return error if have no docs', function (done) {
|
|
|
- this.MongoManager.getArchivedProjectDocs = sinon
|
|
|
- .stub()
|
|
|
- .callsArgWith(1, null, null)
|
|
|
- return this.DocArchiveManager.unArchiveAllDocs(this.project_id, (err) => {
|
|
|
- should.exist(err)
|
|
|
- return done()
|
|
|
+ describe('with the new schema and no ranges', function () {
|
|
|
+ beforeEach(function () {
|
|
|
+ s3Doc = {
|
|
|
+ lines: ['doc', 'lines'],
|
|
|
+ schema_v: 1
|
|
|
+ }
|
|
|
+ mongoDoc = {
|
|
|
+ lines: ['doc', 'lines']
|
|
|
+ }
|
|
|
+ docJson = JSON.stringify(s3Doc)
|
|
|
+ stream.on.withArgs('data').yields(Buffer.from(docJson, 'utf8'))
|
|
|
+ })
|
|
|
+
|
|
|
+ it('should return only the doc lines', async function () {
|
|
|
+ await DocArchiveManager.promises.unarchiveDoc(projectId, docId)
|
|
|
+ expect(
|
|
|
+ MongoManager.promises.upsertIntoDocCollection
|
|
|
+ ).to.have.been.calledWith(projectId, docId, mongoDoc)
|
|
|
+ })
|
|
|
})
|
|
|
- })
|
|
|
|
|
|
- return it('should return the error', function (done) {
|
|
|
- this.MongoManager.getArchivedProjectDocs = sinon
|
|
|
- .stub()
|
|
|
- .callsArgWith(1, this.error, null)
|
|
|
- return this.DocArchiveManager.unArchiveAllDocs(this.project_id, (err) => {
|
|
|
- err.should.equal(this.error)
|
|
|
- return done()
|
|
|
+ describe('with an unrecognised schema', function () {
|
|
|
+ beforeEach(function () {
|
|
|
+ s3Doc = {
|
|
|
+ lines: ['doc', 'lines'],
|
|
|
+ schema_v: 2
|
|
|
+ }
|
|
|
+ docJson = JSON.stringify(s3Doc)
|
|
|
+ stream.on.withArgs('data').yields(Buffer.from(docJson, 'utf8'))
|
|
|
+ })
|
|
|
+
|
|
|
+ it('should throw an error', async function () {
|
|
|
+ await expect(
|
|
|
+ DocArchiveManager.promises.unarchiveDoc(projectId, docId)
|
|
|
+ ).to.eventually.be.rejectedWith(
|
|
|
+ "I don't understand the doc format in s3"
|
|
|
+ )
|
|
|
+ })
|
|
|
})
|
|
|
})
|
|
|
})
|
|
|
|
|
|
- describe('destroyAllDocs', function () {
|
|
|
+ describe('destroyDoc', function () {
|
|
|
+ let docId
|
|
|
+
|
|
|
beforeEach(function () {
|
|
|
- this.request.del = sinon
|
|
|
- .stub()
|
|
|
- .callsArgWith(1, null, { statusCode: 204 }, {})
|
|
|
- this.MongoManager.getProjectsDocs = sinon
|
|
|
- .stub()
|
|
|
- .callsArgWith(3, null, this.mixedDocs)
|
|
|
- this.MongoManager.findDoc = sinon.stub().callsArgWith(3, null, null)
|
|
|
- this.MongoManager.destroyDoc = sinon.stub().yields()
|
|
|
- return Array.from(this.mixedDocs).map((doc) =>
|
|
|
- this.MongoManager.findDoc
|
|
|
- .withArgs(this.project_id, doc._id)
|
|
|
- .callsArgWith(3, null, doc)
|
|
|
- )
|
|
|
+ docId = mongoDocs[0]._id
|
|
|
})
|
|
|
|
|
|
- it('should destroy all the docs', function (done) {
|
|
|
- this.DocArchiveManager.destroyDoc = sinon.stub().callsArgWith(2, null)
|
|
|
- return this.DocArchiveManager.destroyAllDocs(this.project_id, (err) => {
|
|
|
- for (const doc of Array.from(this.mixedDocs)) {
|
|
|
- this.DocArchiveManager.destroyDoc
|
|
|
- .calledWith(this.project_id, doc._id)
|
|
|
- .should.equal(true)
|
|
|
- }
|
|
|
- should.not.exist(err)
|
|
|
- return done()
|
|
|
- })
|
|
|
+ it('should resolve when passed a valid document', async function () {
|
|
|
+ await expect(DocArchiveManager.promises.destroyDoc(projectId, docId)).to
|
|
|
+ .eventually.be.fulfilled
|
|
|
})
|
|
|
|
|
|
- it('should only the s3 docs from s3', function (done) {
|
|
|
- const docOpts = (doc) => {
|
|
|
- return JSON.parse(
|
|
|
- JSON.stringify({
|
|
|
- aws: {
|
|
|
- key: this.settings.docstore.s3.key,
|
|
|
- secret: this.settings.docstore.s3.secret,
|
|
|
- bucket: this.settings.docstore.s3.bucket
|
|
|
- },
|
|
|
- json: true,
|
|
|
- timeout: 30 * 1000,
|
|
|
- uri: `https://${this.settings.docstore.s3.bucket}.s3.amazonaws.com/${this.project_id}/${doc._id}`
|
|
|
- })
|
|
|
- )
|
|
|
- }
|
|
|
+ it('should throw a not found error when there is no document', async function () {
|
|
|
+ await expect(
|
|
|
+ DocArchiveManager.promises.destroyDoc(projectId, 'wombat')
|
|
|
+ ).to.eventually.be.rejected.and.be.instanceof(Errors.NotFoundError)
|
|
|
+ })
|
|
|
|
|
|
- return this.DocArchiveManager.destroyAllDocs(this.project_id, (err) => {
|
|
|
- let doc
|
|
|
- expect(err).not.to.exist
|
|
|
+ describe('when the doc is in s3', function () {
|
|
|
+ beforeEach(function () {
|
|
|
+ mongoDocs[0].inS3 = true
|
|
|
+ })
|
|
|
|
|
|
- for (doc of Array.from(this.archivedDocs)) {
|
|
|
- sinon.assert.calledWith(this.request.del, docOpts(doc))
|
|
|
- }
|
|
|
- for (doc of Array.from(this.unarchivedDocs)) {
|
|
|
- expect(this.request.del.calledWith(docOpts(doc))).to.equal(false)
|
|
|
- } // no notCalledWith
|
|
|
+ it('should delete the document from s3, if it is in s3', async function () {
|
|
|
+ await DocArchiveManager.promises.destroyDoc(projectId, docId)
|
|
|
+ expect(PersistorManager.deleteObject).to.have.been.calledWith(
|
|
|
+ Settings.docstore.bucket,
|
|
|
+ `${projectId}/${docId}`
|
|
|
+ )
|
|
|
+ })
|
|
|
|
|
|
- return done()
|
|
|
+ it('should delete the doc in mongo', async function () {
|
|
|
+ await DocArchiveManager.promises.destroyDoc(projectId, docId)
|
|
|
})
|
|
|
})
|
|
|
|
|
|
- return it('should remove the docs from mongo', function (done) {
|
|
|
- this.DocArchiveManager.destroyAllDocs(this.project_id, (err) => {
|
|
|
- return expect(err).not.to.exist
|
|
|
+ describe('when the doc is not in s3', function () {
|
|
|
+ beforeEach(function () {
|
|
|
+ mongoDocs[0].inS3 = false
|
|
|
})
|
|
|
|
|
|
- for (const doc of Array.from(this.mixedDocs)) {
|
|
|
- sinon.assert.calledWith(this.MongoManager.destroyDoc, doc._id)
|
|
|
- }
|
|
|
+ it('should not delete the document from s3, if it is not in s3', async function () {
|
|
|
+ await DocArchiveManager.promises.destroyDoc(projectId, docId)
|
|
|
+ expect(PersistorManager.deleteObject).not.to.have.been.called
|
|
|
+ })
|
|
|
|
|
|
- return done()
|
|
|
+ it('should delete the doc in mongo', async function () {
|
|
|
+ await DocArchiveManager.promises.destroyDoc(projectId, docId)
|
|
|
+ })
|
|
|
})
|
|
|
})
|
|
|
|
|
|
- describe('_s3DocToMongoDoc', function () {
|
|
|
- describe('with the old schema', function () {
|
|
|
- return it('should return the docs lines', function (done) {
|
|
|
- return this.DocArchiveManager._s3DocToMongoDoc(
|
|
|
- ['doc', 'lines'],
|
|
|
- (error, doc) => {
|
|
|
- expect(doc).to.deep.equal({
|
|
|
- lines: ['doc', 'lines']
|
|
|
- })
|
|
|
- return done()
|
|
|
- }
|
|
|
- )
|
|
|
- })
|
|
|
+ describe('archiveAllDocs', function () {
|
|
|
+ it('should resolve with valid arguments', async function () {
|
|
|
+ await expect(DocArchiveManager.promises.archiveAllDocs(projectId)).to
|
|
|
+ .eventually.be.fulfilled
|
|
|
})
|
|
|
|
|
|
- describe('with the new schema', function () {
|
|
|
- it('should return the doc lines and ranges', function (done) {
|
|
|
- this.RangeManager.jsonRangesToMongo = sinon
|
|
|
- .stub()
|
|
|
- .returns({ mongo: 'ranges' })
|
|
|
- return this.DocArchiveManager._s3DocToMongoDoc(
|
|
|
- {
|
|
|
- lines: ['doc', 'lines'],
|
|
|
- ranges: { json: 'ranges' },
|
|
|
- schema_v: 1
|
|
|
- },
|
|
|
- (error, doc) => {
|
|
|
- expect(doc).to.deep.equal({
|
|
|
- lines: ['doc', 'lines'],
|
|
|
- ranges: { mongo: 'ranges' }
|
|
|
- })
|
|
|
- return done()
|
|
|
- }
|
|
|
- )
|
|
|
- })
|
|
|
+ it('should archive all project docs which are not in s3', async function () {
|
|
|
+ await DocArchiveManager.promises.archiveAllDocs(projectId)
|
|
|
+ // not inS3
|
|
|
+ expect(MongoManager.promises.markDocAsArchived).to.have.been.calledWith(
|
|
|
+ mongoDocs[0]._id
|
|
|
+ )
|
|
|
+ expect(MongoManager.promises.markDocAsArchived).to.have.been.calledWith(
|
|
|
+ mongoDocs[1]._id
|
|
|
+ )
|
|
|
+ expect(MongoManager.promises.markDocAsArchived).to.have.been.calledWith(
|
|
|
+ mongoDocs[4]._id
|
|
|
+ )
|
|
|
|
|
|
- return it('should return just the doc lines when there are no ranges', function (done) {
|
|
|
- return this.DocArchiveManager._s3DocToMongoDoc(
|
|
|
- {
|
|
|
- lines: ['doc', 'lines'],
|
|
|
- schema_v: 1
|
|
|
- },
|
|
|
- (error, doc) => {
|
|
|
- expect(doc).to.deep.equal({
|
|
|
- lines: ['doc', 'lines']
|
|
|
- })
|
|
|
- return done()
|
|
|
- }
|
|
|
- )
|
|
|
- })
|
|
|
+ // inS3
|
|
|
+ expect(
|
|
|
+ MongoManager.promises.markDocAsArchived
|
|
|
+ ).not.to.have.been.calledWith(mongoDocs[2]._id)
|
|
|
+ expect(
|
|
|
+ MongoManager.promises.markDocAsArchived
|
|
|
+ ).not.to.have.been.calledWith(mongoDocs[3]._id)
|
|
|
})
|
|
|
|
|
|
- return describe('with an unrecognised schema', function () {
|
|
|
- return it('should return an error', function (done) {
|
|
|
- return this.DocArchiveManager._s3DocToMongoDoc(
|
|
|
- {
|
|
|
- schema_v: 2
|
|
|
- },
|
|
|
- (error, doc) => {
|
|
|
- expect(error).to.exist
|
|
|
- return done()
|
|
|
- }
|
|
|
- )
|
|
|
- })
|
|
|
+ it('should return error if the project has no docs', async function () {
|
|
|
+ MongoManager.promises.getProjectsDocs.resolves(null)
|
|
|
+
|
|
|
+ await expect(
|
|
|
+ DocArchiveManager.promises.archiveAllDocs(projectId)
|
|
|
+ ).to.eventually.be.rejected.and.be.instanceof(Errors.NotFoundError)
|
|
|
})
|
|
|
})
|
|
|
|
|
|
- return describe('_mongoDocToS3Doc', function () {
|
|
|
- describe('with a valid doc', function () {
|
|
|
- return it('should return the json version', function (done) {
|
|
|
- let doc
|
|
|
- return this.DocArchiveManager._mongoDocToS3Doc(
|
|
|
- (doc = {
|
|
|
- lines: ['doc', 'lines'],
|
|
|
- ranges: { mock: 'ranges' }
|
|
|
- }),
|
|
|
- (err, s3_doc) => {
|
|
|
- expect(s3_doc).to.equal(
|
|
|
- JSON.stringify({
|
|
|
- lines: ['doc', 'lines'],
|
|
|
- ranges: { mock: 'ranges' },
|
|
|
- schema_v: 1
|
|
|
- })
|
|
|
- )
|
|
|
- return done()
|
|
|
- }
|
|
|
+ describe('unArchiveAllDocs', function () {
|
|
|
+ it('should resolve with valid arguments', async function () {
|
|
|
+ await expect(DocArchiveManager.promises.unArchiveAllDocs(projectId)).to
|
|
|
+ .eventually.be.fulfilled
|
|
|
+ })
|
|
|
+
|
|
|
+ it('should unarchive all inS3 docs', async function () {
|
|
|
+ await DocArchiveManager.promises.unArchiveAllDocs(projectId)
|
|
|
+
|
|
|
+ for (const doc of archivedDocs) {
|
|
|
+ expect(PersistorManager.getObjectStream).to.have.been.calledWith(
|
|
|
+ Settings.docstore.bucket,
|
|
|
+ `${projectId}/${doc._id}`
|
|
|
)
|
|
|
- })
|
|
|
+ }
|
|
|
})
|
|
|
|
|
|
- describe('with null bytes in the result', function () {
|
|
|
- beforeEach(function () {
|
|
|
- this._stringify = JSON.stringify
|
|
|
- return (JSON.stringify = sinon.stub().returns('{"bad": "\u0000"}'))
|
|
|
- })
|
|
|
+ it('should return error if the project has no docs', async function () {
|
|
|
+ MongoManager.promises.getArchivedProjectDocs.resolves(null)
|
|
|
|
|
|
- afterEach(function () {
|
|
|
- return (JSON.stringify = this._stringify)
|
|
|
- })
|
|
|
+ await expect(
|
|
|
+ DocArchiveManager.promises.unArchiveAllDocs(projectId)
|
|
|
+ ).to.eventually.be.rejected.and.be.instanceof(Errors.NotFoundError)
|
|
|
+ })
|
|
|
+ })
|
|
|
|
|
|
- return it('should return an error', function (done) {
|
|
|
- return this.DocArchiveManager._mongoDocToS3Doc(
|
|
|
- {
|
|
|
- lines: ['doc', 'lines'],
|
|
|
- ranges: { mock: 'ranges' }
|
|
|
- },
|
|
|
- (err, s3_doc) => {
|
|
|
- expect(err).to.exist
|
|
|
- return done()
|
|
|
- }
|
|
|
+ describe('destroyAllDocs', function () {
|
|
|
+ it('should resolve with valid arguments', async function () {
|
|
|
+ await expect(DocArchiveManager.promises.destroyAllDocs(projectId)).to
|
|
|
+ .eventually.be.fulfilled
|
|
|
+ })
|
|
|
+
|
|
|
+ it('should delete all docs that are in s3 from s3', async function () {
|
|
|
+ await DocArchiveManager.promises.destroyAllDocs(projectId)
|
|
|
+
|
|
|
+ // not inS3
|
|
|
+ for (const index of [0, 1, 4]) {
|
|
|
+ expect(PersistorManager.deleteObject).not.to.have.been.calledWith(
|
|
|
+ Settings.docstore.bucket,
|
|
|
+ `${projectId}/${mongoDocs[index]._id}`
|
|
|
)
|
|
|
- })
|
|
|
+ }
|
|
|
+
|
|
|
+ // inS3
|
|
|
+ for (const index of [2, 3]) {
|
|
|
+ expect(PersistorManager.deleteObject).to.have.been.calledWith(
|
|
|
+ Settings.docstore.bucket,
|
|
|
+ `${projectId}/${mongoDocs[index]._id}`
|
|
|
+ )
|
|
|
+ }
|
|
|
})
|
|
|
|
|
|
- return describe('without doc lines', function () {
|
|
|
- return it('should return an error', function (done) {
|
|
|
- return this.DocArchiveManager._mongoDocToS3Doc({}, (err, s3_doc) => {
|
|
|
- expect(err).to.exist
|
|
|
- return done()
|
|
|
- })
|
|
|
- })
|
|
|
+ it('should destroy all docs in mongo', async function () {
|
|
|
+ await DocArchiveManager.promises.destroyAllDocs(projectId)
|
|
|
+
|
|
|
+ for (const mongoDoc of mongoDocs) {
|
|
|
+ expect(MongoManager.promises.destroyDoc).to.have.been.calledWith(
|
|
|
+ mongoDoc._id
|
|
|
+ )
|
|
|
+ }
|
|
|
})
|
|
|
})
|
|
|
})
|