overleaf/services/filestore/test/unit/js/FSPersistorManagerTests.js

308 lines
9.7 KiB
JavaScript
Raw Normal View History

const sinon = require('sinon')
const chai = require('chai')
const { expect } = chai
const SandboxedModule = require('sandboxed-module')
2020-01-02 06:29:28 -05:00
const Errors = require('../../../app/js/Errors')
chai.use(require('sinon-chai'))
chai.use(require('chai-as-promised'))
const modulePath = '../../../app/js/FSPersistorManager.js'
describe('FSPersistorManagerTests', function() {
2020-01-02 06:29:28 -05:00
const stat = { size: 4 }
const fd = 1234
const readStream = 'readStream'
const writeStream = 'writeStream'
const remoteStream = 'remoteStream'
const tempFile = '/tmp/potato.txt'
const location = '/foo'
const error = new Error('guru meditation error')
const files = ['wombat.txt', 'potato.tex']
let fs, rimraf, stream, LocalFileWriter, FSPersistorManager
beforeEach(function() {
2020-01-02 06:29:28 -05:00
fs = {
createReadStream: sinon.stub().returns(readStream),
createWriteStream: sinon.stub().returns(writeStream),
unlink: sinon.stub().yields(),
open: sinon.stub().yields(null, fd),
readdir: sinon.stub().yields(null, files),
stat: sinon.stub().yields(null, stat)
}
2020-01-02 06:29:28 -05:00
rimraf = sinon.stub().yields()
stream = { pipeline: sinon.stub().yields() }
LocalFileWriter = {
promises: {
writeStream: sinon.stub().resolves(tempFile),
deleteFile: sinon.stub().resolves()
}
}
2020-01-02 06:29:28 -05:00
FSPersistorManager = SandboxedModule.require(modulePath, {
requires: {
'./LocalFileWriter': LocalFileWriter,
fs: fs,
'logger-sharelatex': {
log() {},
err() {}
},
rimraf: rimraf,
stream: stream,
'./Errors': Errors
},
2020-01-02 06:29:28 -05:00
globals: { console }
})
})
describe('sendFile', function() {
2020-01-02 06:29:28 -05:00
it('should copy the file', async function() {
await FSPersistorManager.promises.sendFile(location, files[0], files[1])
expect(fs.createReadStream).to.have.been.calledWith(files[1])
expect(fs.createWriteStream).to.have.been.calledWith(
`${location}/${files[0]}`
)
2020-01-02 06:29:28 -05:00
expect(stream.pipeline).to.have.been.calledWith(readStream, writeStream)
})
2020-01-02 06:29:28 -05:00
it('should return an error if the file cannot be stored', async function() {
stream.pipeline.yields(error)
await expect(
FSPersistorManager.promises.sendFile(location, files[0], files[1])
).to.eventually.be.rejectedWith(error)
})
})
describe('sendStream', function() {
2020-01-02 06:29:28 -05:00
it('should send the stream to LocalFileWriter', async function() {
await FSPersistorManager.promises.sendStream(
location,
files[0],
remoteStream
)
expect(LocalFileWriter.promises.writeStream).to.have.been.calledWith(
remoteStream
)
})
2020-01-02 06:29:28 -05:00
it('should delete the temporary file', async function() {
await FSPersistorManager.promises.sendStream(
location,
files[0],
remoteStream
)
expect(LocalFileWriter.promises.deleteFile).to.have.been.calledWith(
tempFile
)
})
2020-01-02 06:29:28 -05:00
it('should return the error from LocalFileWriter', async function() {
LocalFileWriter.promises.writeStream.rejects(error)
await expect(
FSPersistorManager.promises.sendStream(location, files[0], remoteStream)
).to.eventually.be.rejectedWith(error)
})
2020-01-02 06:29:28 -05:00
it('should send the temporary file to the filestore', async function() {
await FSPersistorManager.promises.sendStream(
location,
files[0],
remoteStream
)
2020-01-02 06:29:28 -05:00
expect(fs.createReadStream).to.have.been.calledWith(tempFile)
})
})
describe('getFileStream', function() {
2020-01-02 06:29:28 -05:00
const filename = 'wombat/potato'
const filteredFilename = 'wombat_potato'
2020-01-02 06:29:28 -05:00
it('should use correct file location', async function() {
await FSPersistorManager.promises.getFileStream(location, filename, {})
expect(fs.open).to.have.been.calledWith(`${location}/${filteredFilename}`)
})
2020-01-02 06:29:28 -05:00
it('should pass the options to createReadStream', async function() {
await FSPersistorManager.promises.getFileStream(location, filename, {
start: 0,
end: 8
})
2020-01-02 06:29:28 -05:00
expect(fs.createReadStream).to.have.been.calledWith(null, {
start: 0,
end: 8,
fd
})
})
2020-01-02 06:29:28 -05:00
it('should give a NotFoundError if the file does not exist', async function() {
const err = new Error()
err.code = 'ENOENT'
fs.open.yields(err)
2020-01-02 06:29:28 -05:00
await expect(
FSPersistorManager.promises.getFileStream(location, filename, {})
)
.to.eventually.be.rejectedWith('file not found')
.and.be.an.instanceOf(Errors.NotFoundError)
})
it('should wrap any other error', async function() {
fs.open.yields(error)
await expect(
FSPersistorManager.promises.getFileStream(location, filename, {})
)
.to.eventually.be.rejectedWith('failed to open file for streaming')
.and.be.an.instanceOf(Errors.ReadError)
.and.have.property('cause', error)
})
})
describe('getFileSize', function() {
2020-01-02 06:29:28 -05:00
const filename = 'wombat/potato'
const badFilename = 'neenaw.tex'
const filteredFilename = 'wombat_potato'
const size = 65536
const noentError = new Error('not found')
noentError.code = 'ENOENT'
beforeEach(function() {
fs.stat
.yields(error)
.withArgs(`${location}/${filteredFilename}`)
.yields(null, { size })
.withArgs(`${location}/${badFilename}`)
.yields(noentError)
})
2020-01-02 06:29:28 -05:00
it('should return the file size', async function() {
expect(
await FSPersistorManager.promises.getFileSize(location, filename)
).to.equal(size)
})
2020-01-02 06:29:28 -05:00
it('should throw a NotFoundError if the file does not exist', async function() {
await expect(
FSPersistorManager.promises.getFileSize(location, badFilename)
).to.eventually.be.rejected.and.be.an.instanceOf(Errors.NotFoundError)
})
2020-01-02 06:29:28 -05:00
it('should wrap any other error', async function() {
await expect(FSPersistorManager.promises.getFileSize(location, 'raccoon'))
.to.eventually.be.rejected.and.be.an.instanceOf(Errors.ReadError)
.and.have.property('cause', error)
})
2020-01-02 06:29:28 -05:00
})
2020-01-02 06:29:28 -05:00
describe('copyFile', function() {
it('Should open the source for reading', async function() {
await FSPersistorManager.promises.copyFile(location, files[0], files[1])
expect(fs.createReadStream).to.have.been.calledWith(
`${location}/${files[0]}`
)
})
2020-01-02 06:29:28 -05:00
it('Should open the target for writing', async function() {
await FSPersistorManager.promises.copyFile(location, files[0], files[1])
expect(fs.createWriteStream).to.have.been.calledWith(
`${location}/${files[1]}`
)
})
2020-01-02 06:29:28 -05:00
it('Should pipe the source to the target', async function() {
await FSPersistorManager.promises.copyFile(location, files[0], files[1])
expect(stream.pipeline).to.have.been.calledWith(readStream, writeStream)
})
})
describe('deleteFile', function() {
2020-01-02 06:29:28 -05:00
it('Should call unlink with correct options', async function() {
await FSPersistorManager.promises.deleteFile(location, files[0])
expect(fs.unlink).to.have.been.calledWith(`${location}/${files[0]}`)
})
2020-01-02 06:29:28 -05:00
it('Should propagate the error', async function() {
fs.unlink.yields(error)
await expect(
FSPersistorManager.promises.deleteFile(location, files[0])
).to.eventually.be.rejectedWith(error)
})
})
describe('deleteDirectory', function() {
2020-01-02 06:29:28 -05:00
it('Should call rmdir(rimraf) with correct options', async function() {
await FSPersistorManager.promises.deleteDirectory(location, files[0])
expect(rimraf).to.have.been.calledWith(`${location}/${files[0]}`)
})
2020-01-02 06:29:28 -05:00
it('Should propagate the error', async function() {
rimraf.yields(error)
await expect(
FSPersistorManager.promises.deleteDirectory(location, files[0])
).to.eventually.be.rejectedWith(error)
})
})
describe('checkIfFileExists', function() {
2020-01-02 06:29:28 -05:00
const filename = 'wombat'
const badFilename = 'potato'
const noentError = new Error('not found')
noentError.code = 'ENOENT'
beforeEach(function() {
2020-01-02 06:29:28 -05:00
fs.stat
.yields(error)
.withArgs(`${location}/${filename}`)
.yields(null, {})
.withArgs(`${location}/${badFilename}`)
.yields(noentError)
})
2020-01-02 06:29:28 -05:00
it('Should call stat with correct options', async function() {
await FSPersistorManager.promises.checkIfFileExists(location, filename)
expect(fs.stat).to.have.been.calledWith(`${location}/${filename}`)
})
2020-01-02 06:29:28 -05:00
it('Should return true for existing files', async function() {
expect(
await FSPersistorManager.promises.checkIfFileExists(location, filename)
).to.equal(true)
})
2020-01-02 06:29:28 -05:00
it('Should return false for non-existing files', async function() {
expect(
await FSPersistorManager.promises.checkIfFileExists(
location,
badFilename
)
).to.equal(false)
})
2020-01-02 06:29:28 -05:00
it('should wrap the error if there is a problem', async function() {
await expect(
FSPersistorManager.promises.checkIfFileExists(location, 'llama')
)
2020-01-02 06:29:28 -05:00
.to.eventually.be.rejected.and.be.an.instanceOf(Errors.ReadError)
.and.have.property('cause', error)
})
})
2020-01-02 06:29:28 -05:00
describe('directorySize', function() {
it('should wrap the error', async function() {
fs.readdir.yields(error)
await expect(
FSPersistorManager.promises.directorySize(location, 'wombat')
)
2020-01-02 06:29:28 -05:00
.to.eventually.be.rejected.and.be.an.instanceOf(Errors.ReadError)
.and.include({ cause: error })
.and.have.property('info')
.which.includes({ location, name: 'wombat' })
})
2020-01-02 06:29:28 -05:00
it('should sum directory files size', async function() {
expect(
await FSPersistorManager.promises.directorySize(location, 'wombat')
).to.equal(stat.size * files.length)
})
})
})