ソースを参照

[PnP migration] Remove mock-fs dependency (#33835)

GitOrigin-RevId: ff8df32d85b2ecd2837c9eee6d6d2b3b95285239
Anna Claire Fields 2 ヶ月 前
コミット
97247b8ea5

+ 0 - 1
libraries/object-persistor/package.json

@@ -38,7 +38,6 @@
     "mocha": "^11.1.0",
     "mocha-junit-reporter": "^2.2.1",
     "mocha-multi-reporters": "^1.5.1",
-    "mock-fs": "^5.2.0",
     "mongodb": "6.12.0",
     "sandboxed-module": "^2.0.4",
     "sinon": "^9.2.4",

+ 6 - 2
libraries/object-persistor/src/FSPersistor.js

@@ -3,9 +3,12 @@ const fs = require('node:fs')
 const fsPromises = require('node:fs/promises')
 const { glob } = require('glob')
 const Path = require('node:path')
+const { promisify } = require('node:util')
 const { PassThrough } = require('node:stream')
 const { pipeline } = require('node:stream/promises')
 
+const openCb = promisify(fs.open)
+
 const AbstractPersistor = require('./AbstractPersistor')
 const { ReadError, WriteError, NotImplementedError } = require('./Errors')
 const PersistorHelper = require('./PersistorHelper')
@@ -85,8 +88,9 @@ module.exports = class FSPersistor extends AbstractPersistor {
     })
     const fsPath = this._getFsPath(location, name, opts.useSubdirectories)
 
+    let fd
     try {
-      opts.fd = await fsPromises.open(fsPath, 'r')
+      fd = await openCb(fsPath, 'r')
     } catch (err) {
       throw PersistorHelper.wrapError(
         err,
@@ -96,7 +100,7 @@ module.exports = class FSPersistor extends AbstractPersistor {
       )
     }
 
-    const stream = fs.createReadStream(null, opts)
+    const stream = fs.createReadStream(null, { ...opts, fd })
     // Return a PassThrough stream with a minimal interface. It will buffer until the caller starts reading. It will emit errors from the source stream (Stream.pipeline passes errors along).
     const pass = new PassThrough()
     pipeline(stream, observer, pass).catch(() => {})

+ 122 - 54
libraries/object-persistor/test/unit/FSPersistorTests.js

@@ -1,6 +1,6 @@
 const crypto = require('node:crypto')
+const os = require('node:os')
 const { expect } = require('chai')
-const mockFs = require('mock-fs')
 const fs = require('node:fs')
 const fsPromises = require('node:fs/promises')
 const Path = require('node:path')
@@ -10,22 +10,59 @@ const Errors = require('../../src/Errors')
 
 const MODULE_PATH = '../../src/FSPersistor.js'
 
+function createTree(base, tree) {
+  fs.mkdirSync(base, { recursive: true })
+  for (const [name, content] of Object.entries(tree)) {
+    const fullPath = Path.join(base, name)
+    if (Buffer.isBuffer(content) || typeof content === 'string') {
+      fs.writeFileSync(fullPath, content)
+    } else if (content && typeof content.symlink === 'string') {
+      fs.symlinkSync(content.symlink, fullPath)
+    } else {
+      createTree(fullPath, content)
+    }
+  }
+}
+
 describe('FSPersistorTests', function () {
-  const localFiles = {
-    '/uploads/info.txt': Buffer.from('This information is critical', {
+  const fileContents = {
+    'info.txt': Buffer.from('This information is critical', {
       encoding: 'utf-8',
     }),
-    '/uploads/other.txt': Buffer.from('Some other content', {
+    'other.txt': Buffer.from('Some other content', {
       encoding: 'utf-8',
     }),
   }
-  const location = '/bucket'
+  let tmpDir
+  let location
+  let notADirPath
   const files = {
     wombat: 'animals/wombat.tex',
     giraffe: 'animals/giraffe.tex',
     potato: 'vegetables/potato.tex',
   }
 
+  beforeEach(function () {
+    tmpDir = fs.mkdtempSync(Path.join(os.tmpdir(), 'fs-persistor-test-'))
+    createTree(tmpDir, {
+      uploads: {
+        'info.txt': fileContents['info.txt'],
+        'other.txt': fileContents['other.txt'],
+      },
+      'not-a-dir':
+        'This regular file is meant to prevent using this path as a directory',
+      directory: {
+        subdirectory: {},
+      },
+    })
+    notADirPath = Path.join(tmpDir, 'not-a-dir')
+    location = Path.join(tmpDir, 'bucket')
+  })
+
+  afterEach(function () {
+    fs.rmSync(tmpDir, { recursive: true })
+  })
+
   const scenarios = [
     {
       description: 'default settings',
@@ -54,31 +91,26 @@ describe('FSPersistorTests', function () {
         persistor = new FSPersistor(scenario.settings)
       })
 
-      beforeEach(function () {
-        mockFs({
-          ...localFiles,
-          '/not-a-dir':
-            'This regular file is meant to prevent using this path as a directory',
-          '/directory/subdirectory': {},
-        })
-      })
-
-      afterEach(function () {
-        mockFs.restore()
-      })
-
       describe('sendFile', function () {
         it('should copy the file', async function () {
-          await persistor.sendFile(location, files.wombat, '/uploads/info.txt')
+          await persistor.sendFile(
+            location,
+            files.wombat,
+            Path.join(tmpDir, 'uploads', 'info.txt')
+          )
           const contents = await fsPromises.readFile(
             scenario.fsPath(files.wombat)
           )
-          expect(contents.equals(localFiles['/uploads/info.txt'])).to.be.true
+          expect(contents.equals(fileContents['info.txt'])).to.be.true
         })
 
         it('should return an error if the file cannot be stored', async function () {
           await expect(
-            persistor.sendFile('/not-a-dir', files.wombat, '/uploads/info.txt')
+            persistor.sendFile(
+              notADirPath,
+              files.wombat,
+              Path.join(tmpDir, 'uploads', 'info.txt')
+            )
           ).to.be.rejectedWith(Errors.WriteError)
         })
       })
@@ -88,7 +120,9 @@ describe('FSPersistorTests', function () {
 
         describe("when the file doesn't exist", function () {
           beforeEach(function () {
-            stream = fs.createReadStream('/uploads/info.txt')
+            stream = fs.createReadStream(
+              Path.join(tmpDir, 'uploads', 'info.txt')
+            )
           })
 
           it('should write the stream to disk', async function () {
@@ -96,7 +130,7 @@ describe('FSPersistorTests', function () {
             const contents = await fsPromises.readFile(
               scenario.fsPath(files.wombat)
             )
-            expect(contents.equals(localFiles['/uploads/info.txt'])).to.be.true
+            expect(contents.equals(fileContents['info.txt'])).to.be.true
           })
 
           it('should delete the temporary file', async function () {
@@ -109,7 +143,7 @@ describe('FSPersistorTests', function () {
           describe('on error', function () {
             beforeEach(async function () {
               await expect(
-                persistor.sendStream('/not-a-dir', files.wombat, stream)
+                persistor.sendStream(notADirPath, files.wombat, stream)
               ).to.be.rejectedWith(Errors.WriteError)
             })
 
@@ -129,13 +163,12 @@ describe('FSPersistorTests', function () {
           describe('when the md5 hash matches', function () {
             it('should write the stream to disk', async function () {
               await persistor.sendStream(location, files.wombat, stream, {
-                sourceMd5: md5(localFiles['/uploads/info.txt']),
+                sourceMd5: md5(fileContents['info.txt']),
               })
               const contents = await fsPromises.readFile(
                 scenario.fsPath(files.wombat)
               )
-              expect(contents.equals(localFiles['/uploads/info.txt'])).to.be
-                .true
+              expect(contents.equals(fileContents['info.txt'])).to.be.true
             })
           })
 
@@ -169,9 +202,11 @@ describe('FSPersistorTests', function () {
             await persistor.sendFile(
               location,
               files.wombat,
-              '/uploads/info.txt'
+              Path.join(tmpDir, 'uploads', 'info.txt')
+            )
+            stream = fs.createReadStream(
+              Path.join(tmpDir, 'uploads', 'other.txt')
             )
-            stream = fs.createReadStream('/uploads/other.txt')
           })
 
           it('should write the stream to disk', async function () {
@@ -179,7 +214,7 @@ describe('FSPersistorTests', function () {
             const contents = await fsPromises.readFile(
               scenario.fsPath(files.wombat)
             )
-            expect(contents.equals(localFiles['/uploads/other.txt'])).to.be.true
+            expect(contents.equals(fileContents['other.txt'])).to.be.true
           })
 
           it('should delete the temporary file', async function () {
@@ -192,7 +227,7 @@ describe('FSPersistorTests', function () {
           describe('on error', function () {
             beforeEach(async function () {
               await expect(
-                persistor.sendStream('/not-a-dir', files.wombat, stream)
+                persistor.sendStream(notADirPath, files.wombat, stream)
               ).to.be.rejectedWith(Errors.WriteError)
             })
 
@@ -200,8 +235,7 @@ describe('FSPersistorTests', function () {
               const contents = await fsPromises.readFile(
                 scenario.fsPath(files.wombat)
               )
-              expect(contents.equals(localFiles['/uploads/info.txt'])).to.be
-                .true
+              expect(contents.equals(fileContents['info.txt'])).to.be.true
             })
 
             it('should delete the temporary file', async function () {
@@ -215,13 +249,12 @@ describe('FSPersistorTests', function () {
           describe('when the md5 hash matches', function () {
             it('should write the stream to disk', async function () {
               await persistor.sendStream(location, files.wombat, stream, {
-                sourceMd5: md5(localFiles['/uploads/other.txt']),
+                sourceMd5: md5(fileContents['other.txt']),
               })
               const contents = await fsPromises.readFile(
                 scenario.fsPath(files.wombat)
               )
-              expect(contents.equals(localFiles['/uploads/other.txt'])).to.be
-                .true
+              expect(contents.equals(fileContents['other.txt'])).to.be.true
             })
           })
 
@@ -238,8 +271,7 @@ describe('FSPersistorTests', function () {
               const contents = await fsPromises.readFile(
                 scenario.fsPath(files.wombat)
               )
-              expect(contents.equals(localFiles['/uploads/info.txt'])).to.be
-                .true
+              expect(contents.equals(fileContents['info.txt'])).to.be.true
             })
 
             it('should delete the temporary file', async function () {
@@ -254,13 +286,17 @@ describe('FSPersistorTests', function () {
 
       describe('getObjectStream', function () {
         beforeEach(async function () {
-          await persistor.sendFile(location, files.wombat, '/uploads/info.txt')
+          await persistor.sendFile(
+            location,
+            files.wombat,
+            Path.join(tmpDir, 'uploads', 'info.txt')
+          )
         })
 
         it('should return a string with the object contents', async function () {
           const stream = await persistor.getObjectStream(location, files.wombat)
           const contents = await streamToBuffer(stream)
-          expect(contents.equals(localFiles['/uploads/info.txt'])).to.be.true
+          expect(contents.equals(fileContents['info.txt'])).to.be.true
         })
 
         it('should support ranges', async function () {
@@ -274,8 +310,8 @@ describe('FSPersistorTests', function () {
           )
           const contents = await streamToBuffer(stream)
           // end is inclusive in ranges, but exclusive in slice()
-          expect(contents.equals(localFiles['/uploads/info.txt'].slice(5, 17)))
-            .to.be.true
+          expect(contents.equals(fileContents['info.txt'].slice(5, 17))).to.be
+            .true
         })
 
         it('should give a NotFoundError if the file does not exist', async function () {
@@ -287,13 +323,17 @@ describe('FSPersistorTests', function () {
 
       describe('getObjectSize', function () {
         beforeEach(async function () {
-          await persistor.sendFile(location, files.wombat, '/uploads/info.txt')
+          await persistor.sendFile(
+            location,
+            files.wombat,
+            Path.join(tmpDir, 'uploads', 'info.txt')
+          )
         })
 
         it('should return the file size', async function () {
           expect(
             await persistor.getObjectSize(location, files.wombat)
-          ).to.equal(localFiles['/uploads/info.txt'].length)
+          ).to.equal(fileContents['info.txt'].length)
         })
 
         it('should throw a NotFoundError if the file does not exist', async function () {
@@ -305,7 +345,11 @@ describe('FSPersistorTests', function () {
 
       describe('copyObject', function () {
         beforeEach(async function () {
-          await persistor.sendFile(location, files.wombat, '/uploads/info.txt')
+          await persistor.sendFile(
+            location,
+            files.wombat,
+            Path.join(tmpDir, 'uploads', 'info.txt')
+          )
         })
 
         it('Should copy the file to the new location', async function () {
@@ -313,13 +357,17 @@ describe('FSPersistorTests', function () {
           const contents = await fsPromises.readFile(
             scenario.fsPath(files.potato)
           )
-          expect(contents.equals(localFiles['/uploads/info.txt'])).to.be.true
+          expect(contents.equals(fileContents['info.txt'])).to.be.true
         })
       })
 
       describe('deleteObject', function () {
         beforeEach(async function () {
-          await persistor.sendFile(location, files.wombat, '/uploads/info.txt')
+          await persistor.sendFile(
+            location,
+            files.wombat,
+            Path.join(tmpDir, 'uploads', 'info.txt')
+          )
           await fsPromises.access(scenario.fsPath(files.wombat))
         })
 
@@ -337,7 +385,11 @@ describe('FSPersistorTests', function () {
       describe('deleteDirectory', function () {
         beforeEach(async function () {
           for (const file of Object.values(files)) {
-            await persistor.sendFile(location, file, '/uploads/info.txt')
+            await persistor.sendFile(
+              location,
+              file,
+              Path.join(tmpDir, 'uploads', 'info.txt')
+            )
             await fsPromises.access(scenario.fsPath(file))
           }
         })
@@ -365,7 +417,11 @@ describe('FSPersistorTests', function () {
 
       describe('checkIfObjectExists', function () {
         beforeEach(async function () {
-          await persistor.sendFile(location, files.wombat, '/uploads/info.txt')
+          await persistor.sendFile(
+            location,
+            files.wombat,
+            Path.join(tmpDir, 'uploads', 'info.txt')
+          )
         })
 
         it('should return true for existing files', async function () {
@@ -384,13 +440,17 @@ describe('FSPersistorTests', function () {
       describe('directorySize', function () {
         beforeEach(async function () {
           for (const file of Object.values(files)) {
-            await persistor.sendFile(location, file, '/uploads/info.txt')
+            await persistor.sendFile(
+              location,
+              file,
+              Path.join(tmpDir, 'uploads', 'info.txt')
+            )
           }
         })
 
         it('should sum directory files size', async function () {
           expect(await persistor.directorySize(location, 'animals')).to.equal(
-            2 * localFiles['/uploads/info.txt'].length
+            2 * fileContents['info.txt'].length
           )
         })
 
@@ -404,7 +464,11 @@ describe('FSPersistorTests', function () {
       describe('listDirectoryKeys', function () {
         beforeEach(async function () {
           for (const file of Object.values(files)) {
-            await persistor.sendFile(location, file, '/uploads/info.txt')
+            await persistor.sendFile(
+              location,
+              file,
+              Path.join(tmpDir, 'uploads', 'info.txt')
+            )
           }
         })
 
@@ -427,7 +491,11 @@ describe('FSPersistorTests', function () {
       describe('listDirectoryStats', function () {
         beforeEach(async function () {
           for (const file of Object.values(files)) {
-            await persistor.sendFile(location, file, '/uploads/info.txt')
+            await persistor.sendFile(
+              location,
+              file,
+              Path.join(tmpDir, 'uploads', 'info.txt')
+            )
           }
         })
 
@@ -438,7 +506,7 @@ describe('FSPersistorTests', function () {
           expect(keys).to.include(scenario.fsPath(files.wombat))
           expect(keys).to.include(scenario.fsPath(files.giraffe))
           for (const stat of stats) {
-            expect(stat.size).to.equal(localFiles['/uploads/info.txt'].length)
+            expect(stat.size).to.equal(fileContents['info.txt'].length)
           }
         })
 

+ 0 - 1
package.json

@@ -101,7 +101,6 @@
     "knip": "5.64.1",
     "eslint-plugin-testing-library": "7.5.3",
     "chart.js": "4.0.1",
-    "mock-fs": "5.2.0",
     "@customerio/cdp-analytics-node": "0.3.9",
     "@google-cloud/bigquery": "8.1.1",
     "moment": "2.29.4",

+ 0 - 1
services/clsi/package.json

@@ -46,7 +46,6 @@
     "mocha": "^11.1.0",
     "mocha-junit-reporter": "^2.2.1",
     "mocha-multi-reporters": "^1.5.1",
-    "mock-fs": "^5.1.2",
     "node-fetch": "^2.7.0",
     "nyc": "^17.1.0",
     "sinon": "~9.0.1",

+ 7 - 7
services/clsi/test/unit/js/DraftModeManager.test.js

@@ -1,7 +1,8 @@
 import { vi, expect, describe, beforeEach, afterEach, it } from 'vitest'
 import Path from 'node:path'
+import fs from 'node:fs'
 import fsPromises from 'node:fs/promises'
-import mockFs from 'mock-fs'
+import os from 'node:os'
 
 const MODULE_PATH = Path.join(
   import.meta.dirname,
@@ -15,20 +16,19 @@ describe('DraftModeManager', () => {
     }))
 
     ctx.DraftModeManager = (await import(MODULE_PATH)).default
-    ctx.filename = '/mock/filename.tex'
+    ctx.tmpDir = fs.mkdtempSync(Path.join(os.tmpdir(), 'draft-mode-test-'))
+    ctx.filename = Path.join(ctx.tmpDir, 'filename.tex')
     ctx.contents = `\
 \\documentclass{article}
 \\begin{document}
 Hello world
 \\end{document}\
 `
-    mockFs({
-      [ctx.filename]: ctx.contents,
-    })
+    fs.writeFileSync(ctx.filename, ctx.contents)
   })
 
-  afterEach(() => {
-    mockFs.restore()
+  afterEach(ctx => {
+    fs.rmSync(ctx.tmpDir, { recursive: true })
   })
 
   describe('injectDraftMode', () => {

+ 29 - 19
services/clsi/test/unit/js/OutputFileFinder.test.js

@@ -1,6 +1,6 @@
-import sinon from 'sinon'
 import { expect, describe, beforeEach, afterEach, it } from 'vitest'
-import mockFs from 'mock-fs'
+import fs from 'node:fs'
+import os from 'node:os'
 import path from 'node:path'
 
 const modulePath = path.join(
@@ -8,30 +8,40 @@ const modulePath = path.join(
   '../../../app/js/OutputFileFinder'
 )
 
+function createTree(base, tree) {
+  fs.mkdirSync(base, { recursive: true })
+  for (const [name, content] of Object.entries(tree)) {
+    const fullPath = path.join(base, name)
+    if (Buffer.isBuffer(content) || typeof content === 'string') {
+      fs.writeFileSync(fullPath, content)
+    } else if (content && content.symlink) {
+      fs.symlinkSync(content.symlink, fullPath)
+    } else {
+      createTree(fullPath, content)
+    }
+  }
+}
+
 describe('OutputFileFinder', function () {
   beforeEach(async function (ctx) {
     ctx.OutputFileFinder = (await import(modulePath)).default
-    ctx.directory = '/test/dir'
-    ctx.callback = sinon.stub()
-
-    mockFs({
-      [ctx.directory]: {
-        resource: {
-          'path.tex': 'a source file',
-        },
-        'output.pdf': 'a generated pdf file',
-        extra: {
-          'file.tex': 'a generated tex file',
-        },
-        'sneaky-file': mockFs.symlink({
-          path: '../foo',
-        }),
+    ctx.directory = fs.mkdtempSync(
+      path.join(os.tmpdir(), 'output-finder-test-')
+    )
+    createTree(ctx.directory, {
+      resource: {
+        'path.tex': 'a source file',
+      },
+      'output.pdf': 'a generated pdf file',
+      extra: {
+        'file.tex': 'a generated tex file',
       },
+      'sneaky-file': { symlink: '../foo' },
     })
   })
 
-  afterEach(function () {
-    mockFs.restore()
+  afterEach(function (ctx) {
+    fs.rmSync(ctx.directory, { recursive: true })
   })
 
   describe('findOutputFiles', function () {

+ 0 - 1
services/web/package.json

@@ -370,7 +370,6 @@
     "mocha-each": "^2.0.1",
     "mocha-junit-reporter": "^2.2.1",
     "mocha-multi-reporters": "^1.5.1",
-    "mock-fs": "^5.1.2",
     "nock": "^13.5.6",
     "nvd3": "^1.8.6",
     "nyc": "^17.1.0",

+ 78 - 41
services/web/test/unit/src/Uploads/FileSystemImportManager.test.mjs

@@ -1,6 +1,8 @@
 import { vi, expect } from 'vitest'
 import sinon from 'sinon'
-import mockFs from 'mock-fs'
+import fs from 'node:fs'
+import os from 'node:os'
+import path from 'node:path'
 import mongodb from 'mongodb-legacy'
 import Settings from '@overleaf/settings'
 
@@ -9,6 +11,20 @@ const { ObjectId } = mongodb
 const MODULE_PATH =
   '../../../../app/src/Features/Uploads/FileSystemImportManager.mjs'
 
+function createTree(base, tree) {
+  fs.mkdirSync(base, { recursive: true })
+  for (const [name, content] of Object.entries(tree)) {
+    const fullPath = path.join(base, name)
+    if (Buffer.isBuffer(content) || typeof content === 'string') {
+      fs.writeFileSync(fullPath, content)
+    } else if (content && typeof content.symlink === 'string') {
+      fs.symlinkSync(content.symlink, fullPath)
+    } else {
+      createTree(fullPath, content)
+    }
+  }
+}
+
 describe('FileSystemImportManager', function () {
   beforeEach(async function (ctx) {
     ctx.projectId = new ObjectId()
@@ -48,14 +64,16 @@ describe('FileSystemImportManager', function () {
 
   describe('importDir', function () {
     beforeEach(async function (ctx) {
-      mockFs({
+      ctx.tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'import-test-'))
+      ctx.importPath = path.join(ctx.tmpDir, 'import-test')
+      createTree(ctx.tmpDir, {
         'import-test': {
           'main.tex': 'My thesis',
-          'link-to-main.tex': mockFs.symlink({ path: 'import-test/main.tex' }),
-          '.DS_Store': 'Should be ignored',
-          images: {
-            'cat.jpg': Buffer.from([1, 2, 3, 4]),
+          'link-to-main.tex': {
+            symlink: path.join(ctx.tmpDir, 'import-test', 'main.tex'),
           },
+          '.DS_Store': 'Should be ignored',
+          images: { 'cat.jpg': Buffer.from([1, 2, 3, 4]) },
           'line-endings': {
             'unix.txt': 'one\ntwo\nthree',
             'mac.txt': 'uno\rdos\rtres',
@@ -67,15 +85,16 @@ describe('FileSystemImportManager', function () {
             'latin1.txt': Buffer.from('tétanisant!', 'latin1'),
           },
         },
-        symlink: mockFs.symlink({ path: 'import-test' }),
+        symlink: { symlink: path.join(ctx.tmpDir, 'import-test') },
       })
-      ctx.entries =
-        await ctx.FileSystemImportManager.promises.importDir('import-test')
+      ctx.entries = await ctx.FileSystemImportManager.promises.importDir(
+        ctx.importPath
+      )
       ctx.projectPaths = ctx.entries.map(x => x.projectPath)
     })
 
-    afterEach(function () {
-      mockFs.restore()
+    afterEach(function (ctx) {
+      fs.rmSync(ctx.tmpDir, { recursive: true, force: true })
     })
 
     it('should import regular docs', function (ctx) {
@@ -98,7 +117,7 @@ describe('FileSystemImportManager', function () {
       expect(ctx.entries).to.deep.include({
         type: 'file',
         projectPath: '/images/cat.jpg',
-        fsPath: 'import-test/images/cat.jpg',
+        fsPath: path.join(ctx.importPath, 'images', 'cat.jpg'),
       })
     })
 
@@ -142,15 +161,20 @@ describe('FileSystemImportManager', function () {
     })
 
     it('should error when the root folder is a symlink', async function (ctx) {
-      await expect(ctx.FileSystemImportManager.promises.importDir('symlink')).to
-        .be.rejected
+      await expect(
+        ctx.FileSystemImportManager.promises.importDir(
+          path.join(ctx.tmpDir, 'symlink')
+        )
+      ).to.be.rejected
     })
   })
 
   describe('addEntity', function () {
     describe('with directory', function () {
       beforeEach(async function (ctx) {
-        mockFs({
+        ctx.tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'addentity-dir-'))
+        ctx.fsPath = path.join(ctx.tmpDir, 'path', 'to', 'folder')
+        createTree(ctx.tmpDir, {
           path: {
             to: {
               folder: {
@@ -160,19 +184,18 @@ describe('FileSystemImportManager', function () {
             },
           },
         })
-
         await ctx.FileSystemImportManager.promises.addEntity(
           ctx.userId,
           ctx.projectId,
           ctx.folderId,
           'folder',
-          'path/to/folder',
+          ctx.fsPath,
           false
         )
       })
 
-      afterEach(function () {
-        mockFs.restore()
+      afterEach(function (ctx) {
+        fs.rmSync(ctx.tmpDir, { recursive: true, force: true })
       })
 
       it('should add a folder to the project', function (ctx) {
@@ -197,7 +220,7 @@ describe('FileSystemImportManager', function () {
           ctx.projectId,
           ctx.newFolderId,
           'image.jpg',
-          'path/to/folder/image.jpg',
+          path.join(ctx.fsPath, 'image.jpg'),
           null,
           'upload',
           ctx.userId
@@ -206,12 +229,16 @@ describe('FileSystemImportManager', function () {
     })
 
     describe('with binary file', function () {
-      beforeEach(function () {
-        mockFs({ 'uploaded-file': Buffer.from([1, 2, 3, 4]) })
+      beforeEach(function (ctx) {
+        ctx.tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'addentity-bin-'))
+        ctx.fsPath = path.join(ctx.tmpDir, 'uploaded-file')
+        createTree(ctx.tmpDir, {
+          'uploaded-file': Buffer.from([1, 2, 3, 4]),
+        })
       })
 
-      afterEach(function () {
-        mockFs.restore()
+      afterEach(function (ctx) {
+        fs.rmSync(ctx.tmpDir, { recursive: true, force: true })
       })
 
       describe('with replace set to false', function () {
@@ -221,7 +248,7 @@ describe('FileSystemImportManager', function () {
             ctx.projectId,
             ctx.folderId,
             'image.jpg',
-            'uploaded-file',
+            ctx.fsPath,
             false
           )
         })
@@ -231,7 +258,7 @@ describe('FileSystemImportManager', function () {
             ctx.projectId,
             ctx.folderId,
             'image.jpg',
-            'uploaded-file',
+            ctx.fsPath,
             null,
             'upload',
             ctx.userId
@@ -246,7 +273,7 @@ describe('FileSystemImportManager', function () {
             ctx.projectId,
             ctx.folderId,
             'image.jpg',
-            'uploaded-file',
+            ctx.fsPath,
             true
           )
         })
@@ -256,7 +283,7 @@ describe('FileSystemImportManager', function () {
             ctx.projectId,
             ctx.folderId,
             'image.jpg',
-            'uploaded-file',
+            ctx.fsPath,
             null,
             'upload',
             ctx.userId
@@ -271,16 +298,20 @@ describe('FileSystemImportManager', function () {
       ['Windows', '\r\n'],
     ]) {
       describe(`with text file (${lineEndingDescription} line endings)`, function () {
-        beforeEach(function () {
-          mockFs({
+        beforeEach(function (ctx) {
+          ctx.tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'addentity-txt-'))
+          ctx.fsPath = path.join(ctx.tmpDir, 'path', 'to', 'uploaded-file')
+          createTree(ctx.tmpDir, {
             path: {
-              to: { 'uploaded-file': `one${lineEnding}two${lineEnding}three` },
+              to: {
+                'uploaded-file': `one${lineEnding}two${lineEnding}three`,
+              },
             },
           })
         })
 
-        afterEach(function () {
-          mockFs.restore()
+        afterEach(function (ctx) {
+          fs.rmSync(ctx.tmpDir, { recursive: true, force: true })
         })
 
         describe('with replace set to false', function () {
@@ -290,7 +321,7 @@ describe('FileSystemImportManager', function () {
               ctx.projectId,
               ctx.folderId,
               'doc.tex',
-              'path/to/uploaded-file',
+              ctx.fsPath,
               false
             )
           })
@@ -314,7 +345,7 @@ describe('FileSystemImportManager', function () {
               ctx.projectId,
               ctx.folderId,
               'doc.tex',
-              'path/to/uploaded-file',
+              ctx.fsPath,
               true
             )
           })
@@ -334,14 +365,20 @@ describe('FileSystemImportManager', function () {
     }
 
     describe('with symlink', function () {
-      beforeEach(function () {
-        mockFs({
-          path: { to: { symlink: mockFs.symlink({ path: '/etc/passwd' }) } },
+      beforeEach(function (ctx) {
+        ctx.tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'addentity-sym-'))
+        ctx.fsPath = path.join(ctx.tmpDir, 'path', 'to', 'symlink')
+        createTree(ctx.tmpDir, {
+          path: {
+            to: {
+              symlink: { symlink: '/etc/passwd' },
+            },
+          },
         })
       })
 
-      afterEach(function () {
-        mockFs.restore()
+      afterEach(function (ctx) {
+        fs.rmSync(ctx.tmpDir, { recursive: true, force: true })
       })
 
       it('should stop with an error', async function (ctx) {
@@ -351,7 +388,7 @@ describe('FileSystemImportManager', function () {
             ctx.projectId,
             ctx.folderId,
             'main.tex',
-            'path/to/symlink',
+            ctx.fsPath,
             false
           )
         ).to.be.rejectedWith('path is symlink')

+ 0 - 10
yarn.lock

@@ -6498,7 +6498,6 @@ __metadata:
     mocha: "npm:^11.1.0"
     mocha-junit-reporter: "npm:^2.2.1"
     mocha-multi-reporters: "npm:^1.5.1"
-    mock-fs: "npm:^5.1.2"
     multer: "npm:2.1.1"
     node-fetch: "npm:^2.7.0"
     nyc: "npm:^17.1.0"
@@ -6878,7 +6877,6 @@ __metadata:
     mocha: "npm:^11.1.0"
     mocha-junit-reporter: "npm:^2.2.1"
     mocha-multi-reporters: "npm:^1.5.1"
-    mock-fs: "npm:^5.2.0"
     mongodb: "npm:6.12.0"
     range-parser: "npm:^1.2.1"
     sandboxed-module: "npm:^2.0.4"
@@ -7475,7 +7473,6 @@ __metadata:
     mocha-each: "npm:^2.0.1"
     mocha-junit-reporter: "npm:^2.2.1"
     mocha-multi-reporters: "npm:^1.5.1"
-    mock-fs: "npm:^5.1.2"
     moment: "npm:^2.29.4"
     mongodb-legacy: "npm:6.1.3"
     mongoose: "npm:8.22.1"
@@ -24737,13 +24734,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"mock-fs@npm:5.2.0":
-  version: 5.2.0
-  resolution: "mock-fs@npm:5.2.0"
-  checksum: 10c0/e917e71295ee9d805c7d9ab0214a66658db0212f35731ba0c75dc1ccbb9964e6787a6d725561f05fcf569c64cf4a1176f5ce438f98b009227520f646f8abf174
-  languageName: node
-  linkType: hard
-
 "module-details-from-path@npm:^1.0.3, module-details-from-path@npm:^1.0.4":
   version: 1.0.4
   resolution: "module-details-from-path@npm:1.0.4"