/* 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. /* * 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) }) }) }) })