overleaf/services/docstore/test/unit/coffee/DocArchiveManagerTests.coffee
2019-07-02 15:02:37 +01:00

356 lines
11 KiB
CoffeeScript

assert = require("chai").assert
sinon = require('sinon')
chai = require('chai')
should = chai.should()
expect = chai.expect
modulePath = "../../../app/js/DocArchiveManager.js"
SandboxedModule = require('sandboxed-module')
ObjectId = require("mongojs").ObjectId
Errors = require "../../../app/js/Errors"
crypto = require("crypto")
describe "DocArchiveManager", ->
beforeEach ->
@settings =
docstore:
s3:
secret: "secret"
key: "this_key"
bucket:"doc-archive-unit-test"
@request =
put: {}
get: {}
del: {}
@archivedDocs = [{
_id: ObjectId()
inS3:true
rev: 2
}, {
_id: ObjectId()
inS3:true
rev: 4
}, {
_id: ObjectId()
inS3:true
rev: 6
}]
@mongoDocs = [{
_id: ObjectId()
lines: ["one", "two", "three"]
rev: 2
}, {
_id: ObjectId()
lines: ["aaa", "bbb", "ccc"]
rev: 4
}, {
_id: ObjectId()
inS3: true
rev: 6
}, {
_id: ObjectId()
inS3: true
rev: 6
}, {
_id: ObjectId()
lines: ["111", "222", "333"]
rev: 6
}]
@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
}]
@mixedDocs = @archivedDocs.concat(@unarchivedDocs)
@MongoManager =
markDocAsArchived: sinon.stub().callsArgWith(2, null)
upsertIntoDocCollection: sinon.stub().callsArgWith(3, null)
getProjectsDocs: sinon.stub().callsArgWith(3, null, @mongoDocs)
getArchivedProjectDocs: sinon.stub().callsArgWith(2, null, @mongoDocs)
@requires =
"settings-sharelatex": @settings
"./MongoManager": @MongoManager
"request": @request
"./RangeManager": @RangeManager = {}
"logger-sharelatex":
log:->
err:->
@globals =
JSON: JSON
@error = "my errror"
@project_id = ObjectId().toString()
@stubbedError = new Errors.NotFoundError("Error in S3 request")
@DocArchiveManager = SandboxedModule.require modulePath, requires: @requires, globals: @globals
describe "archiveDoc", ->
it "should use correct options", (done)->
@request.put = sinon.stub().callsArgWith(1, null, {statusCode:200,headers:{etag:""}})
@DocArchiveManager.archiveDoc @project_id, @mongoDocs[0], (err)=>
opts = @request.put.args[0][0]
assert.deepEqual(opts.aws, {key:@settings.docstore.s3.key, secret:@settings.docstore.s3.secret, bucket:@settings.docstore.s3.bucket})
opts.body.should.equal JSON.stringify(
lines: @mongoDocs[0].lines
ranges: @mongoDocs[0].ranges
schema_v: 1
)
opts.timeout.should.equal (30*1000)
opts.uri.should.equal "https://#{@settings.docstore.s3.bucket}.s3.amazonaws.com/#{@project_id}/#{@mongoDocs[0]._id}"
done()
it "should return no md5 error", (done)->
data = JSON.stringify(
lines: @mongoDocs[0].lines
ranges: @mongoDocs[0].ranges
schema_v: 1
)
@md5 = crypto.createHash("md5").update(data).digest("hex")
@request.put = sinon.stub().callsArgWith(1, null, {statusCode:200,headers:{etag:@md5}})
@DocArchiveManager.archiveDoc @project_id, @mongoDocs[0], (err)=>
should.not.exist err
done()
it "should return the error", (done)->
@request.put = sinon.stub().callsArgWith(1, @stubbedError, {statusCode:400,headers:{etag:""}})
@DocArchiveManager.archiveDoc @project_id, @mongoDocs[0], (err)=>
should.exist err
done()
describe "unarchiveDoc", ->
it "should use correct options", (done)->
@request.get = sinon.stub().callsArgWith(1, null, statusCode:200, @mongoDocs[0].lines)
@request.del = sinon.stub().callsArgWith(1, null, statusCode:204, {})
@DocArchiveManager.unarchiveDoc @project_id, @mongoDocs[0]._id, (err)=>
opts = @request.get.args[0][0]
assert.deepEqual(opts.aws, {key:@settings.docstore.s3.key, secret:@settings.docstore.s3.secret, bucket:@settings.docstore.s3.bucket})
opts.json.should.equal true
opts.timeout.should.equal (30*1000)
opts.uri.should.equal "https://#{@settings.docstore.s3.bucket}.s3.amazonaws.com/#{@project_id}/#{@mongoDocs[0]._id}"
done()
it "should return the error", (done)->
@request.get = sinon.stub().callsArgWith(1, @stubbedError, {}, {})
@DocArchiveManager.unarchiveDoc @project_id, @mongoDocs[0], (err)=>
should.exist err
done()
it "should error if the doc lines are a string not an array", (done)->
@request.get = sinon.stub().callsArgWith(1, null, statusCode:200, "this is a string")
@request.del = sinon.stub()
@DocArchiveManager.unarchiveDoc @project_id, @mongoDocs[0], (err)=>
should.exist err
@request.del.called.should.equal false
done()
describe "archiveAllDocs", ->
it "should archive all project docs which are not in s3", (done)->
@MongoManager.getProjectsDocs = sinon.stub().callsArgWith(3, null, @mongoDocs)
@DocArchiveManager.archiveDoc = sinon.stub().callsArgWith(2, null)
@DocArchiveManager.archiveAllDocs @project_id, (err)=>
@DocArchiveManager.archiveDoc.calledWith(@project_id, @mongoDocs[0]).should.equal true
@DocArchiveManager.archiveDoc.calledWith(@project_id, @mongoDocs[1]).should.equal true
@DocArchiveManager.archiveDoc.calledWith(@project_id, @mongoDocs[4]).should.equal true
@DocArchiveManager.archiveDoc.calledWith(@project_id, @mongoDocs[2]).should.equal false
@DocArchiveManager.archiveDoc.calledWith(@project_id, @mongoDocs[3]).should.equal false
should.not.exist err
done()
it "should return error if have no docs", (done)->
@MongoManager.getProjectsDocs = sinon.stub().callsArgWith(3, null, null)
@DocArchiveManager.archiveAllDocs @project_id, (err)=>
should.exist err
done()
it "should return the error", (done)->
@MongoManager.getProjectsDocs = sinon.stub().callsArgWith(3, @error, null)
@DocArchiveManager.archiveAllDocs @project_id, (err)=>
err.should.equal @error
done()
describe "when most have been already put in s3", ->
beforeEach ->
numberOfDocs = 10 * 1000
@mongoDocs = []
while --numberOfDocs != 0
@mongoDocs.push({inS3:true, _id: ObjectId()})
@MongoManager.getProjectsDocs = sinon.stub().callsArgWith(3, null, @mongoDocs)
@DocArchiveManager.archiveDoc = sinon.stub().callsArgWith(2, null)
it "should not throw and error", (done)->
@DocArchiveManager.archiveAllDocs @project_id, (err)=>
should.not.exist err
done()
describe "unArchiveAllDocs", ->
it "should unarchive all inS3 docs", (done)->
@MongoManager.getArchivedProjectDocs = sinon.stub().callsArgWith(1, null, @archivedDocs)
@DocArchiveManager.unarchiveDoc = sinon.stub().callsArgWith(2, null)
@DocArchiveManager.unArchiveAllDocs @project_id, (err)=>
for doc in @archivedDocs
@DocArchiveManager.unarchiveDoc.calledWith(@project_id, doc._id).should.equal true
should.not.exist err
done()
it "should return error if have no docs", (done)->
@MongoManager.getArchivedProjectDocs = sinon.stub().callsArgWith(1, null, null)
@DocArchiveManager.unArchiveAllDocs @project_id, (err)=>
should.exist err
done()
it "should return the error", (done)->
@MongoManager.getArchivedProjectDocs = sinon.stub().callsArgWith(1, @error, null)
@DocArchiveManager.unArchiveAllDocs @project_id, (err)=>
err.should.equal @error
done()
describe "destroyAllDocs", ->
beforeEach ->
@request.del = sinon.stub().callsArgWith(1, null, statusCode:204, {})
@MongoManager.getProjectsDocs = sinon.stub().callsArgWith(3, null, @mixedDocs)
@MongoManager.findDoc = sinon.stub().callsArgWith(3, null, null)
@MongoManager.destroyDoc = sinon.stub().yields()
for doc in @mixedDocs
@MongoManager.findDoc.withArgs(@project_id, doc._id).callsArgWith(3, null, doc)
it "should destroy all the docs", (done)->
@DocArchiveManager.destroyDoc = sinon.stub().callsArgWith(2, null)
@DocArchiveManager.destroyAllDocs @project_id, (err)=>
for doc in @mixedDocs
@DocArchiveManager.destroyDoc.calledWith(@project_id, doc._id).should.equal true
should.not.exist err
done()
it "should only the s3 docs from s3", (done)->
docOpts = (doc) =>
JSON.parse(JSON.stringify({
aws: {key:@settings.docstore.s3.key, secret:@settings.docstore.s3.secret, bucket:@settings.docstore.s3.bucket},
json: true,
timeout: 30 * 1000
uri:"https://#{@settings.docstore.s3.bucket}.s3.amazonaws.com/#{@project_id}/#{doc._id}"
}))
@DocArchiveManager.destroyAllDocs @project_id, (err)=>
expect(err).not.to.exist
for doc in @archivedDocs
sinon.assert.calledWith(@request.del, docOpts(doc))
for doc in @unarchivedDocs
expect(@request.del.calledWith(docOpts(doc))).to.equal false # no notCalledWith
done()
it "should remove the docs from mongo", (done)->
@DocArchiveManager.destroyAllDocs @project_id, (err)=>
expect(err).not.to.exist
for doc in @mixedDocs
sinon.assert.calledWith(@MongoManager.destroyDoc, doc._id)
done()
describe "_s3DocToMongoDoc", ->
describe "with the old schema", ->
it "should return the docs lines", (done) ->
@DocArchiveManager._s3DocToMongoDoc ["doc", "lines"], (error, doc) ->
expect(doc).to.deep.equal {
lines: ["doc", "lines"]
}
done()
describe "with the new schema", ->
it "should return the doc lines and ranges", (done) ->
@RangeManager.jsonRangesToMongo = sinon.stub().returns {"mongo": "ranges"}
@DocArchiveManager._s3DocToMongoDoc {
lines: ["doc", "lines"]
ranges: {"json": "ranges"}
schema_v: 1
}, (error, doc) ->
expect(doc).to.deep.equal {
lines: ["doc", "lines"]
ranges: {"mongo": "ranges"}
}
done()
it "should return just the doc lines when there are no ranges", (done) ->
@DocArchiveManager._s3DocToMongoDoc {
lines: ["doc", "lines"]
schema_v: 1
}, (error, doc) ->
expect(doc).to.deep.equal {
lines: ["doc", "lines"]
}
done()
describe "with an unrecognised schema", ->
it "should return an error", (done) ->
@DocArchiveManager._s3DocToMongoDoc {
schema_v: 2
}, (error, doc) ->
expect(error).to.exist
done()
describe "_mongoDocToS3Doc", ->
describe "with a valid doc", ->
it "should return the json version", (done) ->
@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
})
done()
describe "with null bytes in the result", ->
beforeEach ->
@_stringify = JSON.stringify
JSON.stringify = sinon.stub().returns '{"bad": "\u0000"}'
afterEach ->
JSON.stringify = @_stringify
it "should return an error", (done) ->
@DocArchiveManager._mongoDocToS3Doc {
lines: ["doc", "lines"]
ranges: { "mock": "ranges" }
}, (err, s3_doc) ->
expect(err).to.exist
done()
describe "without doc lines", ->
it "should return an error", (done) ->
@DocArchiveManager._mongoDocToS3Doc {}, (err, s3_doc) ->
expect(err).to.exist
done()