| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689 |
- /* eslint-disable
- no-return-assign,
- no-unused-vars,
- */
- // TODO: This file was created by bulk-decaffeinate.
- // Fix any style issues and re-enable lint.
- /*
- * decaffeinate suggestions:
- * DS101: Remove unnecessary use of Array.from
- * DS102: Remove unnecessary code created because of implicit returns
- * DS206: Consider reworking classes to avoid initClass
- * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
- */
- const sinon = require('sinon')
- const modulePath = '../../../../app/js/UpdateManager.js'
- const SandboxedModule = require('sandboxed-module')
- describe('UpdateManager', function () {
- beforeEach(function () {
- let Profiler, Timer
- this.project_id = 'project-id-123'
- this.projectHistoryId = 'history-id-123'
- this.doc_id = 'document-id-123'
- this.callback = sinon.stub()
- return (this.UpdateManager = SandboxedModule.require(modulePath, {
- requires: {
- './LockManager': (this.LockManager = {}),
- './RedisManager': (this.RedisManager = {}),
- './RealTimeRedisManager': (this.RealTimeRedisManager = {}),
- './ShareJsUpdateManager': (this.ShareJsUpdateManager = {}),
- './HistoryManager': (this.HistoryManager = {}),
- './Metrics': (this.Metrics = {
- Timer: (Timer = (function () {
- Timer = class Timer {
- static initClass() {
- this.prototype.done = sinon.stub()
- }
- }
- Timer.initClass()
- return Timer
- })()),
- }),
- '@overleaf/settings': (this.Settings = {}),
- './DocumentManager': (this.DocumentManager = {}),
- './RangesManager': (this.RangesManager = {}),
- './SnapshotManager': (this.SnapshotManager = {}),
- './Profiler': (Profiler = (function () {
- Profiler = class Profiler {
- static initClass() {
- this.prototype.log = sinon.stub().returns({ end: sinon.stub() })
- this.prototype.end = sinon.stub()
- }
- }
- Profiler.initClass()
- return Profiler
- })()),
- },
- }))
- })
- describe('processOutstandingUpdates', function () {
- beforeEach(function () {
- this.UpdateManager.fetchAndApplyUpdates = sinon.stub().callsArg(2)
- return this.UpdateManager.processOutstandingUpdates(
- this.project_id,
- this.doc_id,
- this.callback
- )
- })
- it('should apply the updates', function () {
- return this.UpdateManager.fetchAndApplyUpdates
- .calledWith(this.project_id, this.doc_id)
- .should.equal(true)
- })
- it('should call the callback', function () {
- return this.callback.called.should.equal(true)
- })
- return it('should time the execution', function () {
- return this.Metrics.Timer.prototype.done.called.should.equal(true)
- })
- })
- describe('processOutstandingUpdatesWithLock', function () {
- describe('when the lock is free', function () {
- beforeEach(function () {
- this.LockManager.tryLock = sinon
- .stub()
- .callsArgWith(1, null, true, (this.lockValue = 'mock-lock-value'))
- this.LockManager.releaseLock = sinon.stub().callsArg(2)
- this.UpdateManager.continueProcessingUpdatesWithLock = sinon
- .stub()
- .callsArg(2)
- return (this.UpdateManager.processOutstandingUpdates = sinon
- .stub()
- .callsArg(2))
- })
- describe('successfully', function () {
- beforeEach(function () {
- return this.UpdateManager.processOutstandingUpdatesWithLock(
- this.project_id,
- this.doc_id,
- this.callback
- )
- })
- it('should acquire the lock', function () {
- return this.LockManager.tryLock
- .calledWith(this.doc_id)
- .should.equal(true)
- })
- it('should free the lock', function () {
- return this.LockManager.releaseLock
- .calledWith(this.doc_id, this.lockValue)
- .should.equal(true)
- })
- it('should process the outstanding updates', function () {
- return this.UpdateManager.processOutstandingUpdates
- .calledWith(this.project_id, this.doc_id)
- .should.equal(true)
- })
- it('should do everything with the lock acquired', function () {
- this.UpdateManager.processOutstandingUpdates
- .calledAfter(this.LockManager.tryLock)
- .should.equal(true)
- return this.UpdateManager.processOutstandingUpdates
- .calledBefore(this.LockManager.releaseLock)
- .should.equal(true)
- })
- it('should continue processing new updates that may have come in', function () {
- return this.UpdateManager.continueProcessingUpdatesWithLock
- .calledWith(this.project_id, this.doc_id)
- .should.equal(true)
- })
- return it('should return the callback', function () {
- return this.callback.called.should.equal(true)
- })
- })
- return describe('when processOutstandingUpdates returns an error', function () {
- beforeEach(function () {
- this.UpdateManager.processOutstandingUpdates = sinon
- .stub()
- .callsArgWith(2, (this.error = new Error('Something went wrong')))
- return this.UpdateManager.processOutstandingUpdatesWithLock(
- this.project_id,
- this.doc_id,
- this.callback
- )
- })
- it('should free the lock', function () {
- return this.LockManager.releaseLock
- .calledWith(this.doc_id, this.lockValue)
- .should.equal(true)
- })
- return it('should return the error in the callback', function () {
- return this.callback.calledWith(this.error).should.equal(true)
- })
- })
- })
- return describe('when the lock is taken', function () {
- beforeEach(function () {
- this.LockManager.tryLock = sinon.stub().callsArgWith(1, null, false)
- this.UpdateManager.processOutstandingUpdates = sinon.stub().callsArg(2)
- return this.UpdateManager.processOutstandingUpdatesWithLock(
- this.project_id,
- this.doc_id,
- this.callback
- )
- })
- it('should return the callback', function () {
- return this.callback.called.should.equal(true)
- })
- return it('should not process the updates', function () {
- return this.UpdateManager.processOutstandingUpdates.called.should.equal(
- false
- )
- })
- })
- })
- describe('continueProcessingUpdatesWithLock', function () {
- describe('when there are outstanding updates', function () {
- beforeEach(function () {
- this.RealTimeRedisManager.getUpdatesLength = sinon
- .stub()
- .callsArgWith(1, null, 3)
- this.UpdateManager.processOutstandingUpdatesWithLock = sinon
- .stub()
- .callsArg(2)
- return this.UpdateManager.continueProcessingUpdatesWithLock(
- this.project_id,
- this.doc_id,
- this.callback
- )
- })
- it('should process the outstanding updates', function () {
- return this.UpdateManager.processOutstandingUpdatesWithLock
- .calledWith(this.project_id, this.doc_id)
- .should.equal(true)
- })
- return it('should return the callback', function () {
- return this.callback.called.should.equal(true)
- })
- })
- return describe('when there are no outstanding updates', function () {
- beforeEach(function () {
- this.RealTimeRedisManager.getUpdatesLength = sinon
- .stub()
- .callsArgWith(1, null, 0)
- this.UpdateManager.processOutstandingUpdatesWithLock = sinon
- .stub()
- .callsArg(2)
- return this.UpdateManager.continueProcessingUpdatesWithLock(
- this.project_id,
- this.doc_id,
- this.callback
- )
- })
- it('should not try to process the outstanding updates', function () {
- return this.UpdateManager.processOutstandingUpdatesWithLock.called.should.equal(
- false
- )
- })
- return it('should return the callback', function () {
- return this.callback.called.should.equal(true)
- })
- })
- })
- describe('fetchAndApplyUpdates', function () {
- describe('with updates', function () {
- beforeEach(function () {
- this.updates = [{ p: 1, t: 'foo' }]
- this.updatedDocLines = ['updated', 'lines']
- this.version = 34
- this.RealTimeRedisManager.getPendingUpdatesForDoc = sinon
- .stub()
- .callsArgWith(1, null, this.updates)
- this.UpdateManager.applyUpdate = sinon
- .stub()
- .callsArgWith(3, null, this.updatedDocLines, this.version)
- return this.UpdateManager.fetchAndApplyUpdates(
- this.project_id,
- this.doc_id,
- this.callback
- )
- })
- it('should get the pending updates', function () {
- return this.RealTimeRedisManager.getPendingUpdatesForDoc
- .calledWith(this.doc_id)
- .should.equal(true)
- })
- it('should apply the updates', function () {
- return Array.from(this.updates).map(update =>
- this.UpdateManager.applyUpdate
- .calledWith(this.project_id, this.doc_id, update)
- .should.equal(true)
- )
- })
- return it('should call the callback', function () {
- return this.callback.called.should.equal(true)
- })
- })
- return describe('when there are no updates', function () {
- beforeEach(function () {
- this.updates = []
- this.RealTimeRedisManager.getPendingUpdatesForDoc = sinon
- .stub()
- .callsArgWith(1, null, this.updates)
- this.UpdateManager.applyUpdate = sinon.stub()
- this.RedisManager.setDocument = sinon.stub()
- return this.UpdateManager.fetchAndApplyUpdates(
- this.project_id,
- this.doc_id,
- this.callback
- )
- })
- it('should not call applyUpdate', function () {
- return this.UpdateManager.applyUpdate.called.should.equal(false)
- })
- return it('should call the callback', function () {
- return this.callback.called.should.equal(true)
- })
- })
- })
- describe('applyUpdate', function () {
- beforeEach(function () {
- this.updateMeta = { user_id: 'last-author-fake-id' }
- this.update = { op: [{ p: 42, i: 'foo' }], meta: this.updateMeta }
- this.updatedDocLines = ['updated', 'lines']
- this.version = 34
- this.lines = ['original', 'lines']
- this.ranges = { entries: 'mock', comments: 'mock' }
- this.updated_ranges = { entries: 'updated', comments: 'updated' }
- this.appliedOps = [
- { v: 42, op: 'mock-op-42' },
- { v: 45, op: 'mock-op-45' },
- ]
- this.doc_ops_length = sinon.stub()
- this.project_ops_length = sinon.stub()
- this.pathname = '/a/b/c.tex'
- this.DocumentManager.getDoc = sinon
- .stub()
- .yields(
- null,
- this.lines,
- this.version,
- this.ranges,
- this.pathname,
- this.projectHistoryId
- )
- this.RangesManager.applyUpdate = sinon
- .stub()
- .yields(null, this.updated_ranges, false)
- this.ShareJsUpdateManager.applyUpdate = sinon
- .stub()
- .yields(null, this.updatedDocLines, this.version, this.appliedOps)
- this.RedisManager.updateDocument = sinon
- .stub()
- .yields(null, this.doc_ops_length, this.project_ops_length)
- this.RealTimeRedisManager.sendData = sinon.stub()
- this.UpdateManager._addProjectHistoryMetadataToOps = sinon.stub()
- return (this.HistoryManager.recordAndFlushHistoryOps = sinon
- .stub()
- .callsArg(5))
- })
- describe('normally', function () {
- beforeEach(function () {
- return this.UpdateManager.applyUpdate(
- this.project_id,
- this.doc_id,
- this.update,
- this.callback
- )
- })
- it('should apply the updates via ShareJS', function () {
- return this.ShareJsUpdateManager.applyUpdate
- .calledWith(
- this.project_id,
- this.doc_id,
- this.update,
- this.lines,
- this.version
- )
- .should.equal(true)
- })
- it('should update the ranges', function () {
- return this.RangesManager.applyUpdate
- .calledWith(
- this.project_id,
- this.doc_id,
- this.ranges,
- this.appliedOps,
- this.updatedDocLines
- )
- .should.equal(true)
- })
- it('should save the document', function () {
- return this.RedisManager.updateDocument
- .calledWith(
- this.project_id,
- this.doc_id,
- this.updatedDocLines,
- this.version,
- this.appliedOps,
- this.updated_ranges,
- this.updateMeta
- )
- .should.equal(true)
- })
- it('should add metadata to the ops', function () {
- return this.UpdateManager._addProjectHistoryMetadataToOps
- .calledWith(
- this.appliedOps,
- this.pathname,
- this.projectHistoryId,
- this.lines
- )
- .should.equal(true)
- })
- it('should push the applied ops into the history queue', function () {
- return this.HistoryManager.recordAndFlushHistoryOps
- .calledWith(
- this.project_id,
- this.doc_id,
- this.appliedOps,
- this.doc_ops_length,
- this.project_ops_length
- )
- .should.equal(true)
- })
- return it('should call the callback', function () {
- return this.callback.called.should.equal(true)
- })
- })
- describe('with UTF-16 surrogate pairs in the update', function () {
- beforeEach(function () {
- this.update = { op: [{ p: 42, i: '\uD835\uDC00' }] }
- return this.UpdateManager.applyUpdate(
- this.project_id,
- this.doc_id,
- this.update,
- this.callback
- )
- })
- return it('should apply the update but with surrogate pairs removed', function () {
- this.ShareJsUpdateManager.applyUpdate
- .calledWith(this.project_id, this.doc_id, this.update)
- .should.equal(true)
- // \uFFFD is 'replacement character'
- return this.update.op[0].i.should.equal('\uFFFD\uFFFD')
- })
- })
- describe('with an error', function () {
- beforeEach(function () {
- this.error = new Error('something went wrong')
- this.ShareJsUpdateManager.applyUpdate = sinon.stub().yields(this.error)
- return this.UpdateManager.applyUpdate(
- this.project_id,
- this.doc_id,
- this.update,
- this.callback
- )
- })
- it('should call RealTimeRedisManager.sendData with the error', function () {
- return this.RealTimeRedisManager.sendData
- .calledWith({
- project_id: this.project_id,
- doc_id: this.doc_id,
- error: this.error.message,
- })
- .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 ranges get collapsed', function () {
- beforeEach(function () {
- this.RangesManager.applyUpdate = sinon
- .stub()
- .yields(null, this.updated_ranges, true)
- this.SnapshotManager.recordSnapshot = sinon.stub().yields()
- return this.UpdateManager.applyUpdate(
- this.project_id,
- this.doc_id,
- this.update,
- this.callback
- )
- })
- return it('should call SnapshotManager.recordSnapshot', function () {
- return this.SnapshotManager.recordSnapshot
- .calledWith(
- this.project_id,
- this.doc_id,
- this.version,
- this.pathname,
- this.lines,
- this.ranges
- )
- .should.equal(true)
- })
- })
- })
- describe('_addProjectHistoryMetadataToOps', function () {
- return it('should add projectHistoryId, pathname and doc_length metadata to the ops', function () {
- const lines = ['some', 'test', 'data']
- const appliedOps = [
- {
- v: 42,
- op: [
- { i: 'foo', p: 4 },
- { i: 'bar', p: 6 },
- ],
- },
- {
- v: 45,
- op: [
- { d: 'qux', p: 4 },
- { i: 'bazbaz', p: 14 },
- ],
- },
- { v: 49, op: [{ i: 'penguin', p: 18 }] },
- ]
- this.UpdateManager._addProjectHistoryMetadataToOps(
- appliedOps,
- this.pathname,
- this.projectHistoryId,
- lines
- )
- return appliedOps.should.deep.equal([
- {
- projectHistoryId: this.projectHistoryId,
- v: 42,
- op: [
- { i: 'foo', p: 4 },
- { i: 'bar', p: 6 },
- ],
- meta: {
- pathname: this.pathname,
- doc_length: 14,
- },
- },
- {
- projectHistoryId: this.projectHistoryId,
- v: 45,
- op: [
- { d: 'qux', p: 4 },
- { i: 'bazbaz', p: 14 },
- ],
- meta: {
- pathname: this.pathname,
- doc_length: 20,
- }, // 14 + 'foo' + 'bar'
- },
- {
- projectHistoryId: this.projectHistoryId,
- v: 49,
- op: [{ i: 'penguin', p: 18 }],
- meta: {
- pathname: this.pathname,
- doc_length: 23,
- }, // 14 - 'qux' + 'bazbaz'
- },
- ])
- })
- })
- return describe('lockUpdatesAndDo', function () {
- beforeEach(function () {
- this.method = sinon.stub().callsArgWith(3, null, this.response_arg1)
- this.callback = sinon.stub()
- this.arg1 = 'argument 1'
- this.response_arg1 = 'response argument 1'
- this.lockValue = 'mock-lock-value'
- this.LockManager.getLock = sinon
- .stub()
- .callsArgWith(1, null, this.lockValue)
- return (this.LockManager.releaseLock = sinon.stub().callsArg(2))
- })
- describe('successfully', function () {
- beforeEach(function () {
- this.UpdateManager.continueProcessingUpdatesWithLock = sinon.stub()
- this.UpdateManager.processOutstandingUpdates = sinon.stub().callsArg(2)
- return this.UpdateManager.lockUpdatesAndDo(
- this.method,
- this.project_id,
- this.doc_id,
- this.arg1,
- this.callback
- )
- })
- it('should lock the doc', function () {
- return this.LockManager.getLock
- .calledWith(this.doc_id)
- .should.equal(true)
- })
- it('should process any outstanding updates', function () {
- return this.UpdateManager.processOutstandingUpdates
- .calledWith(this.project_id, this.doc_id)
- .should.equal(true)
- })
- it('should call the method', function () {
- return this.method
- .calledWith(this.project_id, this.doc_id, this.arg1)
- .should.equal(true)
- })
- it('should return the method response to the callback', function () {
- return this.callback
- .calledWith(null, this.response_arg1)
- .should.equal(true)
- })
- it('should release the lock', function () {
- return this.LockManager.releaseLock
- .calledWith(this.doc_id, this.lockValue)
- .should.equal(true)
- })
- return it('should continue processing updates', function () {
- return this.UpdateManager.continueProcessingUpdatesWithLock
- .calledWith(this.project_id, this.doc_id)
- .should.equal(true)
- })
- })
- describe('when processOutstandingUpdates returns an error', function () {
- beforeEach(function () {
- this.UpdateManager.processOutstandingUpdates = sinon
- .stub()
- .callsArgWith(2, (this.error = new Error('Something went wrong')))
- return this.UpdateManager.lockUpdatesAndDo(
- this.method,
- this.project_id,
- this.doc_id,
- this.arg1,
- this.callback
- )
- })
- it('should free the lock', function () {
- return this.LockManager.releaseLock
- .calledWith(this.doc_id, this.lockValue)
- .should.equal(true)
- })
- return it('should return the error in the callback', function () {
- return this.callback.calledWith(this.error).should.equal(true)
- })
- })
- return describe('when the method returns an error', function () {
- beforeEach(function () {
- this.UpdateManager.processOutstandingUpdates = sinon.stub().callsArg(2)
- this.method = sinon
- .stub()
- .callsArgWith(
- 3,
- (this.error = new Error('something went wrong')),
- this.response_arg1
- )
- return this.UpdateManager.lockUpdatesAndDo(
- this.method,
- this.project_id,
- this.doc_id,
- this.arg1,
- this.callback
- )
- })
- it('should free the lock', function () {
- return this.LockManager.releaseLock
- .calledWith(this.doc_id, this.lockValue)
- .should.equal(true)
- })
- return it('should return the error in the callback', function () {
- return this.callback.calledWith(this.error).should.equal(true)
- })
- })
- })
- })
|