| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144 |
- const { expect } = require('chai')
- const sinon = require('sinon')
- const tk = require('timekeeper')
- const Errors = require('../../../../app/src/Features/Errors/Errors')
- const { ObjectId } = require('mongodb')
- const SandboxedModule = require('sandboxed-module')
- const { DeletedFile } = require('../helpers/models/DeletedFile')
- const { Project } = require('../helpers/models/Project')
- const MODULE_PATH =
- '../../../../app/src/Features/Project/ProjectEntityMongoUpdateHandler'
- describe('ProjectEntityMongoUpdateHandler', function () {
- beforeEach(function () {
- this.doc = {
- _id: ObjectId(),
- name: 'test-doc.txt',
- lines: ['hello', 'world'],
- rev: 1234,
- }
- this.docPath = {
- mongo: 'rootFolder.0.docs.0',
- fileSystem: '/test-doc.txt',
- }
- this.file = {
- _id: ObjectId(),
- name: 'something.jpg',
- linkedFileData: { provider: 'url' },
- hash: 'some-hash',
- }
- this.filePath = {
- fileSystem: '/something.png',
- mongo: 'rootFolder.0.fileRefs.0',
- }
- this.subfolder = { _id: ObjectId(), name: 'test-subfolder' }
- this.subfolderPath = {
- fileSystem: '/test-folder/test-subfolder',
- mongo: 'rootFolder.0.folders.0.folders.0',
- }
- this.notSubfolder = { _id: ObjectId(), name: 'test-folder-2' }
- this.notSubfolderPath = {
- fileSystem: '/test-folder-2/test-subfolder',
- mongo: 'rootFolder.0.folders.0.folders.0',
- }
- this.folder = {
- _id: ObjectId(),
- name: 'test-folder',
- folders: [this.subfolder],
- }
- this.folderPath = {
- fileSystem: '/test-folder',
- mongo: 'rootFolder.0.folders.0',
- }
- this.rootFolder = {
- _id: ObjectId(),
- folders: [this.folder],
- docs: [this.doc],
- fileRefs: [this.file],
- }
- this.rootFolderPath = {
- fileSystem: '/',
- mongo: 'rootFolder.0',
- }
- this.project = {
- _id: ObjectId(),
- name: 'project name',
- rootFolder: [this.rootFolder],
- }
- this.Settings = { maxEntitiesPerProject: 100 }
- this.CooldownManager = {}
- this.LockManager = {
- promises: {
- runWithLock: sinon.spy((namespace, id, runner) => runner()),
- },
- }
- this.FolderModel = sinon.stub()
- this.DeletedFileMock = sinon.mock(DeletedFile)
- this.ProjectMock = sinon.mock(Project)
- this.ProjectEntityHandler = {
- getAllEntitiesFromProject: sinon.stub(),
- }
- this.ProjectLocator = {
- findElementByMongoPath: sinon.stub().throws(new Error('not found')),
- promises: {
- findElement: sinon.stub().rejects(new Error('not found')),
- findElementByPath: sinon.stub().rejects(new Error('not found')),
- },
- }
- this.ProjectLocator.promises.findElement
- .withArgs({
- project: this.project,
- element_id: this.rootFolder._id,
- type: 'folder',
- })
- .resolves({
- element: this.rootFolder,
- path: this.rootFolderPath,
- })
- this.ProjectLocator.promises.findElement
- .withArgs({
- project: this.project,
- element_id: this.folder._id,
- type: 'folder',
- })
- .resolves({
- element: this.folder,
- path: this.folderPath,
- folder: this.rootFolder,
- })
- this.ProjectLocator.promises.findElement
- .withArgs({
- project: this.project,
- element_id: this.subfolder._id,
- type: 'folder',
- })
- .resolves({
- element: this.subfolder,
- path: this.subfolderPath,
- folder: this.folder,
- })
- this.ProjectLocator.promises.findElement
- .withArgs({
- project: this.project,
- element_id: this.file._id,
- type: 'file',
- })
- .resolves({
- element: this.file,
- path: this.filePath,
- folder: this.rootFolder,
- })
- this.ProjectLocator.promises.findElement
- .withArgs({
- project: this.project,
- element_id: this.doc._id,
- type: 'doc',
- })
- .resolves({
- element: this.doc,
- path: this.docPath,
- folder: this.rootFolder,
- })
- this.ProjectLocator.promises.findElementByPath
- .withArgs(
- sinon.match({
- project: this.project,
- path: '/',
- })
- )
- .resolves({ element: this.rootFolder, type: 'folder', folder: null })
- this.ProjectLocator.promises.findElementByPath
- .withArgs(
- sinon.match({
- project: this.project,
- path: '/test-folder',
- })
- )
- .resolves({
- element: this.folder,
- type: 'folder',
- folder: this.rootFolder,
- })
- this.ProjectLocator.promises.findElementByPath
- .withArgs(
- sinon.match({
- project: this.project,
- path: '/test-folder/test-subfolder',
- })
- )
- .resolves({
- element: this.subfolder,
- type: 'folder',
- folder: this.folder,
- })
- this.ProjectGetter = {
- promises: {
- getProjectWithoutLock: sinon
- .stub()
- .withArgs(this.project._id)
- .resolves(this.project),
- getProjectWithOnlyFolders: sinon.stub().resolves(this.project),
- },
- }
- this.FolderStructureBuilder = {
- buildFolderStructure: sinon.stub(),
- }
- this.subject = SandboxedModule.require(MODULE_PATH, {
- requires: {
- mongodb: { ObjectId },
- '@overleaf/settings': this.Settings,
- '../Cooldown/CooldownManager': this.CooldownManager,
- '../../models/Folder': { Folder: this.FolderModel },
- '../../infrastructure/LockManager': this.LockManager,
- '../../models/DeletedFile': { DeletedFile },
- '../../models/Project': { Project },
- './ProjectEntityHandler': this.ProjectEntityHandler,
- './ProjectLocator': this.ProjectLocator,
- './ProjectGetter': this.ProjectGetter,
- './FolderStructureBuilder': this.FolderStructureBuilder,
- },
- })
- })
- afterEach(function () {
- this.DeletedFileMock.restore()
- this.ProjectMock.restore()
- })
- beforeEach(function () {
- tk.freeze(Date.now())
- })
- afterEach(function () {
- tk.reset()
- })
- describe('addDoc', function () {
- beforeEach(async function () {
- const doc = { _id: ObjectId(), name: 'other.txt' }
- this.ProjectMock.expects('findOneAndUpdate')
- .withArgs(
- {
- _id: this.project._id,
- 'rootFolder.0.folders.0': { $exists: true },
- },
- {
- $push: { 'rootFolder.0.folders.0.docs': doc },
- $inc: { version: 1 },
- }
- )
- .chain('exec')
- .resolves(this.project)
- this.result = await this.subject.promises.addDoc(
- this.project._id,
- this.folder._id,
- doc
- )
- })
- it('adds the document in Mongo', function () {
- this.ProjectMock.verify()
- })
- it('returns path info and the project', function () {
- expect(this.result).to.deep.equal({
- result: {
- path: {
- mongo: 'rootFolder.0.folders.0',
- fileSystem: '/test-folder/other.txt',
- },
- },
- project: this.project,
- })
- })
- })
- describe('addFile', function () {
- beforeEach(function () {
- this.newFile = { _id: ObjectId(), name: 'picture.jpg' }
- this.ProjectMock.expects('findOneAndUpdate')
- .withArgs(
- {
- _id: this.project._id,
- 'rootFolder.0.folders.0': { $exists: true },
- },
- {
- $push: { 'rootFolder.0.folders.0.fileRefs': this.newFile },
- $inc: { version: 1 },
- }
- )
- .chain('exec')
- .resolves(this.project)
- })
- describe('happy path', function () {
- beforeEach(async function () {
- this.result = await this.subject.promises.addFile(
- this.project._id,
- this.folder._id,
- this.newFile
- )
- })
- it('adds the file in Mongo', function () {
- this.ProjectMock.verify()
- })
- it('returns path info and the project', function () {
- expect(this.result).to.deep.equal({
- result: {
- path: {
- mongo: 'rootFolder.0.folders.0',
- fileSystem: '/test-folder/picture.jpg',
- },
- },
- project: this.project,
- })
- })
- })
- describe('when entity limit is reached', function () {
- beforeEach(function () {
- this.savedMaxEntities = this.Settings.maxEntitiesPerProject
- this.Settings.maxEntitiesPerProject = 3
- })
- afterEach(function () {
- this.Settings.maxEntitiesPerProject = this.savedMaxEntities
- })
- it('should throw an error', async function () {
- await expect(
- this.subject.promises.addFile(
- this.project._id,
- this.folder._id,
- this.newFile
- )
- ).to.be.rejected
- })
- })
- })
- describe('addFolder', function () {
- beforeEach(async function () {
- const folderName = 'New folder'
- this.FolderModel.withArgs({ name: folderName }).returns({
- _id: ObjectId(),
- name: folderName,
- })
- this.ProjectMock.expects('findOneAndUpdate')
- .withArgs(
- {
- _id: this.project._id,
- 'rootFolder.0.folders.0': { $exists: true },
- },
- {
- $push: {
- 'rootFolder.0.folders.0.folders': sinon.match({
- name: folderName,
- }),
- },
- $inc: { version: 1 },
- }
- )
- .chain('exec')
- .resolves(this.project)
- await this.subject.promises.addFolder(
- this.project._id,
- this.folder._id,
- folderName
- )
- })
- it('adds the folder in Mongo', function () {
- this.ProjectMock.verify()
- })
- })
- describe('replaceFileWithNew', function () {
- beforeEach(async function () {
- const newFile = {
- _id: ObjectId(),
- name: 'some-other-file.png',
- linkedFileData: { some: 'data' },
- hash: 'some-hash',
- }
- // Add a deleted file record
- this.DeletedFileMock.expects('create')
- .withArgs({
- projectId: this.project._id,
- _id: this.file._id,
- name: this.file.name,
- linkedFileData: this.file.linkedFileData,
- hash: this.file.hash,
- deletedAt: sinon.match.date,
- })
- .resolves()
- // Update the file in place
- this.ProjectMock.expects('findOneAndUpdate')
- .withArgs(
- {
- _id: this.project._id,
- 'rootFolder.0.fileRefs.0': { $exists: true },
- },
- {
- $set: {
- 'rootFolder.0.fileRefs.0._id': newFile._id,
- 'rootFolder.0.fileRefs.0.created': sinon.match.date,
- 'rootFolder.0.fileRefs.0.linkedFileData': newFile.linkedFileData,
- 'rootFolder.0.fileRefs.0.hash': newFile.hash,
- },
- $inc: {
- version: 1,
- 'rootFolder.0.fileRefs.0.rev': 1,
- },
- }
- )
- .chain('exec')
- .resolves(this.project)
- this.ProjectLocator.findElementByMongoPath
- .withArgs(this.project, 'rootFolder.0.fileRefs.0')
- .returns(newFile)
- await this.subject.promises.replaceFileWithNew(
- this.project._id,
- this.file._id,
- newFile
- )
- })
- it('updates the database', function () {
- this.DeletedFileMock.verify()
- this.ProjectMock.verify()
- })
- })
- describe('mkdirp', function () {
- describe('when the path is just a slash', function () {
- beforeEach(async function () {
- this.result = await this.subject.promises.mkdirp(this.project._id, '/')
- })
- it('should return the root folder', function () {
- expect(this.result.folder).to.deep.equal(this.rootFolder)
- })
- it('should not report a parent folder', function () {
- expect(this.result.folder.parentFolder_id).not.to.exist
- })
- it('should not return new folders', function () {
- expect(this.result.newFolders).to.have.length(0)
- })
- })
- describe('when the folder already exists', function () {
- beforeEach(async function () {
- this.result = await this.subject.promises.mkdirp(
- this.project._id,
- '/test-folder'
- )
- })
- it('should return the existing folder', function () {
- expect(this.result.folder).to.deep.equal(this.folder)
- })
- it('should report the parent folder', function () {
- expect(this.result.folder.parentFolder_id).to.equal(this.rootFolder._id)
- })
- it('should not return new folders', function () {
- expect(this.result.newFolders).to.have.length(0)
- })
- })
- describe('when the path is a new folder at the top level', function () {
- beforeEach(async function () {
- this.newFolder = { _id: ObjectId(), name: 'new-folder' }
- this.FolderModel.returns(this.newFolder)
- this.exactCaseMatch = false
- this.ProjectMock.expects('findOneAndUpdate')
- .withArgs(
- { _id: this.project._id, 'rootFolder.0': { $exists: true } },
- {
- $push: { 'rootFolder.0.folders': this.newFolder },
- $inc: { version: 1 },
- }
- )
- .chain('exec')
- .resolves(this.project)
- this.result = await this.subject.promises.mkdirp(
- this.project._id,
- '/new-folder/',
- { exactCaseMatch: this.exactCaseMatch }
- )
- })
- it('should update the database', function () {
- this.ProjectMock.verify()
- })
- it('should make just one folder', function () {
- expect(this.result.newFolders).to.have.length(1)
- })
- it('should return the new folder', function () {
- expect(this.result.folder.name).to.equal('new-folder')
- })
- it('should return the parent folder', function () {
- expect(this.result.folder.parentFolder_id).to.equal(this.rootFolder._id)
- })
- it('should pass the exactCaseMatch option to ProjectLocator', function () {
- expect(
- this.ProjectLocator.promises.findElementByPath
- ).to.have.been.calledWithMatch({ exactCaseMatch: this.exactCaseMatch })
- })
- })
- describe('adding a subfolder', function () {
- beforeEach(async function () {
- this.newFolder = { _id: ObjectId(), name: 'new-folder' }
- this.FolderModel.returns(this.newFolder)
- this.ProjectMock.expects('findOneAndUpdate')
- .withArgs(
- {
- _id: this.project._id,
- 'rootFolder.0.folders.0': { $exists: true },
- },
- {
- $push: {
- 'rootFolder.0.folders.0.folders': sinon.match({
- name: 'new-folder',
- }),
- },
- $inc: { version: 1 },
- }
- )
- .chain('exec')
- .resolves(this.project)
- this.result = await this.subject.promises.mkdirp(
- this.project._id,
- '/test-folder/new-folder'
- )
- })
- it('should update the database', function () {
- this.ProjectMock.verify()
- })
- it('should create one folder', function () {
- expect(this.result.newFolders).to.have.length(1)
- })
- it('should return the new folder', function () {
- expect(this.result.folder.name).to.equal('new-folder')
- })
- it('should return the parent folder', function () {
- expect(this.result.folder.parentFolder_id).to.equal(this.folder._id)
- })
- })
- describe('when mutliple folders are missing', async function () {
- beforeEach(function () {
- this.folder1 = { _id: ObjectId(), name: 'folder1' }
- this.folder1Path = {
- fileSystem: '/test-folder/folder1',
- mongo: 'rootFolder.0.folders.0.folders.0',
- }
- this.folder2 = { _id: ObjectId(), name: 'folder2' }
- this.folder2Path = {
- fileSystem: '/test-folder/folder1/folder2',
- mongo: 'rootFolder.0.folders.0.folders.0.folders.0',
- }
- this.FolderModel.onFirstCall().returns(this.folder1)
- this.FolderModel.onSecondCall().returns(this.folder2)
- this.ProjectLocator.promises.findElement
- .withArgs({
- project: this.project,
- element_id: this.folder1._id,
- type: 'folder',
- })
- .resolves({
- element: this.folder1,
- path: this.folder1Path,
- })
- this.ProjectLocator.promises.findElement
- .withArgs({
- project: this.project,
- element_id: this.folder2._id,
- type: 'folder',
- })
- .resolves({
- element: this.folder2,
- path: this.folder2Path,
- })
- this.ProjectMock.expects('findOneAndUpdate')
- .withArgs(
- {
- _id: this.project._id,
- 'rootFolder.0.folders.0': { $exists: true },
- },
- {
- $push: {
- 'rootFolder.0.folders.0.folders': sinon.match({
- name: 'folder1',
- }),
- },
- $inc: { version: 1 },
- }
- )
- .chain('exec')
- .resolves(this.project)
- this.ProjectMock.expects('findOneAndUpdate')
- .withArgs(
- {
- _id: this.project._id,
- 'rootFolder.0.folders.0.folders.0': { $exists: true },
- },
- {
- $push: {
- 'rootFolder.0.folders.0.folders.0.folders': sinon.match({
- name: 'folder2',
- }),
- },
- $inc: { version: 1 },
- }
- )
- .chain('exec')
- .resolves(this.project)
- })
- ;[
- {
- description: 'without a trailing slash',
- path: '/test-folder/folder1/folder2',
- },
- {
- description: 'with a trailing slash',
- path: '/test-folder/folder1/folder2/',
- },
- ].forEach(({ description, path }) => {
- describe(description, function () {
- beforeEach(async function () {
- this.result = await this.subject.promises.mkdirp(
- this.project._id,
- path
- )
- })
- it('should update the database', function () {
- this.ProjectMock.verify()
- })
- it('should add multiple folders', function () {
- const newFolders = this.result.newFolders
- expect(newFolders).to.have.length(2)
- expect(newFolders[0].name).to.equal('folder1')
- expect(newFolders[1].name).to.equal('folder2')
- })
- it('should return the last folder', function () {
- expect(this.result.folder.name).to.equal('folder2')
- })
- it('should return the parent folder', function () {
- expect(this.result.folder.parentFolder_id).to.equal(
- this.folder1._id
- )
- })
- })
- })
- })
- })
- describe('moveEntity', function () {
- describe('moving a doc into a different folder', function () {
- beforeEach(async function () {
- this.pathAfterMove = {
- fileSystem: '/somewhere/else.txt',
- }
- this.oldDocs = ['old-doc']
- this.oldFiles = ['old-file']
- this.newDocs = ['new-doc']
- this.newFiles = ['new-file']
- this.ProjectEntityHandler.getAllEntitiesFromProject
- .onFirstCall()
- .returns({ docs: this.oldDocs, files: this.oldFiles })
- this.ProjectEntityHandler.getAllEntitiesFromProject
- .onSecondCall()
- .returns({ docs: this.newDocs, files: this.newFiles })
- this.ProjectMock.expects('findOneAndUpdate')
- .withArgs(
- {
- _id: this.project._id,
- 'rootFolder.0.folders.0': { $exists: true },
- },
- {
- $push: { 'rootFolder.0.folders.0.docs': this.doc },
- $inc: { version: 1 },
- }
- )
- .chain('exec')
- .resolves(this.project)
- this.ProjectMock.expects('findOneAndUpdate')
- .withArgs(
- { _id: this.project._id },
- {
- $pull: { 'rootFolder.0.docs': { _id: this.doc._id } },
- $inc: { version: 1 },
- }
- )
- .chain('exec')
- .resolves(this.project)
- this.result = await this.subject.promises.moveEntity(
- this.project._id,
- this.doc._id,
- this.folder._id,
- 'doc'
- )
- })
- it('should update the database', function () {
- this.ProjectMock.verify()
- })
- it('should report what changed', function () {
- expect(this.result).to.deep.equal({
- project: this.project,
- startPath: '/test-doc.txt',
- endPath: '/test-folder/test-doc.txt',
- rev: this.doc.rev,
- changes: {
- oldDocs: this.oldDocs,
- newDocs: this.newDocs,
- oldFiles: this.oldFiles,
- newFiles: this.newFiles,
- newProject: this.project,
- },
- })
- })
- })
- describe('when moving a folder inside itself', function () {
- it('throws an error', async function () {
- await expect(
- this.subject.promises.moveEntity(
- this.project._id,
- this.folder._id,
- this.folder._id,
- 'folder'
- )
- ).to.be.rejectedWith(Errors.InvalidNameError)
- })
- })
- describe('when moving a folder to a subfolder of itself', function () {
- it('throws an error', async function () {
- await expect(
- this.subject.promises.moveEntity(
- this.project._id,
- this.folder._id,
- this.subfolder._id,
- 'folder'
- )
- ).to.be.rejectedWith(Errors.InvalidNameError)
- })
- })
- describe('when moving a folder to a subfolder which starts with the same characters', function () {
- it('does not throw an error', async function () {
- await expect(
- this.subject.promises.moveEntity(
- this.project._id,
- this.folder._id,
- this.notSubfolder._id,
- 'folder'
- )
- ).not.to.be.rejectedWith(Errors.InvalidNameError)
- })
- })
- })
- describe('deleteEntity', function () {
- beforeEach(async function () {
- this.ProjectMock.expects('findOneAndUpdate')
- .withArgs(
- { _id: this.project._id },
- {
- $pull: { 'rootFolder.0.docs': { _id: this.doc._id } },
- $inc: { version: 1 },
- }
- )
- .chain('exec')
- .resolves(this.project)
- await this.subject.promises.deleteEntity(
- this.project._id,
- this.doc._id,
- 'doc'
- )
- })
- it('should update the database', function () {
- this.ProjectMock.verify()
- })
- })
- describe('renameEntity', function () {
- describe('happy path', function () {
- beforeEach(async function () {
- this.newName = 'new.tex'
- this.oldDocs = ['old-doc']
- this.oldFiles = ['old-file']
- this.newDocs = ['new-doc']
- this.newFiles = ['new-file']
- this.ProjectEntityHandler.getAllEntitiesFromProject
- .onFirstCall()
- .returns({ docs: this.oldDocs, files: this.oldFiles })
- this.ProjectEntityHandler.getAllEntitiesFromProject
- .onSecondCall()
- .returns({ docs: this.newDocs, files: this.newFiles })
- this.ProjectMock.expects('findOneAndUpdate')
- .withArgs(
- { _id: this.project._id, 'rootFolder.0.docs.0': { $exists: true } },
- {
- $set: { 'rootFolder.0.docs.0.name': this.newName },
- $inc: { version: 1 },
- }
- )
- .chain('exec')
- .resolves(this.project)
- this.result = await this.subject.promises.renameEntity(
- this.project._id,
- this.doc._id,
- 'doc',
- this.newName
- )
- })
- it('should update the database', function () {
- this.ProjectMock.verify()
- })
- it('returns info', function () {
- expect(this.result).to.deep.equal({
- project: this.project,
- startPath: '/test-doc.txt',
- endPath: '/new.tex',
- rev: this.doc.rev,
- changes: {
- oldDocs: this.oldDocs,
- newDocs: this.newDocs,
- oldFiles: this.oldFiles,
- newFiles: this.newFiles,
- newProject: this.project,
- },
- })
- })
- })
- describe('name already exists', function () {
- it('should throw an error', async function () {
- await expect(
- this.subject.promises.renameEntity(
- this.project._id,
- this.doc._id,
- 'doc',
- this.folder.name
- )
- ).to.be.rejectedWith(Errors.InvalidNameError)
- })
- })
- })
- describe('_putElement', function () {
- describe('updating the project', function () {
- describe('when the parent folder is given', function () {
- beforeEach(function () {
- this.newFile = { _id: ObjectId(), name: 'new file.png' }
- this.ProjectMock.expects('findOneAndUpdate')
- .withArgs(
- {
- _id: this.project._id,
- 'rootFolder.0.folders.0': { $exists: true },
- },
- {
- $push: { 'rootFolder.0.folders.0.fileRefs': this.newFile },
- $inc: { version: 1 },
- }
- )
- .chain('exec')
- .resolves(this.project)
- })
- it('should update the database', async function () {
- await this.subject.promises._putElement(
- this.project,
- this.folder._id,
- this.newFile,
- 'files'
- )
- this.ProjectMock.verify()
- })
- it('should add an s onto the type if not included', async function () {
- await this.subject.promises._putElement(
- this.project,
- this.folder._id,
- this.newFile,
- 'file'
- )
- this.ProjectMock.verify()
- })
- })
- describe('error cases', function () {
- it('should throw an error if element is null', async function () {
- await expect(
- this.subject.promises._putElement(
- this.project,
- this.folder._id,
- null,
- 'file'
- )
- ).to.be.rejected
- })
- it('should error if the element has no _id', async function () {
- const file = { name: 'something' }
- await expect(
- this.subject.promises._putElement(
- this.project,
- this.folder._id,
- file,
- 'file'
- )
- ).to.be.rejected
- })
- it('should error if element name contains invalid characters', async function () {
- const file = { _id: ObjectId(), name: 'something*bad' }
- await expect(
- this.subject.promises._putElement(
- this.project,
- this.folder._id,
- file,
- 'file'
- )
- ).to.be.rejected
- })
- it('should error if element name is too long', async function () {
- const file = {
- _id: ObjectId(),
- name: 'long-'.repeat(1000) + 'something',
- }
- await expect(
- this.subject.promises._putElement(
- this.project,
- this.folder._id,
- file,
- 'file'
- )
- ).to.be.rejectedWith(Errors.InvalidNameError)
- })
- it('should error if the folder name is too long', async function () {
- const file = {
- _id: ObjectId(),
- name: 'something',
- }
- this.ProjectLocator.promises.findElement
- .withArgs({
- project: this.project,
- element_id: this.folder._id,
- type: 'folder',
- })
- .resolves({
- element: this.folder,
- path: { fileSystem: 'subdir/'.repeat(1000) + 'foo' },
- })
- await expect(
- this.subject.promises._putElement(
- this.project,
- this.folder._id,
- file,
- 'file'
- )
- ).to.be.rejectedWith(Errors.InvalidNameError)
- })
- ;['file', 'doc', 'folder'].forEach(entityType => {
- it(`should error if a ${entityType} already exists with the same name`, async function () {
- const file = {
- _id: ObjectId(),
- name: this[entityType].name,
- }
- await expect(
- this.subject.promises._putElement(
- this.project,
- null,
- file,
- 'file'
- )
- ).to.be.rejectedWith(Errors.InvalidNameError)
- })
- })
- })
- })
- describe('when the parent folder is not given', function () {
- it('should default to root folder insert', async function () {
- this.newFile = { _id: ObjectId(), name: 'new file.png' }
- this.ProjectMock.expects('findOneAndUpdate')
- .withArgs(
- { _id: this.project._id, 'rootFolder.0': { $exists: true } },
- {
- $push: { 'rootFolder.0.fileRefs': this.newFile },
- $inc: { version: 1 },
- }
- )
- .chain('exec')
- .resolves(this.project)
- await this.subject.promises._putElement(
- this.project,
- this.rootFolder._id,
- this.newFile,
- 'file'
- )
- })
- })
- })
- describe('_insertDeletedFileReference', function () {
- beforeEach(async function () {
- this.DeletedFileMock.expects('create')
- .withArgs({
- projectId: this.project._id,
- _id: this.file._id,
- name: this.file.name,
- linkedFileData: this.file.linkedFileData,
- hash: this.file.hash,
- deletedAt: sinon.match.date,
- })
- .resolves()
- await this.subject.promises._insertDeletedFileReference(
- this.project._id,
- this.file
- )
- })
- it('should update the database', function () {
- this.DeletedFileMock.verify()
- })
- })
- describe('createNewFolderStructure', function () {
- beforeEach(function () {
- this.mockRootFolder = 'MOCK_ROOT_FOLDER'
- this.docUploads = ['MOCK_DOC_UPLOAD']
- this.fileUploads = ['MOCK_FILE_UPLOAD']
- this.FolderStructureBuilder.buildFolderStructure
- .withArgs(this.docUploads, this.fileUploads)
- .returns(this.mockRootFolder)
- this.updateExpectation = this.ProjectMock.expects('findOneAndUpdate')
- .withArgs(
- {
- _id: this.project._id,
- 'rootFolder.0.folders.0': { $exists: false },
- 'rootFolder.0.docs.0': { $exists: false },
- 'rootFolder.0.files.0': { $exists: false },
- },
- { $set: { rootFolder: [this.mockRootFolder] }, $inc: { version: 1 } },
- { new: true, lean: true, fields: { version: 1 } }
- )
- .chain('exec')
- })
- describe('happy path', function () {
- beforeEach(async function () {
- this.updateExpectation.resolves({ version: 1 })
- await this.subject.promises.createNewFolderStructure(
- this.project._id,
- this.docUploads,
- this.fileUploads
- )
- })
- it('updates the database', function () {
- this.ProjectMock.verify()
- })
- })
- describe("when the update doesn't find a matching document", function () {
- beforeEach(async function () {
- this.updateExpectation.resolves(null)
- })
- it('throws an error', async function () {
- await expect(
- this.subject.promises.createNewFolderStructure(
- this.project._id,
- this.docUploads,
- this.fileUploads
- )
- ).to.be.rejected
- })
- })
- })
- describe('replaceDocWithFile', function () {
- it('should simultaneously remove the doc and add the file', async function () {
- this.ProjectMock.expects('findOneAndUpdate')
- .withArgs(
- { _id: this.project._id, 'rootFolder.0': { $exists: true } },
- {
- $pull: { 'rootFolder.0.docs': { _id: this.doc._id } },
- $push: { 'rootFolder.0.fileRefs': this.file },
- $inc: { version: 1 },
- },
- { new: true }
- )
- .chain('exec')
- .resolves(this.project)
- await this.subject.promises.replaceDocWithFile(
- this.project._id,
- this.doc._id,
- this.file
- )
- this.ProjectMock.verify()
- })
- })
- describe('replaceFileWithDoc', function () {
- it('should simultaneously remove the file and add the doc', async function () {
- this.ProjectMock.expects('findOneAndUpdate')
- .withArgs(
- { _id: this.project._id, 'rootFolder.0': { $exists: true } },
- {
- $pull: { 'rootFolder.0.fileRefs': { _id: this.file._id } },
- $push: { 'rootFolder.0.docs': this.doc },
- $inc: { version: 1 },
- },
- { new: true }
- )
- .chain('exec')
- .resolves(this.project)
- await this.subject.promises.replaceFileWithDoc(
- this.project._id,
- this.file._id,
- this.doc
- )
- this.ProjectMock.verify()
- })
- })
- })
|