From dc2097e6845e5d86050498f6be1153140a731608 Mon Sep 17 00:00:00 2001 From: Eric Mc Sween Date: Wed, 7 Sep 2022 06:28:42 -0400 Subject: [PATCH] Merge pull request #9535 from overleaf/em-promisify-update-merger Promisify UpdateMerger GitOrigin-RevId: 5aca78225b524d95f0c47ae872df64edd2685b01 --- .../ThirdPartyDataStore/UpdateMerger.js | 409 +++++++---------- .../ThirdPartyDataStore/UpdateMergerTests.js | 429 ++++++++---------- 2 files changed, 350 insertions(+), 488 deletions(-) diff --git a/services/web/app/src/Features/ThirdPartyDataStore/UpdateMerger.js b/services/web/app/src/Features/ThirdPartyDataStore/UpdateMerger.js index ecbd87439e..68b454d5d9 100644 --- a/services/web/app/src/Features/ThirdPartyDataStore/UpdateMerger.js +++ b/services/web/app/src/Features/ThirdPartyDataStore/UpdateMerger.js @@ -1,264 +1,165 @@ -/* eslint-disable - camelcase, - n/handle-callback-err, - max-len, -*/ -// 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 - * DS207: Consider shorter variations of null checks - * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md - */ -let UpdateMerger -const OError = require('@overleaf/o-error') +const { callbackify } = require('util') const _ = require('underscore') -const async = require('async') -const fs = require('fs') +const fsPromises = require('fs/promises') const logger = require('@overleaf/logger') const EditorController = require('../Editor/EditorController') const FileTypeManager = require('../Uploads/FileTypeManager') const FileWriter = require('../../infrastructure/FileWriter') const ProjectEntityHandler = require('../Project/ProjectEntityHandler') -const { promisifyAll } = require('../../util/promises') -module.exports = UpdateMerger = { - mergeUpdate(user_id, project_id, path, updateRequest, source, callback) { - if (callback == null) { - callback = function () {} +async function mergeUpdate(userId, projectId, path, updateRequest, source) { + const fsPath = await FileWriter.promises.writeStreamToDisk( + projectId, + updateRequest + ) + try { + await _mergeUpdate(userId, projectId, path, fsPath, source) + } finally { + try { + await fsPromises.unlink(fsPath) + } catch (err) { + logger.err({ projectId, fsPath }, 'error deleting file') } - return FileWriter.writeStreamToDisk( - project_id, - updateRequest, - function (err, fsPath) { - if (err != null) { - return callback(err) - } - return UpdateMerger._mergeUpdate( - user_id, - project_id, - path, - fsPath, - source, - mergeErr => - fs.unlink(fsPath, function (deleteErr) { - if (deleteErr != null) { - logger.err({ project_id, fsPath }, 'error deleting file') - } - return callback(mergeErr) - }) - ) - } - ) - }, - - _findExistingFileType(project_id, path, callback) { - ProjectEntityHandler.getAllEntities( - project_id, - function (err, { docs, files }) { - if (err != null) { - return callback(err) - } - let existingFileType = null - if (_.some(files, f => f.path === path)) { - existingFileType = 'file' - } - if (_.some(docs, d => d.path === path)) { - existingFileType = 'doc' - } - callback(null, existingFileType) - } - ) - }, - - _determineFileType(project_id, path, fsPath, callback) { - if (callback == null) { - callback = function () {} - } - // check if there is an existing file with the same path (we either need - // to overwrite it or delete it) - UpdateMerger._findExistingFileType( - project_id, - path, - function (err, existingFileType) { - if (err) { - return callback(err) - } - // determine whether the update should create a doc or binary file - FileTypeManager.getType( - path, - fsPath, - existingFileType, - function (err, { binary, encoding }) { - if (err != null) { - return callback(err) - } - - // If we receive a non-utf8 encoding, we won't be able to keep things in - // sync, so we'll treat non-utf8 files as binary - const isBinary = binary || encoding !== 'utf-8' - - // Existing | Update | Action - // ---------|-----------|------- - // file | isBinary | existing-file - // file | !isBinary | existing-file - // doc | isBinary | new-file, delete-existing-doc - // doc | !isBinary | existing-doc - // null | isBinary | new-file - // null | !isBinary | new-doc - - // if a binary file already exists, always keep it as a binary file - // even if the update looks like a text file - if (existingFileType === 'file') { - return callback(null, 'existing-file') - } - - // if there is an existing doc, keep it as a doc except when the - // incoming update is binary. In that case delete the doc and replace - // it with a new file. - if (existingFileType === 'doc') { - if (isBinary) { - return callback(null, 'new-file', 'delete-existing-doc') - } else { - return callback(null, 'existing-doc') - } - } - // if there no existing file, create a file or doc as needed - return callback(null, isBinary ? 'new-file' : 'new-doc') - } - ) - } - ) - }, - - _mergeUpdate(user_id, project_id, path, fsPath, source, callback) { - if (callback == null) { - callback = function () {} - } - return UpdateMerger._determineFileType( - project_id, - path, - fsPath, - function (err, fileType, deleteOriginalEntity) { - if (err != null) { - return callback(err) - } - async.series( - [ - function (cb) { - if (deleteOriginalEntity) { - // currently we only delete docs - UpdateMerger.deleteUpdate(user_id, project_id, path, source, cb) - } else { - cb() - } - }, - function (cb) { - if (['existing-file', 'new-file'].includes(fileType)) { - return UpdateMerger.p.processFile( - project_id, - fsPath, - path, - source, - user_id, - cb - ) - } else if (['existing-doc', 'new-doc'].includes(fileType)) { - return UpdateMerger.p.processDoc( - project_id, - user_id, - fsPath, - path, - source, - cb - ) - } else { - return cb(new Error('unrecognized file')) - } - }, - ], - callback - ) - } - ) - }, - - deleteUpdate(user_id, project_id, path, source, callback) { - if (callback == null) { - callback = function () {} - } - return EditorController.deleteEntityWithPath( - project_id, - path, - source, - user_id, - function () { - return callback() - } - ) - }, - - p: { - processDoc(project_id, user_id, fsPath, path, source, callback) { - return UpdateMerger.p.readFileIntoTextArray( - fsPath, - function (err, docLines) { - if (err != null) { - OError.tag( - err, - 'error reading file into text array for process doc update', - { - project_id, - } - ) - return callback(err) - } - logger.debug({ docLines }, 'processing doc update from tpds') - return EditorController.upsertDocWithPath( - project_id, - path, - docLines, - source, - user_id, - function (err) { - return callback(err) - } - ) - } - ) - }, - - processFile(project_id, fsPath, path, source, user_id, callback) { - return EditorController.upsertFileWithPath( - project_id, - path, - fsPath, - null, - source, - user_id, - function (err) { - return callback(err) - } - ) - }, - - readFileIntoTextArray(path, callback) { - return fs.readFile(path, 'utf8', function (error, content) { - if (content == null) { - content = '' - } - if (error != null) { - OError.tag(error, 'error reading file into text array', { - path, - }) - return callback(error) - } - const lines = content.split(/\r\n|\n|\r/) - return callback(error, lines) - }) - }, - }, + } } -module.exports.promises = promisifyAll(module.exports) +async function _findExistingFileType(projectId, path) { + const { docs, files } = await ProjectEntityHandler.promises.getAllEntities( + projectId + ) + if (_.some(docs, d => d.path === path)) { + return 'doc' + } + if (_.some(files, f => f.path === path)) { + return 'file' + } + return null +} + +async function _determineFileType(projectId, path, fsPath) { + // check if there is an existing file with the same path (we either need + // to overwrite it or delete it) + const existingFileType = await _findExistingFileType(projectId, path) + + // determine whether the update should create a doc or binary file + const { binary, encoding } = await FileTypeManager.promises.getType( + path, + fsPath, + existingFileType + ) + + // If we receive a non-utf8 encoding, we won't be able to keep things in + // sync, so we'll treat non-utf8 files as binary + const isBinary = binary || encoding !== 'utf-8' + + // Existing | Update | Action + // ---------|-----------|------- + // file | isBinary | existing-file + // file | !isBinary | existing-file + // doc | isBinary | new-file, delete-existing-doc + // doc | !isBinary | existing-doc + // null | isBinary | new-file + // null | !isBinary | new-doc + + // if a binary file already exists, always keep it as a binary file + // even if the update looks like a text file + if (existingFileType === 'file') { + return { fileType: 'existing-file' } + } + + // if there is an existing doc, keep it as a doc except when the + // incoming update is binary. In that case delete the doc and replace + // it with a new file. + if (existingFileType === 'doc') { + if (isBinary) { + return { + fileType: 'new-file', + deleteOriginalEntity: 'delete-existing-doc', + } + } else { + return { fileType: 'existing-doc' } + } + } + + // if there no existing file, create a file or doc as needed + return { fileType: isBinary ? 'new-file' : 'new-doc' } +} + +async function _mergeUpdate(userId, projectId, path, fsPath, source) { + const { fileType, deleteOriginalEntity } = await _determineFileType( + projectId, + path, + fsPath + ) + + if (deleteOriginalEntity) { + await deleteUpdate(userId, projectId, path, source) + } + + if (['existing-file', 'new-file'].includes(fileType)) { + await _processFile(projectId, fsPath, path, source, userId) + } else if (['existing-doc', 'new-doc'].includes(fileType)) { + await _processDoc(projectId, userId, fsPath, path, source) + } else { + throw new Error('unrecognized file') + } +} + +async function deleteUpdate(userId, projectId, path, source) { + try { + await EditorController.promises.deleteEntityWithPath( + projectId, + path, + source, + userId + ) + } catch (err) { + logger.warn( + { err, userId, projectId, path, source }, + 'failed to delete entity' + ) + } +} + +async function _processDoc(projectId, userId, fsPath, path, source) { + const docLines = await _readFileIntoTextArray(fsPath) + logger.debug({ docLines }, 'processing doc update from tpds') + await EditorController.promises.upsertDocWithPath( + projectId, + path, + docLines, + source, + userId + ) +} + +async function _processFile(projectId, fsPath, path, source, userId) { + await EditorController.promises.upsertFileWithPath( + projectId, + path, + fsPath, + null, + source, + userId + ) +} + +async function _readFileIntoTextArray(path) { + let content = await fsPromises.readFile(path, 'utf8') + if (content == null) { + content = '' + } + const lines = content.split(/\r\n|\n|\r/) + return lines +} + +module.exports = { + mergeUpdate: callbackify(mergeUpdate), + _mergeUpdate: callbackify(_mergeUpdate), + deleteUpdate: callbackify(deleteUpdate), + promises: { + mergeUpdate, + _mergeUpdate, // called by GitBridgeHandler + deleteUpdate, + }, +} diff --git a/services/web/test/unit/src/ThirdPartyDataStore/UpdateMergerTests.js b/services/web/test/unit/src/ThirdPartyDataStore/UpdateMergerTests.js index 02979a9d64..be696b738d 100644 --- a/services/web/test/unit/src/ThirdPartyDataStore/UpdateMergerTests.js +++ b/services/web/test/unit/src/ThirdPartyDataStore/UpdateMergerTests.js @@ -1,25 +1,15 @@ const SandboxedModule = require('sandboxed-module') const sinon = require('sinon') -const modulePath = require('path').join( - __dirname, - '../../../../app/src/Features/ThirdPartyDataStore/UpdateMerger.js' -) +const { expect } = require('chai') const BufferedStream = require('bufferedstream') +const MODULE_PATH = + '../../../../app/src/Features/ThirdPartyDataStore/UpdateMerger.js' + describe('UpdateMerger :', function () { beforeEach(function () { - this.updateMerger = SandboxedModule.require(modulePath, { - requires: { - fs: (this.fs = { unlink: sinon.stub().callsArgWith(1) }), - '../Editor/EditorController': (this.EditorController = {}), - '../Uploads/FileTypeManager': (this.FileTypeManager = {}), - '../../infrastructure/FileWriter': (this.FileWriter = {}), - '../Project/ProjectEntityHandler': (this.ProjectEntityHandler = {}), - '@overleaf/settings': { path: { dumpPath: 'dump_here' } }, - }, - }) - this.project_id = 'project_id_here' - this.user_id = 'mock-user-id' + this.projectId = 'project_id_here' + this.userId = 'mock-user-id' this.docPath = this.newDocPath = '/folder/doc.tex' this.filePath = this.newFilePath = '/folder/file.png' @@ -31,340 +21,311 @@ describe('UpdateMerger :', function () { this.existingDocs = [{ path: '/main.tex' }, { path: '/folder/other.tex' }] this.existingFiles = [{ path: '/figure.pdf' }, { path: '/folder/fig1.pdf' }] - this.ProjectEntityHandler.getAllEntities = sinon - .stub() - .callsArgWith(1, null, { - docs: this.existingDocs, - files: this.existingFiles, - }) this.fsPath = '/tmp/file/path' + this.fileContents = `\\documentclass{article} +\\usepackage[utf8]{inputenc} + +\\title{42} +\\author{Jane Doe} +\\date{June 2011}` + this.docLines = this.fileContents.split('\n') this.source = 'dropbox' this.updateRequest = new BufferedStream() - this.FileWriter.writeStreamToDisk = sinon.stub().yields(null, this.fsPath) - this.callback = sinon.stub() + + this.fsPromises = { + unlink: sinon.stub().resolves(), + readFile: sinon.stub().withArgs(this.fsPath).resolves(this.fileContents), + } + + this.EditorController = { + promises: { + deleteEntityWithPath: sinon.stub().resolves(), + upsertDocWithPath: sinon.stub().resolves(), + upsertFileWithPath: sinon.stub().resolves(), + }, + } + + this.FileTypeManager = { + promises: { + getType: sinon.stub(), + }, + } + + this.FileWriter = { + promises: { + writeStreamToDisk: sinon.stub().resolves(this.fsPath), + }, + } + + this.ProjectEntityHandler = { + promises: { + getAllEntities: sinon.stub().resolves({ + docs: this.existingDocs, + files: this.existingFiles, + }), + }, + } + + this.Settings = { path: { dumpPath: 'dump_here' } } + + this.UpdateMerger = SandboxedModule.require(MODULE_PATH, { + requires: { + 'fs/promises': this.fsPromises, + '../Editor/EditorController': this.EditorController, + '../Uploads/FileTypeManager': this.FileTypeManager, + '../../infrastructure/FileWriter': this.FileWriter, + '../Project/ProjectEntityHandler': this.ProjectEntityHandler, + '@overleaf/settings': this.Settings, + }, + }) }) describe('mergeUpdate', function () { describe('doc updates for a new doc', function () { - beforeEach(function () { - this.FileTypeManager.getType = sinon - .stub() - .yields(null, { binary: false, encoding: 'utf-8' }) - this.updateMerger.p.processDoc = sinon.stub().yields() - this.updateMerger.mergeUpdate( - this.user_id, - this.project_id, + beforeEach(async function () { + this.FileTypeManager.promises.getType.resolves({ + binary: false, + encoding: 'utf-8', + }) + await this.UpdateMerger.promises.mergeUpdate( + this.userId, + this.projectId, this.docPath, this.updateRequest, - this.source, - this.callback + this.source ) }) it('should look at the file contents', function () { - this.FileTypeManager.getType.called.should.equal(true) + expect(this.FileTypeManager.promises.getType).to.have.been.called }) it('should process update as doc', function () { - this.updateMerger.p.processDoc - .calledWith( - this.project_id, - this.user_id, - this.fsPath, - this.docPath, - this.source - ) - .should.equal(true) + expect( + this.EditorController.promises.upsertDocWithPath + ).to.have.been.calledWith( + this.projectId, + this.docPath, + this.docLines, + this.source, + this.userId + ) }) it('removes the temp file from disk', function () { - this.fs.unlink.calledWith(this.fsPath).should.equal(true) + expect(this.fsPromises.unlink).to.have.been.calledWith(this.fsPath) }) }) describe('file updates for a new file ', function () { - beforeEach(function () { - this.FileTypeManager.getType = sinon - .stub() - .yields(null, { binary: true }) - this.updateMerger.p.processFile = sinon.stub().yields() - this.updateMerger.mergeUpdate( - this.user_id, - this.project_id, + beforeEach(async function () { + this.FileTypeManager.promises.getType.resolves({ binary: true }) + await this.UpdateMerger.promises.mergeUpdate( + this.userId, + this.projectId, this.filePath, this.updateRequest, - this.source, - this.callback + this.source ) }) it('should look at the file contents', function () { - this.FileTypeManager.getType.called.should.equal(true) + expect(this.FileTypeManager.promises.getType).to.have.been.called }) it('should process update as file', function () { - this.updateMerger.p.processFile - .calledWith( - this.project_id, - this.fsPath, - this.filePath, - this.source, - this.user_id - ) - .should.equal(true) + expect( + this.EditorController.promises.upsertFileWithPath + ).to.have.been.calledWith( + this.projectId, + this.filePath, + this.fsPath, + null, + this.source, + this.userId + ) }) it('removes the temp file from disk', function () { - this.fs.unlink.calledWith(this.fsPath).should.equal(true) + expect(this.fsPromises.unlink).to.have.been.calledWith(this.fsPath) }) }) describe('doc updates for an existing doc', function () { - beforeEach(function () { - this.FileTypeManager.getType = sinon - .stub() - .yields(null, { binary: false, encoding: 'utf-8' }) - this.updateMerger.p.processDoc = sinon.stub().yields() - this.updateMerger.mergeUpdate( - this.user_id, - this.project_id, + beforeEach(async function () { + this.FileTypeManager.promises.getType.resolves({ + binary: false, + encoding: 'utf-8', + }) + await this.UpdateMerger.promises.mergeUpdate( + this.userId, + this.projectId, this.existingDocPath, this.updateRequest, - this.source, - this.callback + this.source ) }) it('should look at the file contents', function () { - this.FileTypeManager.getType.called.should.equal(true) + expect(this.FileTypeManager.promises.getType).to.have.been.called }) it('should process update as doc', function () { - this.updateMerger.p.processDoc - .calledWith( - this.project_id, - this.user_id, - this.fsPath, - this.existingDocPath, - this.source - ) - .should.equal(true) + expect( + this.EditorController.promises.upsertDocWithPath + ).to.have.been.calledWith( + this.projectId, + this.existingDocPath, + this.docLines, + this.source, + this.userId + ) }) it('removes the temp file from disk', function () { - this.fs.unlink.calledWith(this.fsPath).should.equal(true) + expect(this.fsPromises.unlink).to.have.been.calledWith(this.fsPath) }) }) describe('file updates for an existing file', function () { - beforeEach(function () { - this.FileTypeManager.getType = sinon - .stub() - .yields(null, { binary: true }) - this.updateMerger.p.processFile = sinon.stub().yields() - this.updateMerger.mergeUpdate( - this.user_id, - this.project_id, + beforeEach(async function () { + this.FileTypeManager.promises.getType.resolves({ binary: true }) + await this.UpdateMerger.promises.mergeUpdate( + this.userId, + this.projectId, this.existingFilePath, this.updateRequest, - this.source, - this.callback + this.source ) }) it('should look at the file contents', function () { - this.FileTypeManager.getType.called.should.equal(true) + expect(this.FileTypeManager.promises.getType).to.have.been.called }) it('should process update as file', function () { - this.updateMerger.p.processFile - .calledWith( - this.project_id, - this.fsPath, - this.existingFilePath, - this.source, - this.user_id - ) - .should.equal(true) + expect( + this.EditorController.promises.upsertFileWithPath + ).to.have.been.calledWith( + this.projectId, + this.existingFilePath, + this.fsPath, + null, + this.source, + this.userId + ) }) it('removes the temp file from disk', function () { - this.fs.unlink.calledWith(this.fsPath).should.equal(true) + expect(this.fsPromises.unlink).to.have.been.calledWith(this.fsPath) }) }) }) describe('file updates for an existing doc', function () { - beforeEach(function () { - this.FileTypeManager.getType = sinon.stub().yields(null, { binary: true }) - this.updateMerger.deleteUpdate = sinon.stub().yields() - this.updateMerger.p.processFile = sinon.stub().yields() - this.updateMerger.mergeUpdate( - this.user_id, - this.project_id, + beforeEach(async function () { + this.FileTypeManager.promises.getType.resolves({ binary: true }) + await this.UpdateMerger.promises.mergeUpdate( + this.userId, + this.projectId, this.existingDocPath, this.updateRequest, - this.source, - this.callback + this.source ) }) it('should look at the file contents', function () { - this.FileTypeManager.getType.called.should.equal(true) + expect(this.FileTypeManager.promises.getType).to.have.been.called }) it('should delete the existing doc', function () { - this.updateMerger.deleteUpdate - .calledWith( - this.user_id, - this.project_id, - this.existingDocPath, - this.source - ) - .should.equal(true) + expect( + this.EditorController.promises.deleteEntityWithPath + ).to.have.been.calledWith( + this.projectId, + this.existingDocPath, + this.source, + this.userId + ) }) it('should process update as file', function () { - this.updateMerger.p.processFile - .calledWith( - this.project_id, - this.fsPath, - this.existingDocPath, - this.source, - this.user_id - ) - .should.equal(true) + expect( + this.EditorController.promises.upsertFileWithPath + ).to.have.been.calledWith( + this.projectId, + this.existingDocPath, + this.fsPath, + null, + this.source, + this.userId + ) }) it('removes the temp file from disk', function () { - this.fs.unlink.calledWith(this.fsPath).should.equal(true) + expect(this.fsPromises.unlink).to.have.been.calledWith(this.fsPath) }) }) describe('doc updates for an existing file', function () { - beforeEach(function () { - this.FileTypeManager.getType = sinon.stub().yields(null, { binary: true }) - this.updateMerger.deleteUpdate = sinon.stub().yields() - this.updateMerger.p.processFile = sinon.stub().yields() - this.updateMerger.mergeUpdate( - this.user_id, - this.project_id, + beforeEach(async function () { + this.FileTypeManager.promises.getType.resolves({ binary: true }) + await this.UpdateMerger.promises.mergeUpdate( + this.userId, + this.projectId, this.existingFilePath, this.updateRequest, - this.source, - this.callback + this.source ) }) it('should look at the file contents', function () { - this.FileTypeManager.getType.called.should.equal(true) + expect(this.FileTypeManager.promises.getType).to.have.been.called }) it('should not delete the existing file', function () { - this.updateMerger.deleteUpdate.called.should.equal(false) + expect(this.EditorController.promises.deleteEntityWithPath).to.not.have + .been.called }) it('should process update as file', function () { - this.updateMerger.p.processFile - .calledWith( - this.project_id, - this.fsPath, - this.existingFilePath, - this.source, - this.user_id - ) - .should.equal(true) + expect( + this.EditorController.promises.upsertFileWithPath + ).to.have.been.calledWith( + this.projectId, + this.existingFilePath, + this.fsPath, + null, + this.source, + this.userId + ) }) it('removes the temp file from disk', function () { - this.fs.unlink.calledWith(this.fsPath).should.equal(true) + expect(this.fsPromises.unlink).to.have.been.calledWith(this.fsPath) }) }) describe('deleteUpdate', function () { - beforeEach(function () { - this.EditorController.deleteEntityWithPath = sinon.stub().yields() - this.updateMerger.deleteUpdate( - this.user_id, - this.project_id, + beforeEach(async function () { + await this.UpdateMerger.promises.deleteUpdate( + this.userId, + this.projectId, this.docPath, - this.source, - this.callback + this.source ) }) it('should delete the entity in the editor controller', function () { - this.EditorController.deleteEntityWithPath - .calledWith(this.project_id, this.docPath, this.source, this.user_id) - .should.equal(true) - }) - }) - - describe('private methods', function () { - describe('processDoc', function () { - beforeEach(function () { - this.docLines = - '\\documentclass{article}\n\\usepackage[utf8]{inputenc}\n\n\\title{42}\n\\author{Jane Doe}\n\\date{June 2011}' - this.updateMerger.p.readFileIntoTextArray = sinon - .stub() - .yields(null, this.docLines) - this.EditorController.upsertDocWithPath = sinon.stub().yields() - - this.updateMerger.p.processDoc( - this.project_id, - this.user_id, - this.fsPath, - this.docPath, - this.source, - this.callback - ) - }) - - it('reads the temp file from disk', function () { - this.updateMerger.p.readFileIntoTextArray - .calledWith(this.fsPath) - .should.equal(true) - }) - - it('should upsert the doc in the editor controller', function () { - this.EditorController.upsertDocWithPath - .calledWith( - this.project_id, - this.docPath, - this.docLines, - this.source, - this.user_id - ) - .should.equal(true) - }) - }) - - describe('processFile', function () { - beforeEach(function () { - this.EditorController.upsertFileWithPath = sinon.stub().yields() - this.updateMerger.p.processFile( - this.project_id, - this.fsPath, - this.filePath, - this.source, - this.user_id, - this.callback - ) - }) - - it('should upsert the file in the editor controller', function () { - this.EditorController.upsertFileWithPath - .calledWith( - this.project_id, - this.filePath, - this.fsPath, - null, - this.source, - this.user_id - ) - .should.equal(true) - }) + expect( + this.EditorController.promises.deleteEntityWithPath + ).to.have.been.calledWith( + this.projectId, + this.docPath, + this.source, + this.userId + ) }) }) })