overleaf/services/track-changes/test/unit/coffee/DiffManager/DiffManagerTests.js

304 lines
12 KiB
JavaScript
Raw Normal View History

/*
* 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);
});
});
});
2014-03-04 09:05:17 -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
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);
});
it("should call _tryGetDocumentBeforeVersion", function() {
return this.DiffManager._tryGetDocumentBeforeVersion
.calledWith(this.project_id, this.doc_id, this.version)
.should.equal(true);
});
return it("should call the callback with the response", function() {
return this.callback.calledWith(null, this.document, this.rewound_updates).should.equal(true);
});
});
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);
});
it("should call _tryGetDocumentBeforeVersion twice", function() {
return this.DiffManager._tryGetDocumentBeforeVersion
.calledTwice
.should.equal(true);
});
return it("should call the callback with the response", function() {
return this.callback.calledWith(null, this.document, this.rewound_updates).should.equal(true);
});
});
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);
});
it("should call _tryGetDocumentBeforeVersion once", function() {
return this.DiffManager._tryGetDocumentBeforeVersion
.calledOnce
.should.equal(true);
});
return it("should call the callback with the error", function() {
return this.callback.calledWith(this.error).should.equal(true);
});
});
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);
});
it("should call _tryGetDocumentBeforeVersion three times (max retries)", function() {
return this.DiffManager._tryGetDocumentBeforeVersion
.calledThrice
.should.equal(true);
});
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
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);
});
});
});
});