mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-29 13:23:40 -05:00
ee85d948e2
GitOrigin-RevId: ef2ef77e26df59d1af3df6dc664e284d3c70102d
523 lines
15 KiB
JavaScript
523 lines
15 KiB
JavaScript
import sinon from 'sinon'
|
|
import { expect } from 'chai'
|
|
import { strict as esmock } from 'esmock'
|
|
|
|
const MODULE_PATH = '../../../../app/js/DiffManager.js'
|
|
|
|
describe('DiffManager', function () {
|
|
beforeEach(async function () {
|
|
this.DocumentUpdaterManager = {}
|
|
this.DiffGenerator = {
|
|
buildDiff: sinon.stub(),
|
|
}
|
|
this.UpdatesProcessor = {
|
|
processUpdatesForProject: sinon.stub(),
|
|
}
|
|
this.HistoryStoreManager = {
|
|
getChunkAtVersion: sinon.stub(),
|
|
}
|
|
this.WebApiManager = {
|
|
getHistoryId: sinon.stub(),
|
|
}
|
|
this.ChunkTranslator = {
|
|
convertToDiffUpdates: sinon.stub(),
|
|
}
|
|
this.FileTreeDiffGenerator = {}
|
|
this.DiffManager = await esmock(MODULE_PATH, {
|
|
'../../../../app/js/DocumentUpdaterManager.js':
|
|
this.DocumentUpdaterManager,
|
|
'../../../../app/js/DiffGenerator.js': this.DiffGenerator,
|
|
'../../../../app/js/UpdatesProcessor.js': this.UpdatesProcessor,
|
|
'../../../../app/js/HistoryStoreManager.js': this.HistoryStoreManager,
|
|
'../../../../app/js/WebApiManager.js': this.WebApiManager,
|
|
'../../../../app/js/ChunkTranslator.js': this.ChunkTranslator,
|
|
'../../../../app/js/FileTreeDiffGenerator.js': this.FileTreeDiffGenerator,
|
|
})
|
|
this.projectId = 'mock-project-id'
|
|
this.callback = sinon.stub()
|
|
})
|
|
|
|
describe('getDiff', function () {
|
|
beforeEach(function () {
|
|
this.pathname = 'main.tex'
|
|
this.fromVersion = 4
|
|
this.toVersion = 8
|
|
this.initialContent = 'foo bar baz'
|
|
this.updates = ['mock-updates']
|
|
this.diff = { mock: 'dif' }
|
|
this.UpdatesProcessor.processUpdatesForProject
|
|
.withArgs(this.projectId)
|
|
.yields()
|
|
this.DiffGenerator.buildDiff
|
|
.withArgs(this.initialContent, this.updates)
|
|
.returns(this.diff)
|
|
})
|
|
|
|
describe('with a text file', function () {
|
|
beforeEach(function () {
|
|
this.DiffManager._mocks._getProjectUpdatesBetweenVersions = sinon.stub()
|
|
this.DiffManager._mocks._getProjectUpdatesBetweenVersions
|
|
.withArgs(
|
|
this.projectId,
|
|
this.pathname,
|
|
this.fromVersion,
|
|
this.toVersion
|
|
)
|
|
.yields(null, {
|
|
initialContent: this.initialContent,
|
|
updates: this.updates,
|
|
})
|
|
this.DiffManager.getDiff(
|
|
this.projectId,
|
|
this.pathname,
|
|
this.fromVersion,
|
|
this.toVersion,
|
|
this.callback
|
|
)
|
|
})
|
|
|
|
it('should make sure all pending updates have been process', function () {
|
|
this.UpdatesProcessor.processUpdatesForProject
|
|
.calledWith(this.projectId)
|
|
.should.equal(true)
|
|
})
|
|
|
|
it('should get the updates from the history backend', function () {
|
|
this.DiffManager._mocks._getProjectUpdatesBetweenVersions
|
|
.calledWith(
|
|
this.projectId,
|
|
this.pathname,
|
|
this.fromVersion,
|
|
this.toVersion
|
|
)
|
|
.should.equal(true)
|
|
})
|
|
|
|
it('should convert the updates to a diff', function () {
|
|
this.DiffGenerator.buildDiff
|
|
.calledWith(this.initialContent, this.updates)
|
|
.should.equal(true)
|
|
})
|
|
|
|
it('should return the diff', function () {
|
|
this.callback.calledWith(null, this.diff).should.equal(true)
|
|
})
|
|
})
|
|
|
|
describe('with a binary file', function () {
|
|
beforeEach(function () {
|
|
this.DiffManager._mocks._getProjectUpdatesBetweenVersions = sinon.stub()
|
|
this.DiffManager._mocks._getProjectUpdatesBetweenVersions
|
|
.withArgs(
|
|
this.projectId,
|
|
this.pathname,
|
|
this.fromVersion,
|
|
this.toVersion
|
|
)
|
|
.yields(null, { binary: true })
|
|
this.DiffManager.getDiff(
|
|
this.projectId,
|
|
this.pathname,
|
|
this.fromVersion,
|
|
this.toVersion,
|
|
this.callback
|
|
)
|
|
})
|
|
|
|
it('should make sure all pending updates have been process', function () {
|
|
this.UpdatesProcessor.processUpdatesForProject
|
|
.calledWith(this.projectId)
|
|
.should.equal(true)
|
|
})
|
|
|
|
it('should get the updates from the history backend', function () {
|
|
this.DiffManager._mocks._getProjectUpdatesBetweenVersions
|
|
.calledWith(
|
|
this.projectId,
|
|
this.pathname,
|
|
this.fromVersion,
|
|
this.toVersion
|
|
)
|
|
.should.equal(true)
|
|
})
|
|
|
|
it('should not try convert any updates to a diff', function () {
|
|
this.DiffGenerator.buildDiff.called.should.equal(false)
|
|
})
|
|
|
|
it('should return the binary diff', function () {
|
|
this.callback.calledWith(null, { binary: true }).should.equal(true)
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('_getProjectUpdatesBetweenVersions', function () {
|
|
beforeEach(function () {
|
|
this.pathname = 'main.tex'
|
|
this.fromVersion = 4
|
|
this.toVersion = 8
|
|
this.chunks = ['mock-chunk-1', 'mock-chunk-2']
|
|
this.concatted_chunk = 'mock-chunk'
|
|
this.DiffManager._mocks._concatChunks = sinon.stub()
|
|
this.DiffManager._mocks._concatChunks
|
|
.withArgs(this.chunks)
|
|
.returns(this.concatted_chunk)
|
|
this.updates = ['mock-updates']
|
|
this.initialContent = 'foo bar baz'
|
|
this.ChunkTranslator.convertToDiffUpdates
|
|
.withArgs(
|
|
this.projectId,
|
|
this.concatted_chunk,
|
|
this.pathname,
|
|
this.fromVersion,
|
|
this.toVersion
|
|
)
|
|
.yields(null, {
|
|
initialContent: this.initialContent,
|
|
updates: this.updates,
|
|
})
|
|
})
|
|
|
|
describe('for the normal case', function () {
|
|
beforeEach(function () {
|
|
this.DiffManager._mocks._getChunks = sinon.stub()
|
|
this.DiffManager._mocks._getChunks
|
|
.withArgs(this.projectId, this.fromVersion, this.toVersion)
|
|
.yields(null, this.chunks)
|
|
this.DiffManager._getProjectUpdatesBetweenVersions(
|
|
this.projectId,
|
|
this.pathname,
|
|
this.fromVersion,
|
|
this.toVersion,
|
|
this.callback
|
|
)
|
|
})
|
|
|
|
it('should get the relevant chunks', function () {
|
|
this.DiffManager._mocks._getChunks
|
|
.calledWith(this.projectId, this.fromVersion, this.toVersion)
|
|
.should.equal(true)
|
|
})
|
|
|
|
it('should get the concat the chunks', function () {
|
|
this.DiffManager._mocks._concatChunks
|
|
.calledWith(this.chunks)
|
|
.should.equal(true)
|
|
})
|
|
|
|
it('should convert the chunks to an initial version and updates', function () {
|
|
this.ChunkTranslator.convertToDiffUpdates
|
|
.calledWith(
|
|
this.projectId,
|
|
this.concatted_chunk,
|
|
this.pathname,
|
|
this.fromVersion,
|
|
this.toVersion
|
|
)
|
|
.should.equal(true)
|
|
})
|
|
|
|
it('should return the initialContent and updates', function () {
|
|
this.callback
|
|
.calledWith(null, {
|
|
initialContent: this.initialContent,
|
|
updates: this.updates,
|
|
})
|
|
.should.equal(true)
|
|
})
|
|
})
|
|
|
|
describe('for the error case', function () {
|
|
beforeEach(function () {
|
|
this.DiffManager._mocks._getChunks = sinon.stub()
|
|
this.DiffManager._mocks._getChunks
|
|
.withArgs(this.projectId, this.fromVersion, this.toVersion)
|
|
.yields(new Error('failed to load chunk'))
|
|
this.DiffManager._getProjectUpdatesBetweenVersions(
|
|
this.projectId,
|
|
this.pathname,
|
|
this.fromVersion,
|
|
this.toVersion,
|
|
this.callback
|
|
)
|
|
})
|
|
|
|
it('should call the callback with an error', function () {
|
|
this.callback
|
|
.calledWith(sinon.match.instanceOf(Error))
|
|
.should.equal(true)
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('_getChunks', function () {
|
|
beforeEach(function () {
|
|
this.historyId = 'mock-overleaf-id'
|
|
this.WebApiManager.getHistoryId.yields(null, this.historyId)
|
|
})
|
|
|
|
describe('where only one chunk is needed', function () {
|
|
beforeEach(function (done) {
|
|
this.fromVersion = 4
|
|
this.toVersion = 8
|
|
this.chunk = {
|
|
chunk: {
|
|
startVersion: 2,
|
|
}, // before fromVersion
|
|
}
|
|
this.HistoryStoreManager.getChunkAtVersion
|
|
.withArgs(this.projectId, this.historyId, this.toVersion)
|
|
.yields(null, this.chunk)
|
|
this.DiffManager._getChunks(
|
|
this.projectId,
|
|
this.fromVersion,
|
|
this.toVersion,
|
|
(error, chunks) => {
|
|
this.error = error
|
|
this.chunks = chunks
|
|
done()
|
|
}
|
|
)
|
|
})
|
|
|
|
it("should the project's overleaf id", function () {
|
|
this.WebApiManager.getHistoryId
|
|
.calledWith(this.projectId)
|
|
.should.equal(true)
|
|
})
|
|
|
|
it('should request the first chunk', function () {
|
|
this.HistoryStoreManager.getChunkAtVersion
|
|
.calledWith(this.projectId, this.historyId, this.toVersion)
|
|
.should.equal(true)
|
|
})
|
|
|
|
it('should return an array of chunks', function () {
|
|
expect(this.chunks).to.deep.equal([this.chunk])
|
|
})
|
|
})
|
|
|
|
describe('where multiple chunks are needed', function () {
|
|
beforeEach(function (done) {
|
|
this.fromVersion = 4
|
|
this.toVersion = 8
|
|
this.chunk1 = {
|
|
chunk: {
|
|
startVersion: 6,
|
|
},
|
|
}
|
|
this.chunk2 = {
|
|
chunk: {
|
|
startVersion: 2,
|
|
},
|
|
}
|
|
this.HistoryStoreManager.getChunkAtVersion
|
|
.withArgs(this.projectId, this.historyId, this.toVersion)
|
|
.yields(null, this.chunk1)
|
|
this.HistoryStoreManager.getChunkAtVersion
|
|
.withArgs(
|
|
this.projectId,
|
|
this.historyId,
|
|
this.chunk1.chunk.startVersion
|
|
)
|
|
.yields(null, this.chunk2)
|
|
this.DiffManager._mocks._getChunks(
|
|
this.projectId,
|
|
this.fromVersion,
|
|
this.toVersion,
|
|
(error, chunks) => {
|
|
this.error = error
|
|
this.chunks = chunks
|
|
done()
|
|
}
|
|
)
|
|
})
|
|
|
|
it('should request the first chunk', function () {
|
|
this.HistoryStoreManager.getChunkAtVersion
|
|
.calledWith(this.projectId, this.historyId, this.toVersion)
|
|
.should.equal(true)
|
|
})
|
|
|
|
it('should request the second chunk, from where the first one started', function () {
|
|
this.HistoryStoreManager.getChunkAtVersion
|
|
.calledWith(
|
|
this.projectId,
|
|
this.historyId,
|
|
this.chunk1.chunk.startVersion
|
|
)
|
|
.should.equal(true)
|
|
})
|
|
|
|
it('should return an array of chunks', function () {
|
|
expect(this.chunks).to.deep.equal([this.chunk1, this.chunk2])
|
|
})
|
|
})
|
|
|
|
describe('where more than MAX_CHUNKS are requested', function () {
|
|
beforeEach(function (done) {
|
|
this.fromVersion = 0
|
|
this.toVersion = 8
|
|
this.chunk1 = {
|
|
chunk: {
|
|
startVersion: 6,
|
|
},
|
|
}
|
|
this.chunk2 = {
|
|
chunk: {
|
|
startVersion: 4,
|
|
},
|
|
}
|
|
this.chunk3 = {
|
|
chunk: {
|
|
startVersion: 2,
|
|
},
|
|
}
|
|
this.DiffManager.setMaxChunkRequests(2)
|
|
this.HistoryStoreManager.getChunkAtVersion
|
|
.withArgs(this.projectId, this.historyId, this.toVersion)
|
|
.yields(null, this.chunk1)
|
|
this.HistoryStoreManager.getChunkAtVersion
|
|
.withArgs(
|
|
this.projectId,
|
|
this.historyId,
|
|
this.chunk1.chunk.startVersion
|
|
)
|
|
.yields(null, this.chunk2)
|
|
this.DiffManager._mocks._getChunks(
|
|
this.projectId,
|
|
this.fromVersion,
|
|
this.toVersion,
|
|
(error, chunks) => {
|
|
this.error = error
|
|
this.chunks = chunks
|
|
done()
|
|
}
|
|
)
|
|
})
|
|
|
|
it('should request the first chunk', function () {
|
|
this.HistoryStoreManager.getChunkAtVersion
|
|
.calledWith(this.projectId, this.historyId, this.toVersion)
|
|
.should.equal(true)
|
|
})
|
|
|
|
it('should request the second chunk, from where the first one started', function () {
|
|
this.HistoryStoreManager.getChunkAtVersion
|
|
.calledWith(
|
|
this.projectId,
|
|
this.historyId,
|
|
this.chunk1.chunk.startVersion
|
|
)
|
|
.should.equal(true)
|
|
})
|
|
|
|
it('should not request the third chunk', function () {
|
|
this.HistoryStoreManager.getChunkAtVersion
|
|
.calledWith(
|
|
this.projectId,
|
|
this.historyId,
|
|
this.chunk2.chunk.startVersion
|
|
)
|
|
.should.equal(false)
|
|
})
|
|
|
|
it('should return an error', function () {
|
|
expect(this.error).to.exist
|
|
expect(this.error.message).to.equal('Diff spans too many chunks')
|
|
expect(this.error.name).to.equal('BadRequestError')
|
|
})
|
|
})
|
|
|
|
describe('where fromVersion == toVersion', function () {
|
|
beforeEach(function (done) {
|
|
this.fromVersion = 4
|
|
this.toVersion = 4
|
|
this.chunk = {
|
|
chunk: {
|
|
startVersion: 2,
|
|
}, // before fromVersion
|
|
}
|
|
this.HistoryStoreManager.getChunkAtVersion
|
|
.withArgs(this.projectId, this.historyId, this.toVersion)
|
|
.yields(null, this.chunk)
|
|
this.DiffManager._mocks._getChunks(
|
|
this.projectId,
|
|
this.fromVersion,
|
|
this.toVersion,
|
|
(error, chunks) => {
|
|
this.error = error
|
|
this.chunks = chunks
|
|
done()
|
|
}
|
|
)
|
|
})
|
|
|
|
it('should still request the first chunk (because we need the file contents)', function () {
|
|
this.HistoryStoreManager.getChunkAtVersion
|
|
.calledWith(this.projectId, this.historyId, this.toVersion)
|
|
.should.equal(true)
|
|
})
|
|
|
|
it('should return an array of chunks', function () {
|
|
expect(this.chunks).to.deep.equal([this.chunk])
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('_concatChunks', function () {
|
|
it('should concat the chunks in reverse order', function () {
|
|
const result = this.DiffManager._mocks._concatChunks([
|
|
{
|
|
chunk: {
|
|
history: {
|
|
snapshot: {
|
|
files: {
|
|
mock: 'files-updated-2',
|
|
},
|
|
},
|
|
changes: [7, 8, 9],
|
|
},
|
|
},
|
|
},
|
|
{
|
|
chunk: {
|
|
history: {
|
|
snapshot: {
|
|
files: {
|
|
mock: 'files-updated',
|
|
},
|
|
},
|
|
changes: [4, 5, 6],
|
|
},
|
|
},
|
|
},
|
|
{
|
|
chunk: {
|
|
history: {
|
|
snapshot: {
|
|
files: {
|
|
mock: 'files-original',
|
|
},
|
|
},
|
|
changes: [1, 2, 3],
|
|
},
|
|
},
|
|
},
|
|
])
|
|
|
|
expect(result).to.deep.equal({
|
|
chunk: {
|
|
history: {
|
|
snapshot: {
|
|
files: {
|
|
mock: 'files-original',
|
|
},
|
|
},
|
|
changes: [1, 2, 3, 4, 5, 6, 7, 8, 9],
|
|
},
|
|
},
|
|
})
|
|
})
|
|
})
|
|
})
|