/* eslint-disable
    n/handle-callback-err,
    max-len,
    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 SandboxedModule = require('sandboxed-module')
const sinon = require('sinon')
const { expect } = require('chai')
const OError = require('@overleaf/o-error')

const modulePath = require('path').join(
  __dirname,
  '../../../../app/src/Features/Editor/EditorController'
)
const MockClient = require('../helpers/MockClient')
const assert = require('assert')
const { ObjectId } = require('mongodb-legacy')

describe('EditorController', function () {
  beforeEach(function () {
    this.project_id = 'test-project-id'
    this.source = 'dropbox'
    this.user_id = new ObjectId()

    this.doc = { _id: (this.doc_id = 'test-doc-id') }
    this.docName = 'doc.tex'
    this.docLines = ['1234', 'dskl']
    this.file = { _id: (this.file_id = 'dasdkjk') }
    this.fileName = 'file.png'
    this.fsPath = '/folder/file.png'
    this.linkedFileData = { provider: 'url' }

    this.newFile = { _id: 'new-file-id' }

    this.folder_id = '123ksajdn'
    this.folder = { _id: this.folder_id }
    this.folderName = 'folder'

    this.callback = sinon.stub()

    return (this.EditorController = SandboxedModule.require(modulePath, {
      requires: {
        '../Project/ProjectEntityUpdateHandler':
          (this.ProjectEntityUpdateHandler = {}),
        '../Project/ProjectOptionsHandler': (this.ProjectOptionsHandler = {
          setCompiler: sinon.stub().yields(),
          setImageName: sinon.stub().yields(),
          setSpellCheckLanguage: sinon.stub().yields(),
        }),
        '../Project/ProjectDetailsHandler': (this.ProjectDetailsHandler = {
          setProjectDescription: sinon.stub().yields(),
          renameProject: sinon.stub().yields(),
          setPublicAccessLevel: sinon.stub().yields(),
        }),
        '../Project/ProjectDeleter': (this.ProjectDeleter = {}),
        '../DocumentUpdater/DocumentUpdaterHandler':
          (this.DocumentUpdaterHandler = {
            flushDocToMongo: sinon.stub().yields(),
            setDocument: sinon.stub().yields(),
          }),
        './EditorRealTimeController': (this.EditorRealTimeController = {
          emitToRoom: sinon.stub(),
        }),
        '@overleaf/metrics': (this.Metrics = { inc: sinon.stub() }),
      },
    }))
  })

  describe('addDoc', function () {
    beforeEach(function () {
      this.ProjectEntityUpdateHandler.addDocWithRanges = sinon
        .stub()
        .yields(null, this.doc, this.folder_id)
      return this.EditorController.addDoc(
        this.project_id,
        this.folder_id,
        this.docName,
        this.docLines,
        this.source,
        this.user_id,
        this.callback
      )
    })

    it('should add the doc using the project entity handler', function () {
      return this.ProjectEntityUpdateHandler.addDocWithRanges
        .calledWith(
          this.project_id,
          this.folder_id,
          this.docName,
          this.docLines,
          {},
          this.user_id,
          this.source
        )
        .should.equal(true)
    })

    it('should send the update out to the users in the project', function () {
      return this.EditorRealTimeController.emitToRoom
        .calledWith(
          this.project_id,
          'reciveNewDoc',
          this.folder_id,
          this.doc,
          this.source,
          this.user_id
        )
        .should.equal(true)
    })

    it('calls the callback', function () {
      return this.callback.calledWith(null, this.doc).should.equal(true)
    })
  })

  describe('addFile', function () {
    beforeEach(function () {
      this.ProjectEntityUpdateHandler.addFile = sinon
        .stub()
        .yields(null, this.file, this.folder_id)
      return this.EditorController.addFile(
        this.project_id,
        this.folder_id,
        this.fileName,
        this.fsPath,
        this.linkedFileData,
        this.source,
        this.user_id,
        this.callback
      )
    })

    it('should add the folder using the project entity handler', function () {
      return this.ProjectEntityUpdateHandler.addFile
        .calledWith(
          this.project_id,
          this.folder_id,
          this.fileName,
          this.fsPath,
          this.linkedFileData,
          this.user_id,
          this.source
        )
        .should.equal(true)
    })

    it('should send the update of a new folder out to the users in the project', function () {
      return this.EditorRealTimeController.emitToRoom
        .calledWith(
          this.project_id,
          'reciveNewFile',
          this.folder_id,
          this.file,
          this.source,
          this.linkedFileData,
          this.user_id
        )
        .should.equal(true)
    })

    it('calls the callback', function () {
      return this.callback.calledWith(null, this.file).should.equal(true)
    })
  })

  describe('upsertDoc', function () {
    beforeEach(function () {
      this.ProjectEntityUpdateHandler.upsertDoc = sinon
        .stub()
        .yields(null, this.doc, false)
      return this.EditorController.upsertDoc(
        this.project_id,
        this.folder_id,
        this.docName,
        this.docLines,
        this.source,
        this.user_id,
        this.callback
      )
    })

    it('upserts the doc using the project entity handler', function () {
      return this.ProjectEntityUpdateHandler.upsertDoc
        .calledWith(
          this.project_id,
          this.folder_id,
          this.docName,
          this.docLines,
          this.source
        )
        .should.equal(true)
    })

    it('returns the doc', function () {
      return this.callback.calledWith(null, this.doc).should.equal(true)
    })

    describe('doc does not exist', function () {
      beforeEach(function () {
        this.ProjectEntityUpdateHandler.upsertDoc = sinon
          .stub()
          .yields(null, this.doc, true)
        return this.EditorController.upsertDoc(
          this.project_id,
          this.folder_id,
          this.docName,
          this.docLines,
          this.source,
          this.user_id,
          this.callback
        )
      })

      it('sends an update out to users in the project', function () {
        return this.EditorRealTimeController.emitToRoom
          .calledWith(
            this.project_id,
            'reciveNewDoc',
            this.folder_id,
            this.doc,
            this.source,
            this.user_id
          )
          .should.equal(true)
      })
    })
  })

  describe('upsertFile', function () {
    beforeEach(function () {
      this.ProjectEntityUpdateHandler.upsertFile = sinon
        .stub()
        .yields(null, this.newFile, false, this.file)
      return this.EditorController.upsertFile(
        this.project_id,
        this.folder_id,
        this.fileName,
        this.fsPath,
        this.linkedFileData,
        this.source,
        this.user_id,
        this.callback
      )
    })

    it('upserts the file using the project entity handler', function () {
      return this.ProjectEntityUpdateHandler.upsertFile
        .calledWith(
          this.project_id,
          this.folder_id,
          this.fileName,
          this.fsPath,
          this.linkedFileData,
          this.user_id,
          this.source
        )
        .should.equal(true)
    })

    it('returns the file', function () {
      return this.callback.calledWith(null, this.newFile).should.equal(true)
    })

    describe('file does not exist', function () {
      beforeEach(function () {
        this.ProjectEntityUpdateHandler.upsertFile = sinon
          .stub()
          .yields(null, this.file, true)
        return this.EditorController.upsertFile(
          this.project_id,
          this.folder_id,
          this.fileName,
          this.fsPath,
          this.linkedFileData,
          this.source,
          this.user_id,
          this.callback
        )
      })

      it('should send the update out to users in the project', function () {
        return this.EditorRealTimeController.emitToRoom
          .calledWith(
            this.project_id,
            'reciveNewFile',
            this.folder_id,
            this.file,
            this.source,
            this.linkedFileData,
            this.user_id
          )
          .should.equal(true)
      })
    })
  })

  describe('upsertDocWithPath', function () {
    beforeEach(function () {
      this.docPath = '/folder/doc'

      this.ProjectEntityUpdateHandler.upsertDocWithPath = sinon
        .stub()
        .yields(null, this.doc, false, [], this.folder)
      return this.EditorController.upsertDocWithPath(
        this.project_id,
        this.docPath,
        this.docLines,
        this.source,
        this.user_id,
        this.callback
      )
    })

    it('upserts the doc using the project entity handler', function () {
      return this.ProjectEntityUpdateHandler.upsertDocWithPath
        .calledWith(this.project_id, this.docPath, this.docLines, this.source)
        .should.equal(true)
    })

    describe('doc does not exist', function () {
      beforeEach(function () {
        this.ProjectEntityUpdateHandler.upsertDocWithPath = sinon
          .stub()
          .yields(null, this.doc, true, [], this.folder)
        return this.EditorController.upsertDocWithPath(
          this.project_id,
          this.docPath,
          this.docLines,
          this.source,
          this.user_id,
          this.callback
        )
      })

      it('should send the update for the doc out to users in the project', function () {
        return this.EditorRealTimeController.emitToRoom
          .calledWith(
            this.project_id,
            'reciveNewDoc',
            this.folder_id,
            this.doc,
            this.source,
            this.user_id
          )
          .should.equal(true)
      })
    })

    describe('folders required for doc do not exist', function () {
      beforeEach(function () {
        const folders = [
          (this.folderA = { _id: 2, parentFolder_id: 1 }),
          (this.folderB = { _id: 3, parentFolder_id: 2 }),
        ]
        this.ProjectEntityUpdateHandler.upsertDocWithPath = sinon
          .stub()
          .yields(null, this.doc, true, folders, this.folderB)
        return this.EditorController.upsertDocWithPath(
          this.project_id,
          this.docPath,
          this.docLines,
          this.source,
          this.user_id,
          this.callback
        )
      })

      it('should send the update for each folder to users in the project', function () {
        this.EditorRealTimeController.emitToRoom
          .calledWith(
            this.project_id,
            'reciveNewFolder',
            this.folderA.parentFolder_id,
            this.folderA
          )
          .should.equal(true)
        return this.EditorRealTimeController.emitToRoom
          .calledWith(
            this.project_id,
            'reciveNewFolder',
            this.folderB.parentFolder_id,
            this.folderB
          )
          .should.equal(true)
      })
    })
  })

  describe('upsertFileWithPath', function () {
    beforeEach(function () {
      this.filePath = '/folder/file'

      this.ProjectEntityUpdateHandler.upsertFileWithPath = sinon
        .stub()
        .yields(null, this.newFile, false, this.file, [], this.folder)
      return this.EditorController.upsertFileWithPath(
        this.project_id,
        this.filePath,
        this.fsPath,
        this.linkedFileData,
        this.source,
        this.user_id,
        this.callback
      )
    })

    it('upserts the file using the project entity handler', function () {
      return this.ProjectEntityUpdateHandler.upsertFileWithPath
        .calledWith(
          this.project_id,
          this.filePath,
          this.fsPath,
          this.linkedFileData,
          this.user_id,
          this.source
        )
        .should.equal(true)
    })

    describe('file does not exist', function () {
      beforeEach(function () {
        this.ProjectEntityUpdateHandler.upsertFileWithPath = sinon
          .stub()
          .yields(null, this.file, true, undefined, [], this.folder)
        return this.EditorController.upsertFileWithPath(
          this.project_id,
          this.filePath,
          this.fsPath,
          this.linkedFileData,
          this.source,
          this.user_id,
          this.callback
        )
      })

      it('should send the update for the file out to users in the project', function () {
        return this.EditorRealTimeController.emitToRoom
          .calledWith(
            this.project_id,
            'reciveNewFile',
            this.folder_id,
            this.file,
            this.source,
            this.linkedFileData,
            this.user_id
          )
          .should.equal(true)
      })
    })

    describe('folders required for file do not exist', function () {
      beforeEach(function () {
        const folders = [
          (this.folderA = { _id: 2, parentFolder_id: 1 }),
          (this.folderB = { _id: 3, parentFolder_id: 2 }),
        ]
        this.ProjectEntityUpdateHandler.upsertFileWithPath = sinon
          .stub()
          .yields(null, this.file, true, undefined, folders, this.folderB)
        return this.EditorController.upsertFileWithPath(
          this.project_id,
          this.filePath,
          this.fsPath,
          this.linkedFileData,
          this.source,
          this.user_id,
          this.callback
        )
      })

      it('should send the update for each folder to users in the project', function () {
        this.EditorRealTimeController.emitToRoom
          .calledWith(
            this.project_id,
            'reciveNewFolder',
            this.folderA.parentFolder_id,
            this.folderA
          )
          .should.equal(true)
        return this.EditorRealTimeController.emitToRoom
          .calledWith(
            this.project_id,
            'reciveNewFolder',
            this.folderB.parentFolder_id,
            this.folderB
          )
          .should.equal(true)
      })
    })
  })

  describe('addFolder', function () {
    beforeEach(function () {
      this.EditorController._notifyProjectUsersOfNewFolder = sinon
        .stub()
        .yields()
      this.ProjectEntityUpdateHandler.addFolder = sinon
        .stub()
        .yields(null, this.folder, this.folder_id)
      return this.EditorController.addFolder(
        this.project_id,
        this.folder_id,
        this.folderName,
        this.source,
        this.user_id,
        this.callback
      )
    })

    it('should add the folder using the project entity handler', function () {
      return this.ProjectEntityUpdateHandler.addFolder
        .calledWith(
          this.project_id,
          this.folder_id,
          this.folderName,
          this.user_id
        )
        .should.equal(true)
    })

    it('should notifyProjectUsersOfNewFolder', function () {
      return this.EditorController._notifyProjectUsersOfNewFolder
        .calledWith(this.project_id, this.folder_id, this.folder, this.user_id)
        .should.equal(true)
    })

    it('should return the folder in the callback', function () {
      return this.callback.calledWith(null, this.folder).should.equal(true)
    })
  })

  describe('mkdirp', function () {
    beforeEach(function () {
      this.path = 'folder1/folder2'
      this.folders = [
        (this.folderA = { _id: 2, parentFolder_id: 1 }),
        (this.folderB = { _id: 3, parentFolder_id: 2 }),
      ]
      this.userId = new ObjectId().toString()
      this.EditorController._notifyProjectUsersOfNewFolders = sinon
        .stub()
        .yields()
      this.ProjectEntityUpdateHandler.mkdirp = sinon
        .stub()
        .yields(null, this.folders, this.folder)
      return this.EditorController.mkdirp(
        this.project_id,
        this.path,
        this.userId,
        this.callback
      )
    })

    it('should create the folder using the project entity handler', function () {
      return this.ProjectEntityUpdateHandler.mkdirp
        .calledWith(this.project_id, this.path, this.userId)
        .should.equal(true)
    })

    it('should notifyProjectUsersOfNewFolder', function () {
      return this.EditorController._notifyProjectUsersOfNewFolders.calledWith(
        this.project_id,
        this.folders
      )
    })

    it('should return the folder in the callback', function () {
      return this.callback
        .calledWith(null, this.folders, this.folder)
        .should.equal(true)
    })
  })

  describe('deleteEntity', function () {
    beforeEach(function () {
      this.entity_id = 'entity_id_here'
      this.type = 'doc'
      this.ProjectEntityUpdateHandler.deleteEntity = sinon.stub().yields()
      return this.EditorController.deleteEntity(
        this.project_id,
        this.entity_id,
        this.type,
        this.source,
        this.user_id,
        this.callback
      )
    })

    it('should delete the folder using the project entity handler', function () {
      return this.ProjectEntityUpdateHandler.deleteEntity
        .calledWith(
          this.project_id,
          this.entity_id,
          this.type,
          this.user_id,
          this.source
        )
        .should.equal(true)
    })

    it('notify users an entity has been deleted', function () {
      return this.EditorRealTimeController.emitToRoom
        .calledWith(
          this.project_id,
          'removeEntity',
          this.entity_id,
          this.source
        )
        .should.equal(true)
    })
  })

  describe('deleteEntityWithPath', function () {
    beforeEach(function () {
      this.entity_id = 'entity_id_here'
      this.ProjectEntityUpdateHandler.deleteEntityWithPath = sinon
        .stub()
        .yields(null, this.entity_id)
      this.path = 'folder1/folder2'
      return this.EditorController.deleteEntityWithPath(
        this.project_id,
        this.path,
        this.source,
        this.user_id,
        this.callback
      )
    })

    it('should delete the folder using the project entity handler', function () {
      return this.ProjectEntityUpdateHandler.deleteEntityWithPath
        .calledWith(this.project_id, this.path, this.user_id, this.source)
        .should.equal(true)
    })

    it('notify users an entity has been deleted', function () {
      return this.EditorRealTimeController.emitToRoom
        .calledWith(
          this.project_id,
          'removeEntity',
          this.entity_id,
          this.source
        )
        .should.equal(true)
    })
  })

  describe('updateProjectDescription', function () {
    beforeEach(function () {
      this.description = 'new description'
      return this.EditorController.updateProjectDescription(
        this.project_id,
        this.description,
        this.callback
      )
    })

    it('should send the new description to the project details handler', function () {
      return this.ProjectDetailsHandler.setProjectDescription
        .calledWith(this.project_id, this.description)
        .should.equal(true)
    })

    it('should notify the other clients about the updated description', function () {
      return this.EditorRealTimeController.emitToRoom
        .calledWith(
          this.project_id,
          'projectDescriptionUpdated',
          this.description
        )
        .should.equal(true)
    })
  })

  describe('deleteProject', function () {
    beforeEach(function () {
      this.err = 'errro'
      return (this.ProjectDeleter.deleteProject = sinon
        .stub()
        .callsArgWith(1, this.err))
    })

    it('should call the project handler', function (done) {
      return this.EditorController.deleteProject(this.project_id, err => {
        err.should.equal(this.err)
        this.ProjectDeleter.deleteProject
          .calledWith(this.project_id)
          .should.equal(true)
        return done()
      })
    })
  })

  describe('renameEntity', function () {
    beforeEach(function (done) {
      this.entity_id = 'entity_id_here'
      this.entityType = 'doc'
      this.newName = 'bobsfile.tex'
      this.ProjectEntityUpdateHandler.renameEntity = sinon.stub().yields()

      return this.EditorController.renameEntity(
        this.project_id,
        this.entity_id,
        this.entityType,
        this.newName,
        this.user_id,
        this.source,
        done
      )
    })

    it('should call the project handler', function () {
      return this.ProjectEntityUpdateHandler.renameEntity
        .calledWith(
          this.project_id,
          this.entity_id,
          this.entityType,
          this.newName,
          this.user_id,
          this.source
        )
        .should.equal(true)
    })

    it('should emit the update to the room', function () {
      return this.EditorRealTimeController.emitToRoom
        .calledWith(
          this.project_id,
          'reciveEntityRename',
          this.entity_id,
          this.newName
        )
        .should.equal(true)
    })
  })

  describe('moveEntity', function () {
    beforeEach(function () {
      this.entity_id = 'entity_id_here'
      this.entityType = 'doc'
      this.ProjectEntityUpdateHandler.moveEntity = sinon.stub().yields()
      return this.EditorController.moveEntity(
        this.project_id,
        this.entity_id,
        this.folder_id,
        this.entityType,
        this.user_id,
        this.source,
        this.callback
      )
    })

    it('should call the ProjectEntityUpdateHandler', function () {
      return this.ProjectEntityUpdateHandler.moveEntity
        .calledWith(
          this.project_id,
          this.entity_id,
          this.folder_id,
          this.entityType,
          this.user_id,
          this.source
        )
        .should.equal(true)
    })

    it('should emit the update to the room', function () {
      return this.EditorRealTimeController.emitToRoom
        .calledWith(
          this.project_id,
          'reciveEntityMove',
          this.entity_id,
          this.folder_id
        )
        .should.equal(true)
    })

    it('calls the callback', function () {
      return this.callback.called.should.equal(true)
    })
  })

  describe('renameProject', function () {
    beforeEach(function () {
      this.err = 'errro'
      this.newName = 'new name here'
      return this.EditorController.renameProject(
        this.project_id,
        this.newName,
        this.callback
      )
    })

    it('should call the EditorController', function () {
      return this.ProjectDetailsHandler.renameProject
        .calledWith(this.project_id, this.newName)
        .should.equal(true)
    })

    it('should emit the update to the room', function () {
      return this.EditorRealTimeController.emitToRoom
        .calledWith(this.project_id, 'projectNameUpdated', this.newName)
        .should.equal(true)
    })
  })

  describe('setCompiler', function () {
    beforeEach(function () {
      this.compiler = 'latex'
      return this.EditorController.setCompiler(
        this.project_id,
        this.compiler,
        this.callback
      )
    })

    it('should send the new compiler and project id to the project options handler', function () {
      this.ProjectOptionsHandler.setCompiler
        .calledWith(this.project_id, this.compiler)
        .should.equal(true)
      return this.EditorRealTimeController.emitToRoom
        .calledWith(this.project_id, 'compilerUpdated', this.compiler)
        .should.equal(true)
    })
  })

  describe('setImageName', function () {
    beforeEach(function () {
      this.imageName = 'texlive-1234.5'
      return this.EditorController.setImageName(
        this.project_id,
        this.imageName,
        this.callback
      )
    })

    it('should send the new imageName and project id to the project options handler', function () {
      this.ProjectOptionsHandler.setImageName
        .calledWith(this.project_id, this.imageName)
        .should.equal(true)
      return this.EditorRealTimeController.emitToRoom
        .calledWith(this.project_id, 'imageNameUpdated', this.imageName)
        .should.equal(true)
    })
  })

  describe('setSpellCheckLanguage', function () {
    beforeEach(function () {
      this.languageCode = 'fr'
      return this.EditorController.setSpellCheckLanguage(
        this.project_id,
        this.languageCode,
        this.callback
      )
    })

    it('should send the new languageCode and project id to the project options handler', function () {
      this.ProjectOptionsHandler.setSpellCheckLanguage
        .calledWith(this.project_id, this.languageCode)
        .should.equal(true)
      return this.EditorRealTimeController.emitToRoom
        .calledWith(
          this.project_id,
          'spellCheckLanguageUpdated',
          this.languageCode
        )
        .should.equal(true)
    })
  })

  describe('setPublicAccessLevel', function () {
    describe('when setting to private', function () {
      beforeEach(function () {
        this.newAccessLevel = 'private'
        this.ProjectDetailsHandler.ensureTokensArePresent = sinon
          .stub()
          .yields()
        return this.EditorController.setPublicAccessLevel(
          this.project_id,
          this.newAccessLevel,
          this.callback
        )
      })

      it('should set the access level', function () {
        return this.ProjectDetailsHandler.setPublicAccessLevel
          .calledWith(this.project_id, this.newAccessLevel)
          .should.equal(true)
      })

      it('should broadcast the access level change', function () {
        return this.EditorRealTimeController.emitToRoom
          .calledWith(this.project_id, 'project:publicAccessLevel:changed')
          .should.equal(true)
      })

      it('should not ensure tokens are present for project', function () {
        return this.ProjectDetailsHandler.ensureTokensArePresent
          .calledWith(this.project_id)
          .should.equal(false)
      })
    })

    describe('when setting to tokenBased', function () {
      beforeEach(function () {
        this.newAccessLevel = 'tokenBased'
        this.tokens = { readOnly: 'aaa', readAndWrite: '42bbb' }
        this.ProjectDetailsHandler.ensureTokensArePresent = sinon
          .stub()
          .yields()
        return this.EditorController.setPublicAccessLevel(
          this.project_id,
          this.newAccessLevel,
          this.callback
        )
      })

      it('should set the access level', function () {
        return this.ProjectDetailsHandler.setPublicAccessLevel
          .calledWith(this.project_id, this.newAccessLevel)
          .should.equal(true)
      })

      it('should broadcast the access level change', function () {
        return this.EditorRealTimeController.emitToRoom
          .calledWith(this.project_id, 'project:publicAccessLevel:changed')
          .should.equal(true)
      })

      it('should ensure tokens are present for project', function () {
        return this.ProjectDetailsHandler.ensureTokensArePresent
          .calledWith(this.project_id)
          .should.equal(true)
      })
    })
  })

  describe('setRootDoc', function () {
    beforeEach(function () {
      this.newRootDocID = '21312321321'
      this.ProjectEntityUpdateHandler.setRootDoc = sinon.stub().yields()
      return this.EditorController.setRootDoc(
        this.project_id,
        this.newRootDocID,
        this.callback
      )
    })

    it('should call the ProjectEntityUpdateHandler', function () {
      return this.ProjectEntityUpdateHandler.setRootDoc
        .calledWith(this.project_id, this.newRootDocID)
        .should.equal(true)
    })

    it('should emit the update to the room', function () {
      return this.EditorRealTimeController.emitToRoom
        .calledWith(this.project_id, 'rootDocUpdated', this.newRootDocID)
        .should.equal(true)
    })
  })

  describe('setMainBibliographyDoc', function () {
    describe('on success', function () {
      beforeEach(function (done) {
        this.mainBibliographyId = 'bib-doc-id'
        this.ProjectEntityUpdateHandler.setMainBibliographyDoc = sinon
          .stub()
          .yields()

        this.callback = sinon.stub().callsFake(done)
        this.EditorController.setMainBibliographyDoc(
          this.project_id,
          this.mainBibliographyId,
          this.callback
        )
      })

      it('should forward the call to the ProjectEntityUpdateHandler', function () {
        expect(
          this.ProjectEntityUpdateHandler.setMainBibliographyDoc
        ).to.have.been.calledWith(this.project_id, this.mainBibliographyId)
      })

      it('should emit the update to the room', function () {
        expect(
          this.EditorRealTimeController.emitToRoom
        ).to.have.been.calledWith(
          this.project_id,
          'mainBibliographyDocUpdated',
          this.mainBibliographyId
        )
      })

      it('should return nothing', function () {
        expect(this.callback).to.have.been.calledWithExactly()
      })
    })

    describe('on error', function () {
      beforeEach(function (done) {
        this.mainBibliographyId = 'bib-doc-id'
        this.error = new Error('oh no')
        this.ProjectEntityUpdateHandler.setMainBibliographyDoc = sinon
          .stub()
          .yields(this.error)

        this.callback = sinon.stub().callsFake(() => done())
        this.EditorController.setMainBibliographyDoc(
          this.project_id,
          this.mainBibliographyId,
          this.callback
        )
      })

      it('should forward the call to the ProjectEntityUpdateHandler', function () {
        expect(
          this.ProjectEntityUpdateHandler.setMainBibliographyDoc
        ).to.have.been.calledWith(this.project_id, this.mainBibliographyId)
      })

      it('should return the error', function () {
        expect(this.callback).to.have.been.calledWithExactly(this.error)
      })

      it('should not emit the update to the room', function () {
        expect(this.EditorRealTimeController.emitToRoom).to.not.have.been.called
      })
    })
  })

  describe('appendToDoc', function () {
    describe('on success', function () {
      beforeEach(function () {
        this.docId = 'doc-1'
        this.ProjectEntityUpdateHandler.appendToDoc = sinon
          .stub()
          .yields(null, { rev: '1' })
        this.EditorController.appendToDoc(
          this.project_id,
          this.docId,
          this.docLines,
          this.source,
          this.user_id,
          this.callback
        )
      })

      it('appends to the doc using the project entity handler', function () {
        this.ProjectEntityUpdateHandler.appendToDoc
          .calledWith(this.project_id, this.docId, this.docLines, this.source)
          .should.equal(true)
      })
    })

    describe('on error', function () {
      beforeEach(function () {
        this.docId = 'doc-1'
        this.ProjectEntityUpdateHandler.appendToDoc = sinon
          .stub()
          .yields(new Error('foo'))
        this.EditorController.appendToDoc(
          this.project_id,
          this.docId,
          this.docLines,
          this.source,
          this.user_id,
          this.callback
        )
      })

      it('tries to append to the doc using the project entity handler', function () {
        this.ProjectEntityUpdateHandler.appendToDoc
          .calledWith(this.project_id, this.docId, this.docLines, this.source)
          .should.equal(true)
      })

      it('tags the error', function () {
        this.callback.calledWith(sinon.match.instanceOf(OError))
      })
    })
  })
})
