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

GitOrigin-RevId: ff8df32d85b2ecd2837c9eee6d6d2b3b95285239
This commit is contained in:
Anna Claire Fields
2026-06-05 08:06:54 +00:00
committed by Copybot
parent 3fcd133198
commit 97247b8ea5
10 changed files with 242 additions and 137 deletions
-1
View File
@@ -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",
@@ -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(() => {})
@@ -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)
}
})