2020-02-17 12:35:01 -05:00
|
|
|
/* eslint-disable
|
|
|
|
camelcase,
|
|
|
|
no-return-assign,
|
|
|
|
no-unused-vars,
|
|
|
|
*/
|
|
|
|
// TODO: This file was created by bulk-decaffeinate.
|
|
|
|
// Fix any style issues and re-enable lint.
|
2020-02-17 12:34:50 -05:00
|
|
|
/*
|
|
|
|
* decaffeinate suggestions:
|
|
|
|
* DS102: Remove unnecessary code created because of implicit returns
|
|
|
|
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
|
|
|
*/
|
|
|
|
const sinon = require('sinon');
|
|
|
|
const chai = require('chai');
|
|
|
|
const should = chai.should();
|
|
|
|
const { expect } = chai;
|
|
|
|
const modulePath = "../../../../app/js/DiffManager.js";
|
|
|
|
const SandboxedModule = require('sandboxed-module');
|
|
|
|
|
|
|
|
describe("DiffManager", function() {
|
|
|
|
beforeEach(function() {
|
|
|
|
this.DiffManager = SandboxedModule.require(modulePath, { requires: {
|
|
|
|
"logger-sharelatex": (this.logger = { log: sinon.stub(), error: sinon.stub(), warn: sinon.stub() }),
|
|
|
|
"./UpdatesManager": (this.UpdatesManager = {}),
|
|
|
|
"./DocumentUpdaterManager": (this.DocumentUpdaterManager = {}),
|
|
|
|
"./DiffGenerator": (this.DiffGenerator = {})
|
|
|
|
}
|
|
|
|
});
|
|
|
|
this.callback = sinon.stub();
|
|
|
|
this.from = new Date();
|
|
|
|
this.to = new Date(Date.now() + 10000);
|
|
|
|
this.project_id = "mock-project-id";
|
|
|
|
return this.doc_id = "mock-doc-id";
|
|
|
|
});
|
|
|
|
|
|
|
|
describe("getLatestDocAndUpdates", function() {
|
|
|
|
beforeEach(function() {
|
|
|
|
this.content = "hello world";
|
|
|
|
this.version = 42;
|
|
|
|
this.updates = [ "mock-update-1", "mock-update-2" ];
|
|
|
|
|
|
|
|
this.DocumentUpdaterManager.getDocument = sinon.stub().callsArgWith(2, null, this.content, this.version);
|
|
|
|
return this.UpdatesManager.getDocUpdatesWithUserInfo = sinon.stub().callsArgWith(3, null, this.updates);
|
|
|
|
});
|
|
|
|
|
|
|
|
describe("with a fromVersion", function() {
|
|
|
|
beforeEach(function() {
|
|
|
|
return this.DiffManager.getLatestDocAndUpdates(this.project_id, this.doc_id, this.from, this.callback);
|
|
|
|
});
|
|
|
|
|
|
|
|
it("should get the latest version of the doc", function() {
|
|
|
|
return this.DocumentUpdaterManager.getDocument
|
|
|
|
.calledWith(this.project_id, this.doc_id)
|
|
|
|
.should.equal(true);
|
|
|
|
});
|
|
|
|
|
|
|
|
it("should get the latest updates", function() {
|
|
|
|
return this.UpdatesManager.getDocUpdatesWithUserInfo
|
|
|
|
.calledWith(this.project_id, this.doc_id, {from: this.from})
|
|
|
|
.should.equal(true);
|
|
|
|
});
|
|
|
|
|
|
|
|
return it("should call the callback with the content, version and updates", function() {
|
|
|
|
return this.callback.calledWith(null, this.content, this.version, this.updates).should.equal(true);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
return describe("with no fromVersion", function() {
|
|
|
|
beforeEach(function() {
|
|
|
|
return this.DiffManager.getLatestDocAndUpdates(this.project_id, this.doc_id, null, this.callback);
|
|
|
|
});
|
|
|
|
|
|
|
|
it("should get the latest version of the doc", function() {
|
|
|
|
return this.DocumentUpdaterManager.getDocument
|
|
|
|
.calledWith(this.project_id, this.doc_id)
|
|
|
|
.should.equal(true);
|
|
|
|
});
|
|
|
|
|
|
|
|
it("should not get the latest updates", function() {
|
|
|
|
return this.UpdatesManager.getDocUpdatesWithUserInfo
|
|
|
|
.called.should.equal(false);
|
|
|
|
});
|
|
|
|
|
|
|
|
return it("should call the callback with the content, version and blank updates", function() {
|
|
|
|
return this.callback.calledWith(null, this.content, this.version, []).should.equal(true);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
2017-06-28 10:38:31 -04:00
|
|
|
|
2014-03-04 09:05:17 -05:00
|
|
|
|
2020-02-17 12:34:50 -05:00
|
|
|
describe("getDiff", function() {
|
|
|
|
beforeEach(function() {
|
|
|
|
this.content = "hello world";
|
|
|
|
// Op versions are the version they were applied to, so doc is always one version
|
|
|
|
// ahead.s
|
|
|
|
this.version = 43;
|
|
|
|
this.updates = [
|
|
|
|
{ op: "mock-4", v: 42, meta: { start_ts: new Date(this.to.getTime() + 20)} },
|
|
|
|
{ op: "mock-3", v: 41, meta: { start_ts: new Date(this.to.getTime() + 10)} },
|
|
|
|
{ op: "mock-2", v: 40, meta: { start_ts: new Date(this.to.getTime() - 10)} },
|
|
|
|
{ op: "mock-1", v: 39, meta: { start_ts: new Date(this.to.getTime() - 20)} }
|
|
|
|
];
|
|
|
|
this.fromVersion = 39;
|
|
|
|
this.toVersion = 40;
|
|
|
|
this.diffed_updates = this.updates.slice(2);
|
|
|
|
this.rewound_content = "rewound-content";
|
|
|
|
return this.diff = [ {u: "mock-diff"} ];});
|
2014-03-04 09:05:17 -05:00
|
|
|
|
2020-02-17 12:34:50 -05:00
|
|
|
describe("with matching versions", function() {
|
|
|
|
beforeEach(function() {
|
|
|
|
this.DiffManager.getDocumentBeforeVersion = sinon.stub().callsArgWith(3, null, this.rewound_content, this.updates);
|
|
|
|
this.DiffGenerator.buildDiff = sinon.stub().returns(this.diff);
|
|
|
|
return this.DiffManager.getDiff(this.project_id, this.doc_id, this.fromVersion, this.toVersion, this.callback);
|
|
|
|
});
|
|
|
|
|
|
|
|
it("should get the latest doc and version with all recent updates", function() {
|
|
|
|
return this.DiffManager.getDocumentBeforeVersion
|
|
|
|
.calledWith(this.project_id, this.doc_id, this.fromVersion)
|
|
|
|
.should.equal(true);
|
|
|
|
});
|
|
|
|
|
|
|
|
it("should generate the diff", function() {
|
|
|
|
return this.DiffGenerator.buildDiff
|
|
|
|
.calledWith(this.rewound_content, this.diffed_updates.slice().reverse())
|
|
|
|
.should.equal(true);
|
|
|
|
});
|
|
|
|
|
|
|
|
return it("should call the callback with the diff", function() {
|
|
|
|
return this.callback.calledWith(null, this.diff).should.equal(true);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
return describe("when the updates are inconsistent", function() {
|
|
|
|
beforeEach(function() {
|
|
|
|
this.DiffManager.getLatestDocAndUpdates = sinon.stub().callsArgWith(3, null, this.content, this.version, this.updates);
|
|
|
|
this.DiffGenerator.buildDiff = sinon.stub().throws(this.error = new Error("inconsistent!"));
|
|
|
|
return this.DiffManager.getDiff(this.project_id, this.doc_id, this.fromVersion, this.toVersion, this.callback);
|
|
|
|
});
|
|
|
|
|
|
|
|
return it("should call the callback with an error", function() {
|
|
|
|
return this.callback
|
|
|
|
.calledWith(this.error)
|
|
|
|
.should.equal(true);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe("getDocumentBeforeVersion", function() {
|
|
|
|
beforeEach(function() {
|
|
|
|
this.DiffManager._tryGetDocumentBeforeVersion = sinon.stub();
|
|
|
|
this.document = "mock-documents";
|
|
|
|
return this.rewound_updates = "mock-rewound-updates";
|
|
|
|
});
|
|
|
|
|
|
|
|
describe("succesfully", function() {
|
|
|
|
beforeEach(function() {
|
|
|
|
this.DiffManager._tryGetDocumentBeforeVersion.yields(null, this.document, this.rewound_updates);
|
|
|
|
return this.DiffManager.getDocumentBeforeVersion(this.project_id, this.doc_id, this.version, this.callback);
|
|
|
|
});
|
2016-09-30 06:36:47 -04:00
|
|
|
|
2020-02-17 12:34:50 -05:00
|
|
|
it("should call _tryGetDocumentBeforeVersion", function() {
|
|
|
|
return this.DiffManager._tryGetDocumentBeforeVersion
|
|
|
|
.calledWith(this.project_id, this.doc_id, this.version)
|
|
|
|
.should.equal(true);
|
|
|
|
});
|
2016-09-30 06:36:47 -04:00
|
|
|
|
2020-02-17 12:34:50 -05:00
|
|
|
return it("should call the callback with the response", function() {
|
|
|
|
return this.callback.calledWith(null, this.document, this.rewound_updates).should.equal(true);
|
|
|
|
});
|
|
|
|
});
|
2016-09-30 06:36:47 -04:00
|
|
|
|
2020-02-17 12:34:50 -05:00
|
|
|
describe("with a retry needed", function() {
|
|
|
|
beforeEach(function() {
|
|
|
|
let retried = false;
|
|
|
|
this.DiffManager._tryGetDocumentBeforeVersion = (project_id, doc_id, version, callback) => {
|
|
|
|
if (!retried) {
|
|
|
|
retried = true;
|
|
|
|
const error = new Error();
|
|
|
|
error.retry = true;
|
|
|
|
return callback(error);
|
|
|
|
} else {
|
|
|
|
return callback(null, this.document, this.rewound_updates);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
sinon.spy(this.DiffManager, "_tryGetDocumentBeforeVersion");
|
|
|
|
return this.DiffManager.getDocumentBeforeVersion(this.project_id, this.doc_id, this.version, this.callback);
|
|
|
|
});
|
2016-09-30 06:36:47 -04:00
|
|
|
|
2020-02-17 12:34:50 -05:00
|
|
|
it("should call _tryGetDocumentBeforeVersion twice", function() {
|
|
|
|
return this.DiffManager._tryGetDocumentBeforeVersion
|
2016-09-30 06:36:47 -04:00
|
|
|
.calledTwice
|
2020-02-17 12:34:50 -05:00
|
|
|
.should.equal(true);
|
|
|
|
});
|
2016-09-30 06:36:47 -04:00
|
|
|
|
2020-02-17 12:34:50 -05:00
|
|
|
return it("should call the callback with the response", function() {
|
|
|
|
return this.callback.calledWith(null, this.document, this.rewound_updates).should.equal(true);
|
|
|
|
});
|
|
|
|
});
|
2016-09-30 06:36:47 -04:00
|
|
|
|
2020-02-17 12:34:50 -05:00
|
|
|
describe("with a non-retriable error", function() {
|
|
|
|
beforeEach(function() {
|
|
|
|
this.error = new Error("oops");
|
|
|
|
this.DiffManager._tryGetDocumentBeforeVersion.yields(this.error);
|
|
|
|
return this.DiffManager.getDocumentBeforeVersion(this.project_id, this.doc_id, this.version, this.callback);
|
|
|
|
});
|
2016-09-30 06:36:47 -04:00
|
|
|
|
2020-02-17 12:34:50 -05:00
|
|
|
it("should call _tryGetDocumentBeforeVersion once", function() {
|
|
|
|
return this.DiffManager._tryGetDocumentBeforeVersion
|
2016-09-30 06:36:47 -04:00
|
|
|
.calledOnce
|
2020-02-17 12:34:50 -05:00
|
|
|
.should.equal(true);
|
|
|
|
});
|
2016-09-30 06:36:47 -04:00
|
|
|
|
2020-02-17 12:34:50 -05:00
|
|
|
return it("should call the callback with the error", function() {
|
|
|
|
return this.callback.calledWith(this.error).should.equal(true);
|
|
|
|
});
|
|
|
|
});
|
2016-09-30 06:36:47 -04:00
|
|
|
|
2020-02-17 12:34:50 -05:00
|
|
|
return describe("when retry limit is matched", function() {
|
|
|
|
beforeEach(function() {
|
|
|
|
this.error = new Error("oops");
|
|
|
|
this.error.retry = true;
|
|
|
|
this.DiffManager._tryGetDocumentBeforeVersion.yields(this.error);
|
|
|
|
return this.DiffManager.getDocumentBeforeVersion(this.project_id, this.doc_id, this.version, this.callback);
|
|
|
|
});
|
2016-09-30 06:36:47 -04:00
|
|
|
|
2020-02-17 12:34:50 -05:00
|
|
|
it("should call _tryGetDocumentBeforeVersion three times (max retries)", function() {
|
|
|
|
return this.DiffManager._tryGetDocumentBeforeVersion
|
2016-09-30 06:36:47 -04:00
|
|
|
.calledThrice
|
2020-02-17 12:34:50 -05:00
|
|
|
.should.equal(true);
|
|
|
|
});
|
2016-09-30 06:36:47 -04:00
|
|
|
|
2020-02-17 12:34:50 -05:00
|
|
|
return it("should call the callback with the error", function() {
|
|
|
|
return this.callback.calledWith(this.error).should.equal(true);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
return describe("_tryGetDocumentBeforeVersion", function() {
|
|
|
|
beforeEach(function() {
|
|
|
|
this.content = "hello world";
|
|
|
|
// Op versions are the version they were applied to, so doc is always one version
|
|
|
|
// ahead.s
|
|
|
|
this.version = 43;
|
|
|
|
this.updates = [
|
|
|
|
{ op: "mock-4", v: 42, meta: { start_ts: new Date(this.to.getTime() + 20)} },
|
|
|
|
{ op: "mock-3", v: 41, meta: { start_ts: new Date(this.to.getTime() + 10)} },
|
|
|
|
{ op: "mock-2", v: 40, meta: { start_ts: new Date(this.to.getTime() - 10)} },
|
|
|
|
{ op: "mock-1", v: 39, meta: { start_ts: new Date(this.to.getTime() - 20)} }
|
|
|
|
];
|
|
|
|
this.fromVersion = 39;
|
|
|
|
this.rewound_content = "rewound-content";
|
|
|
|
return this.diff = [ {u: "mock-diff"} ];});
|
2014-03-10 12:03:03 -04:00
|
|
|
|
2020-02-17 12:34:50 -05:00
|
|
|
describe("with matching versions", function() {
|
|
|
|
beforeEach(function() {
|
|
|
|
this.DiffManager.getLatestDocAndUpdates = sinon.stub().callsArgWith(3, null, this.content, this.version, this.updates);
|
|
|
|
this.DiffGenerator.rewindUpdates = sinon.spy((content, updates) => {
|
|
|
|
// the rewindUpdates method reverses the 'updates' array
|
|
|
|
updates.reverse();
|
|
|
|
return this.rewound_content;
|
|
|
|
});
|
|
|
|
this.rewindUpdatesWithArgs = this.DiffGenerator.rewindUpdates.withArgs(this.content, this.updates.slice().reverse());
|
|
|
|
return this.DiffManager._tryGetDocumentBeforeVersion(this.project_id, this.doc_id, this.fromVersion, this.callback);
|
|
|
|
});
|
|
|
|
|
|
|
|
it("should get the latest doc and version with all recent updates", function() {
|
|
|
|
return this.DiffManager.getLatestDocAndUpdates
|
|
|
|
.calledWith(this.project_id, this.doc_id, this.fromVersion)
|
|
|
|
.should.equal(true);
|
|
|
|
});
|
|
|
|
|
|
|
|
it("should rewind the diff", function() {
|
|
|
|
return sinon.assert.calledOnce(this.rewindUpdatesWithArgs);
|
|
|
|
});
|
|
|
|
|
|
|
|
return it("should call the callback with the rewound document and updates", function() {
|
|
|
|
return this.callback.calledWith(null, this.rewound_content, this.updates).should.equal(true);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe("with mismatching versions", function() {
|
|
|
|
beforeEach(function() {
|
|
|
|
this.version = 50;
|
|
|
|
this.updates = [ { op: "mock-1", v: 40 }, { op: "mock-1", v: 39 } ];
|
|
|
|
this.DiffManager.getLatestDocAndUpdates = sinon.stub().callsArgWith(3, null, this.content, this.version, this.updates);
|
|
|
|
return this.DiffManager._tryGetDocumentBeforeVersion(this.project_id, this.doc_id, this.fromVersion, this.callback);
|
|
|
|
});
|
|
|
|
|
|
|
|
return it("should call the callback with an error with retry = true set", function() {
|
|
|
|
this.callback.calledOnce.should.equal(true);
|
|
|
|
const error = this.callback.args[0][0];
|
|
|
|
return expect(error.retry).to.equal(true);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
return describe("when the updates are inconsistent", function() {
|
|
|
|
beforeEach(function() {
|
|
|
|
this.DiffManager.getLatestDocAndUpdates = sinon.stub().callsArgWith(3, null, this.content, this.version, this.updates);
|
|
|
|
this.DiffGenerator.rewindUpdates = sinon.stub().throws(this.error = new Error("inconsistent!"));
|
|
|
|
return this.DiffManager.getDocumentBeforeVersion(this.project_id, this.doc_id, this.fromVersion, this.callback);
|
|
|
|
});
|
|
|
|
|
|
|
|
return it("should call the callback with an error", function() {
|
|
|
|
return this.callback
|
|
|
|
.calledWith(this.error)
|
|
|
|
.should.equal(true);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|