LocalFileWriter.test.js 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. import sinon from 'sinon'
  2. import { beforeEach, describe, expect, it, vi } from 'vitest'
  3. import { Errors } from '@overleaf/object-persistor'
  4. const modulePath = '../../../app/js/LocalFileWriter.js'
  5. describe('LocalFileWriter', function () {
  6. const writeStream = 'writeStream'
  7. const readStream = 'readStream'
  8. const settings = { path: { uploadFolder: '/uploads' } }
  9. const fsPath = '/uploads/wombat'
  10. const filename = 'wombat'
  11. let stream, fs, LocalFileWriter
  12. beforeEach(async function () {
  13. fs = {
  14. createWriteStream: sinon.stub().returns(writeStream),
  15. promises: {
  16. unlink: sinon.stub().resolves(),
  17. },
  18. }
  19. stream = {
  20. pipeline: sinon.stub().yields(),
  21. }
  22. const ObjectPersistor = { Errors }
  23. vi.doMock('fs', () => ({
  24. default: fs,
  25. }))
  26. vi.doMock('stream', () => ({
  27. default: stream,
  28. }))
  29. vi.doMock('@overleaf/settings', () => ({
  30. default: settings,
  31. }))
  32. vi.doMock('@overleaf/metrics', () => ({
  33. default: {
  34. inc: sinon.stub(),
  35. Timer: sinon.stub().returns({ done: sinon.stub() }),
  36. },
  37. }))
  38. vi.doMock('@overleaf/object-persistor', () => ObjectPersistor)
  39. LocalFileWriter = (await import(modulePath)).default
  40. })
  41. describe('writeStream', function () {
  42. it('writes the stream to the upload folder', async function () {
  43. await new Promise(resolve => {
  44. LocalFileWriter.writeStream(readStream, filename, (err, path) => {
  45. expect(err).not.to.exist
  46. expect(fs.createWriteStream).to.have.been.calledWith(fsPath)
  47. expect(stream.pipeline).to.have.been.calledWith(
  48. readStream,
  49. writeStream
  50. )
  51. expect(path).to.equal(fsPath)
  52. resolve()
  53. })
  54. })
  55. })
  56. describe('when there is an error', function () {
  57. const error = new Error('not enough ketchup')
  58. beforeEach(function () {
  59. stream.pipeline.yields(error)
  60. })
  61. it('should wrap the error', async function () {
  62. await expect(
  63. LocalFileWriter.promises.writeStream(readStream, filename)
  64. ).to.be.rejected.and.eventually.have.property('cause', error)
  65. })
  66. it('should delete the temporary file', async function () {
  67. await expect(LocalFileWriter.promises.writeStream(readStream, filename))
  68. .to.be.rejected
  69. expect(fs.promises.unlink).to.have.been.calledWith(fsPath)
  70. })
  71. })
  72. })
  73. describe('deleteFile', function () {
  74. it('should unlink the file', async function () {
  75. await LocalFileWriter.promises.deleteFile(fsPath)
  76. expect(fs.promises.unlink).to.have.been.calledWith(fsPath)
  77. })
  78. it('should not call unlink with an empty path', async function () {
  79. await LocalFileWriter.promises.deleteFile('')
  80. expect(fs.promises.unlink).not.to.have.been.called
  81. })
  82. it('should not throw a error if the file does not exist', async function () {
  83. const error = new Error('file not found')
  84. error.code = 'ENOENT'
  85. fs.promises.unlink = sinon.stub().rejects(error)
  86. await LocalFileWriter.promises.deleteFile(fsPath)
  87. })
  88. it('should wrap the error', async function () {
  89. const error = new Error('failed to reticulate splines')
  90. fs.promises.unlink = sinon.stub().rejects(error)
  91. await expect(
  92. LocalFileWriter.promises.deleteFile(fsPath)
  93. ).to.be.rejectedWith(Errors.WriteError)
  94. })
  95. })
  96. })