/* 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: * 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 modulePath = '../../../../app/js/ShareJsUpdateManager.js' const SandboxedModule = require('sandboxed-module') const crypto = require('node:crypto') describe('ShareJsUpdateManager', function () { beforeEach(function () { let Model this.project_id = 'project-id-123' this.doc_id = 'document-id-123' this.callback = sinon.stub() return (this.ShareJsUpdateManager = SandboxedModule.require(modulePath, { requires: { './sharejs/server/model': (Model = class Model { constructor(db) { this.db = db } }), './ShareJsDB': (this.ShareJsDB = { mockDB: true }), '@overleaf/redis-wrapper': { createClient: () => { return (this.rclient = { auth() {} }) }, }, './RealTimeRedisManager': (this.RealTimeRedisManager = { sendCanaryAppliedOp: sinon.stub(), }), './Metrics': (this.metrics = { inc: sinon.stub() }), }, globals: { clearTimeout: (this.clearTimeout = sinon.stub()), }, })) }) describe('applyUpdate', function () { beforeEach(function () { this.lines = ['one', 'two'] this.version = 34 this.updatedDocLines = ['onefoo', 'two'] const content = this.updatedDocLines.join('\n') this.hash = crypto .createHash('sha1') .update('blob ' + content.length + '\x00') .update(content, 'utf8') .digest('hex') this.update = { p: 4, t: 'foo', v: this.version, hash: this.hash } this.model = { applyOp: sinon.stub().callsArg(2), getSnapshot: sinon.stub(), db: { appliedOps: {}, }, } this.ShareJsUpdateManager.getNewShareJsModel = sinon .stub() .returns(this.model) this.ShareJsUpdateManager._listenForOps = sinon.stub() return (this.ShareJsUpdateManager.removeDocFromCache = sinon .stub() .callsArg(1)) }) describe('successfully', function () { beforeEach(function (done) { this.model.getSnapshot.callsArgWith(1, null, { snapshot: this.updatedDocLines.join('\n'), v: this.version, }) this.model.db.appliedOps[`${this.project_id}:${this.doc_id}`] = this.appliedOps = ['mock-ops'] return this.ShareJsUpdateManager.applyUpdate( this.project_id, this.doc_id, this.update, this.lines, this.version, (err, docLines, version, appliedOps) => { this.callback(err, docLines, version, appliedOps) return done() } ) }) it('should create a new ShareJs model', function () { return this.ShareJsUpdateManager.getNewShareJsModel .calledWith(this.project_id, this.doc_id, this.lines, this.version) .should.equal(true) }) it('should listen for ops on the model', function () { return this.ShareJsUpdateManager._listenForOps .calledWith(this.model) .should.equal(true) }) it('should send the update to ShareJs', function () { return this.model.applyOp .calledWith(`${this.project_id}:${this.doc_id}`, this.update) .should.equal(true) }) it('should get the updated doc lines', function () { return this.model.getSnapshot .calledWith(`${this.project_id}:${this.doc_id}`) .should.equal(true) }) return it('should return the updated doc lines, version and ops', function () { return this.callback .calledWith(null, this.updatedDocLines, this.version, this.appliedOps) .should.equal(true) }) }) describe('when applyOp fails', function () { beforeEach(function (done) { this.error = new Error('Something went wrong') this.model.applyOp = sinon.stub().callsArgWith(2, this.error) return this.ShareJsUpdateManager.applyUpdate( this.project_id, this.doc_id, this.update, this.lines, this.version, (err, docLines, version) => { this.callback(err, docLines, version) return done() } ) }) return it('should call the callback with the error', function () { return this.callback.calledWith(this.error).should.equal(true) }) }) describe('when getSnapshot fails', function () { beforeEach(function (done) { this.error = new Error('Something went wrong') this.model.getSnapshot.callsArgWith(1, this.error) return this.ShareJsUpdateManager.applyUpdate( this.project_id, this.doc_id, this.update, this.lines, this.version, (err, docLines, version) => { this.callback(err, docLines, version) return done() } ) }) return it('should call the callback with the error', function () { return this.callback.calledWith(this.error).should.equal(true) }) }) return describe('with an invalid hash', function () { beforeEach(function (done) { this.error = new Error('invalid hash') this.model.getSnapshot.callsArgWith(1, null, { snapshot: 'unexpected content', v: this.version, }) this.model.db.appliedOps[`${this.project_id}:${this.doc_id}`] = this.appliedOps = ['mock-ops'] return this.ShareJsUpdateManager.applyUpdate( this.project_id, this.doc_id, this.update, this.lines, this.version, (err, docLines, version, appliedOps) => { this.callback(err, docLines, version, appliedOps) return done() } ) }) return it('should call the callback with the error', function () { return this.callback .calledWith(sinon.match.instanceOf(Error)) .should.equal(true) }) }) }) return describe('_listenForOps', function () { beforeEach(function () { this.model = { on: (event, callback) => { return (this.callback = callback) }, } sinon.spy(this.model, 'on') return this.ShareJsUpdateManager._listenForOps(this.model) }) it('should listen to the model for updates', function () { return this.model.on.calledWith('applyOp').should.equal(true) }) return describe('the callback', function () { beforeEach(function () { this.opData = { op: { t: 'foo', p: 1 }, meta: { source: 'bar' }, } this.RealTimeRedisManager.sendData = sinon.stub() return this.callback(`${this.project_id}:${this.doc_id}`, this.opData) }) return it('should publish the op to redis', function () { return this.RealTimeRedisManager.sendData .calledWith({ project_id: this.project_id, doc_id: this.doc_id, op: this.opData, }) .should.equal(true) }) }) }) })