overleaf/services/docstore/test/unit/coffee/DocArchiveManagerTests.js

445 lines
14 KiB
JavaScript
Raw Normal View History

/*
* 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");
describe("DocArchiveManager", function() {
beforeEach(function() {
this.settings = {
docstore: {
s3: {
secret: "secret",
key: "this_key",
bucket:"doc-archive-unit-test"
}
}
};
2015-06-02 16:12:11 -04:00
this.request = {
put: {},
get: {},
2015-06-03 19:05:15 -04:00
del: {}
};
2015-06-02 16:12:11 -04:00
this.archivedDocs = [{
_id: ObjectId(),
inS3:true,
rev: 2
}, {
_id: ObjectId(),
inS3:true,
rev: 4
}, {
_id: ObjectId(),
inS3:true,
rev: 6
}];
this.mongoDocs = [{
_id: ObjectId(),
lines: ["one", "two", "three"],
2015-06-02 16:12:11 -04:00
rev: 2
}, {
_id: ObjectId(),
lines: ["aaa", "bbb", "ccc"],
2015-06-02 16:12:11 -04:00
rev: 4
}, {
_id: ObjectId(),
inS3: true,
rev: 6
}, {
_id: ObjectId(),
inS3: true,
rev: 6
2015-06-02 16:12:11 -04:00
}, {
_id: ObjectId(),
lines: ["111", "222", "333"],
2015-06-02 16:12:11 -04:00
rev: 6
}];
2015-06-02 16:12:11 -04:00
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
}];
this.mixedDocs = this.archivedDocs.concat(this.unarchivedDocs);
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)
};
this.requires = {
"settings-sharelatex": this.settings,
"./MongoManager": this.MongoManager,
"request": this.request,
"./RangeManager": (this.RangeManager = {}),
"logger-sharelatex": {
log() {},
err() {}
}
};
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});
});
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,
2017-03-30 12:13:43 -04:00
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 return no md5 error", function(done){
const data = JSON.stringify({
lines: this.mongoDocs[0].lines,
ranges: this.mongoDocs[0].ranges,
2017-03-30 12:13:43 -04:00
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();
});
});
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();
});
});
});
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 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();
});
});
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();
});
});
});
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();
});
});
it("should return error if have no docs", function(done){
this.MongoManager.getProjectsDocs = sinon.stub().callsArgWith(3, null, null);
return this.DocArchiveManager.archiveAllDocs(this.project_id, err=> {
should.exist(err);
return done();
});
});
it("should return the error", function(done){
this.MongoManager.getProjectsDocs = sinon.stub().callsArgWith(3, this.error, null);
return this.DocArchiveManager.archiveAllDocs(this.project_id, err=> {
err.should.equal(this.error);
return done();
});
});
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);
});
return it("should not throw and error", function(done){
return this.DocArchiveManager.archiveAllDocs(this.project_id, err=> {
should.not.exist(err);
return done();
});
});
});
});
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 (let doc of Array.from(this.archivedDocs)) {
this.DocArchiveManager.unarchiveDoc.calledWith(this.project_id, doc._id).should.equal(true);
}
should.not.exist(err);
return done();
});
});
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();
});
});
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("destroyAllDocs", function() {
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));
});
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 (let 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 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}`
}));
};
return this.DocArchiveManager.destroyAllDocs(this.project_id, err=> {
let doc;
expect(err).not.to.exist;
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
return done();
});
});
return it("should remove the docs from mongo", function(done){
this.DocArchiveManager.destroyAllDocs(this.project_id, err=> {
return expect(err).not.to.exist;
});
for (let doc of Array.from(this.mixedDocs)) {
sinon.assert.calledWith(this.MongoManager.destroyDoc, doc._id);
}
return done();
});
});
2017-04-03 10:50:33 -04:00
describe("_s3DocToMongoDoc", function() {
describe("with the old schema", () => it("should return the docs lines", function(done) {
return this.DocArchiveManager._s3DocToMongoDoc(["doc", "lines"], function(error, doc) {
expect(doc).to.deep.equal({
lines: ["doc", "lines"]
});
return done();
});
}));
2017-04-03 10:50:33 -04:00
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"},
2017-04-03 10:50:33 -04:00
schema_v: 1
}, function(error, doc) {
expect(doc).to.deep.equal({
lines: ["doc", "lines"],
2017-04-03 10:50:33 -04:00
ranges: {"mongo": "ranges"}
});
return done();
});
});
2017-04-03 10:50:33 -04:00
return it("should return just the doc lines when there are no ranges", function(done) {
return this.DocArchiveManager._s3DocToMongoDoc({
lines: ["doc", "lines"],
2017-04-03 10:50:33 -04:00
schema_v: 1
}, function(error, doc) {
expect(doc).to.deep.equal({
2017-04-03 10:50:33 -04:00
lines: ["doc", "lines"]
});
return done();
});
});
});
2017-04-03 10:50:33 -04:00
return describe("with an unrecognised schema", () => it("should return an error", function(done) {
return this.DocArchiveManager._s3DocToMongoDoc({
schema_v: 2
}, function(error, doc) {
expect(error).to.exist;
return done();
});
}));
});
2017-04-03 11:04:54 -04:00
return describe("_mongoDocToS3Doc", function() {
describe("with a valid doc", () => it("should return the json version", function(done) {
let doc;
return this.DocArchiveManager._mongoDocToS3Doc((doc = {
lines: ["doc", "lines"],
ranges: { "mock": "ranges" }
}), function(err, s3_doc) {
expect(s3_doc).to.equal(JSON.stringify({
lines: ["doc", "lines"],
ranges: { "mock": "ranges" },
schema_v: 1
})
);
return done();
});
}));
2017-04-03 11:04:54 -04:00
describe("with null bytes in the result", function() {
beforeEach(function() {
this._stringify = JSON.stringify;
return JSON.stringify = sinon.stub().returns('{"bad": "\u0000"}');
});
2017-04-03 11:04:54 -04:00
afterEach(function() {
return JSON.stringify = this._stringify;
});
2017-04-03 11:04:54 -04:00
return it("should return an error", function(done) {
return this.DocArchiveManager._mongoDocToS3Doc({
lines: ["doc", "lines"],
2017-04-03 11:04:54 -04:00
ranges: { "mock": "ranges" }
}, function(err, s3_doc) {
expect(err).to.exist;
return done();
});
});
});
2017-04-03 11:04:54 -04:00
return describe("without doc lines", () => it("should return an error", function(done) {
return this.DocArchiveManager._mongoDocToS3Doc({}, function(err, s3_doc) {
expect(err).to.exist;
return done();
});
}));
});
});
2017-04-03 11:04:54 -04:00
2017-04-03 10:50:33 -04:00