mirror of
https://github.com/overleaf/overleaf.git
synced 2024-10-31 21:21:03 -04:00
444 lines
14 KiB
JavaScript
444 lines
14 KiB
JavaScript
/* 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 { expect } = require('chai')
|
|
const modulePath = '../../../../app/js/DiffManager.js'
|
|
const SandboxedModule = require('sandboxed-module')
|
|
|
|
describe('DiffManager', function () {
|
|
beforeEach(function () {
|
|
this.DiffManager = SandboxedModule.require(modulePath, {
|
|
requires: {
|
|
'./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)
|
|
})
|
|
})
|
|
|
|
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!')))
|
|
this.DiffGenerator.rewindUpdates = sinon.stub()
|
|
this.DiffManager.getDiff(
|
|
this.project_id,
|
|
this.doc_id,
|
|
this.fromVersion,
|
|
this.toVersion,
|
|
this.callback
|
|
)
|
|
})
|
|
|
|
it('should call the callback with an error', function () {
|
|
this.callback.calledWith(sinon.match(Error)).should.equal(true)
|
|
const errorObj = this.callback.args[0][0]
|
|
expect(errorObj.message).to.include('inconsistent!')
|
|
})
|
|
})
|
|
})
|
|
|
|
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)
|
|
})
|
|
})
|
|
})
|
|
})
|