/* * 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); }); }); }); 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"} ];}); 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"} ];}); 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); }); }); }); });