| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520 |
- 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.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/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],
- },
- },
- })
- })
- })
- })
|