/* eslint-disable camelcase, no-return-assign, */ // 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 * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ const SandboxedModule = require('sandboxed-module') const sinon = require('sinon') require('chai').should() const modulePath = require('path').join( __dirname, '../../../app/js/DocumentUpdaterController' ) const MockClient = require('./helpers/MockClient') describe('DocumentUpdaterController', function () { beforeEach(function () { this.project_id = 'project-id-123' this.doc_id = 'doc-id-123' this.callback = sinon.stub() this.io = { mock: 'socket.io' } this.rclient = [] this.RoomEvents = { on: sinon.stub() } return (this.EditorUpdatesController = SandboxedModule.require(modulePath, { requires: { 'logger-sharelatex': (this.logger = { error: sinon.stub(), log: sinon.stub(), warn: sinon.stub() }), 'settings-sharelatex': (this.settings = { redis: { documentupdater: { key_schema: { pendingUpdates({ doc_id }) { return `PendingUpdates:${doc_id}` } } }, pubsub: null } }), 'redis-sharelatex': (this.redis = { createClient: (name) => { let rclientStub this.rclient.push((rclientStub = { name })) return rclientStub } }), './SafeJsonParse': (this.SafeJsonParse = { parse: (data, cb) => cb(null, JSON.parse(data)) }), './EventLogger': (this.EventLogger = { checkEventOrder: sinon.stub() }), './HealthCheckManager': { check: sinon.stub() }, 'metrics-sharelatex': (this.metrics = { inc: sinon.stub() }), './RoomManager': (this.RoomManager = { eventSource: sinon.stub().returns(this.RoomEvents) }), './ChannelManager': (this.ChannelManager = {}) } })) }) describe('listenForUpdatesFromDocumentUpdater', function () { beforeEach(function () { this.rclient.length = 0 // clear any existing clients this.EditorUpdatesController.rclientList = [ this.redis.createClient('first'), this.redis.createClient('second') ] this.rclient[0].subscribe = sinon.stub() this.rclient[0].on = sinon.stub() this.rclient[1].subscribe = sinon.stub() this.rclient[1].on = sinon.stub() return this.EditorUpdatesController.listenForUpdatesFromDocumentUpdater() }) it('should subscribe to the doc-updater stream', function () { return this.rclient[0].subscribe .calledWith('applied-ops') .should.equal(true) }) it('should register a callback to handle updates', function () { return this.rclient[0].on.calledWith('message').should.equal(true) }) return it('should subscribe to any additional doc-updater stream', function () { this.rclient[1].subscribe.calledWith('applied-ops').should.equal(true) return this.rclient[1].on.calledWith('message').should.equal(true) }) }) describe('_processMessageFromDocumentUpdater', function () { describe('with bad JSON', function () { beforeEach(function () { this.SafeJsonParse.parse = sinon .stub() .callsArgWith(1, new Error('oops')) return this.EditorUpdatesController._processMessageFromDocumentUpdater( this.io, 'applied-ops', 'blah' ) }) return it('should log an error', function () { return this.logger.error.called.should.equal(true) }) }) describe('with update', function () { beforeEach(function () { this.message = { doc_id: this.doc_id, op: { t: 'foo', p: 12 } } this.EditorUpdatesController._applyUpdateFromDocumentUpdater = sinon.stub() return this.EditorUpdatesController._processMessageFromDocumentUpdater( this.io, 'applied-ops', JSON.stringify(this.message) ) }) return it('should apply the update', function () { return this.EditorUpdatesController._applyUpdateFromDocumentUpdater .calledWith(this.io, this.doc_id, this.message.op) .should.equal(true) }) }) return describe('with error', function () { beforeEach(function () { this.message = { doc_id: this.doc_id, error: 'Something went wrong' } this.EditorUpdatesController._processErrorFromDocumentUpdater = sinon.stub() return this.EditorUpdatesController._processMessageFromDocumentUpdater( this.io, 'applied-ops', JSON.stringify(this.message) ) }) return it('should process the error', function () { return this.EditorUpdatesController._processErrorFromDocumentUpdater .calledWith(this.io, this.doc_id, this.message.error) .should.equal(true) }) }) }) describe('_applyUpdateFromDocumentUpdater', function () { beforeEach(function () { this.sourceClient = new MockClient() this.otherClients = [new MockClient(), new MockClient()] this.update = { op: [{ t: 'foo', p: 12 }], meta: { source: this.sourceClient.publicId }, v: (this.version = 42), doc: this.doc_id } return (this.io.sockets = { clients: sinon .stub() .returns([ this.sourceClient, ...Array.from(this.otherClients), this.sourceClient ]) }) }) // include a duplicate client describe('normally', function () { beforeEach(function () { return this.EditorUpdatesController._applyUpdateFromDocumentUpdater( this.io, this.doc_id, this.update ) }) it('should send a version bump to the source client', function () { this.sourceClient.emit .calledWith('otUpdateApplied', { v: this.version, doc: this.doc_id }) .should.equal(true) return this.sourceClient.emit.calledOnce.should.equal(true) }) it('should get the clients connected to the document', function () { return this.io.sockets.clients .calledWith(this.doc_id) .should.equal(true) }) return it('should send the full update to the other clients', function () { return Array.from(this.otherClients).map((client) => client.emit .calledWith('otUpdateApplied', this.update) .should.equal(true) ) }) }) return describe('with a duplicate op', function () { beforeEach(function () { this.update.dup = true return this.EditorUpdatesController._applyUpdateFromDocumentUpdater( this.io, this.doc_id, this.update ) }) it('should send a version bump to the source client as usual', function () { return this.sourceClient.emit .calledWith('otUpdateApplied', { v: this.version, doc: this.doc_id }) .should.equal(true) }) return it("should not send anything to the other clients (they've already had the op)", function () { return Array.from(this.otherClients).map((client) => client.emit.calledWith('otUpdateApplied').should.equal(false) ) }) }) }) return describe('_processErrorFromDocumentUpdater', function () { beforeEach(function () { this.clients = [new MockClient(), new MockClient()] this.io.sockets = { clients: sinon.stub().returns(this.clients) } return this.EditorUpdatesController._processErrorFromDocumentUpdater( this.io, this.doc_id, 'Something went wrong' ) }) it('should log a warning', function () { return this.logger.warn.called.should.equal(true) }) return it('should disconnect all clients in that document', function () { this.io.sockets.clients.calledWith(this.doc_id).should.equal(true) return Array.from(this.clients).map((client) => client.disconnect.called.should.equal(true) ) }) }) })