FileHandlerTests.js 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. const sinon = require('sinon')
  2. const chai = require('chai')
  3. const { expect } = chai
  4. const modulePath = '../../../app/js/FileHandler.js'
  5. const SandboxedModule = require('sandboxed-module')
  6. const { ObjectId } = require('mongodb')
  7. chai.use(require('sinon-chai'))
  8. chai.use(require('chai-as-promised'))
  9. describe('FileHandler', function() {
  10. let PersistorManager,
  11. LocalFileWriter,
  12. FileConverter,
  13. KeyBuilder,
  14. ImageOptimiser,
  15. FileHandler,
  16. fs
  17. const settings = {
  18. s3: {
  19. buckets: {
  20. user_files: 'user_files'
  21. }
  22. }
  23. }
  24. const bucket = 'my_bucket'
  25. const key = `${ObjectId()}/${ObjectId()}`
  26. const convertedFolderKey = `${ObjectId()}/${ObjectId()}`
  27. const sourceStream = 'sourceStream'
  28. const convertedKey = 'convertedKey'
  29. const readStream = {
  30. stream: 'readStream',
  31. on: sinon.stub()
  32. }
  33. beforeEach(function() {
  34. PersistorManager = {
  35. promises: {
  36. getFileStream: sinon.stub().resolves(sourceStream),
  37. checkIfFileExists: sinon.stub().resolves(),
  38. deleteFile: sinon.stub().resolves(),
  39. deleteDirectory: sinon.stub().resolves(),
  40. sendStream: sinon.stub().resolves(),
  41. insertFile: sinon.stub().resolves(),
  42. sendFile: sinon.stub().resolves(),
  43. directorySize: sinon.stub().resolves()
  44. }
  45. }
  46. LocalFileWriter = {
  47. // the callback style is used for detached cleanup calls
  48. deleteFile: sinon.stub().yields(),
  49. promises: {
  50. writeStream: sinon.stub().resolves(),
  51. deleteFile: sinon.stub().resolves()
  52. }
  53. }
  54. FileConverter = {
  55. promises: {
  56. convert: sinon.stub().resolves(),
  57. thumbnail: sinon.stub().resolves(),
  58. preview: sinon.stub().resolves()
  59. }
  60. }
  61. KeyBuilder = {
  62. addCachingToKey: sinon.stub().returns(convertedKey),
  63. getConvertedFolderKey: sinon.stub().returns(convertedFolderKey)
  64. }
  65. ImageOptimiser = {
  66. promises: {
  67. compressPng: sinon.stub().resolves()
  68. }
  69. }
  70. fs = {
  71. createReadStream: sinon.stub().returns(readStream)
  72. }
  73. FileHandler = SandboxedModule.require(modulePath, {
  74. requires: {
  75. 'settings-sharelatex': settings,
  76. './PersistorManager': PersistorManager,
  77. './LocalFileWriter': LocalFileWriter,
  78. './FileConverter': FileConverter,
  79. './KeyBuilder': KeyBuilder,
  80. './ImageOptimiser': ImageOptimiser,
  81. fs: fs
  82. },
  83. globals: { console }
  84. })
  85. })
  86. describe('insertFile', function() {
  87. const stream = 'stream'
  88. it('should send file to the filestore', function(done) {
  89. FileHandler.insertFile(bucket, key, stream, err => {
  90. expect(err).not.to.exist
  91. expect(PersistorManager.promises.sendStream).to.have.been.calledWith(
  92. bucket,
  93. key,
  94. stream
  95. )
  96. done()
  97. })
  98. })
  99. it('should delete the convertedKey folder', function(done) {
  100. FileHandler.insertFile(bucket, key, stream, err => {
  101. expect(err).not.to.exist
  102. expect(
  103. PersistorManager.promises.deleteDirectory
  104. ).to.have.been.calledWith(bucket, convertedFolderKey)
  105. done()
  106. })
  107. })
  108. it('should throw an error when the key is in the wrong format', function(done) {
  109. KeyBuilder.getConvertedFolderKey.returns('wombat')
  110. FileHandler.insertFile(bucket, key, stream, err => {
  111. expect(err).to.exist
  112. done()
  113. })
  114. })
  115. })
  116. describe('deleteFile', function() {
  117. it('should tell the filestore manager to delete the file', function(done) {
  118. FileHandler.deleteFile(bucket, key, err => {
  119. expect(err).not.to.exist
  120. expect(PersistorManager.promises.deleteFile).to.have.been.calledWith(
  121. bucket,
  122. key
  123. )
  124. done()
  125. })
  126. })
  127. it('should tell the filestore manager to delete the cached folder', function(done) {
  128. FileHandler.deleteFile(bucket, key, err => {
  129. expect(err).not.to.exist
  130. expect(
  131. PersistorManager.promises.deleteDirectory
  132. ).to.have.been.calledWith(bucket, convertedFolderKey)
  133. done()
  134. })
  135. })
  136. it('should throw an error when the key is in the wrong format', function(done) {
  137. KeyBuilder.getConvertedFolderKey.returns('wombat')
  138. FileHandler.deleteFile(bucket, key, err => {
  139. expect(err).to.exist
  140. done()
  141. })
  142. })
  143. })
  144. describe('getFile', function() {
  145. it('should return the source stream no format or style are defined', function(done) {
  146. FileHandler.getFile(bucket, key, null, (err, stream) => {
  147. expect(err).not.to.exist
  148. expect(stream).to.equal(sourceStream)
  149. done()
  150. })
  151. })
  152. it('should pass options through to PersistorManager', function(done) {
  153. const options = { start: 0, end: 8 }
  154. FileHandler.getFile(bucket, key, options, err => {
  155. expect(err).not.to.exist
  156. expect(PersistorManager.promises.getFileStream).to.have.been.calledWith(
  157. bucket,
  158. key,
  159. options
  160. )
  161. done()
  162. })
  163. })
  164. describe('when a format is defined', function() {
  165. let result
  166. describe('when the file is not cached', function() {
  167. beforeEach(function(done) {
  168. FileHandler.getFile(bucket, key, { format: 'png' }, (err, stream) => {
  169. result = { err, stream }
  170. done()
  171. })
  172. })
  173. it('should convert the file', function() {
  174. expect(FileConverter.promises.convert).to.have.been.called
  175. })
  176. it('should compress the converted file', function() {
  177. expect(ImageOptimiser.promises.compressPng).to.have.been.called
  178. })
  179. it('should return the the converted stream', function() {
  180. expect(result.err).not.to.exist
  181. expect(result.stream).to.equal(readStream)
  182. expect(
  183. PersistorManager.promises.getFileStream
  184. ).to.have.been.calledWith(bucket, key)
  185. })
  186. })
  187. describe('when the file is cached', function() {
  188. beforeEach(function(done) {
  189. PersistorManager.promises.checkIfFileExists = sinon
  190. .stub()
  191. .resolves(true)
  192. FileHandler.getFile(bucket, key, { format: 'png' }, (err, stream) => {
  193. result = { err, stream }
  194. done()
  195. })
  196. })
  197. it('should not convert the file', function() {
  198. expect(FileConverter.promises.convert).not.to.have.been.called
  199. })
  200. it('should not compress the converted file again', function() {
  201. expect(ImageOptimiser.promises.compressPng).not.to.have.been.called
  202. })
  203. it('should return the cached stream', function() {
  204. expect(result.err).not.to.exist
  205. expect(result.stream).to.equal(sourceStream)
  206. expect(
  207. PersistorManager.promises.getFileStream
  208. ).to.have.been.calledWith(bucket, convertedKey)
  209. })
  210. })
  211. })
  212. describe('when a style is defined', function() {
  213. it('generates a thumbnail when requested', function(done) {
  214. FileHandler.getFile(bucket, key, { style: 'thumbnail' }, err => {
  215. expect(err).not.to.exist
  216. expect(FileConverter.promises.thumbnail).to.have.been.called
  217. expect(FileConverter.promises.preview).not.to.have.been.called
  218. done()
  219. })
  220. })
  221. it('generates a preview when requested', function(done) {
  222. FileHandler.getFile(bucket, key, { style: 'preview' }, err => {
  223. expect(err).not.to.exist
  224. expect(FileConverter.promises.thumbnail).not.to.have.been.called
  225. expect(FileConverter.promises.preview).to.have.been.called
  226. done()
  227. })
  228. })
  229. })
  230. })
  231. describe('getDirectorySize', function() {
  232. it('should call the filestore manager to get directory size', function(done) {
  233. FileHandler.getDirectorySize(bucket, key, err => {
  234. expect(err).not.to.exist
  235. expect(PersistorManager.promises.directorySize).to.have.been.calledWith(
  236. bucket,
  237. key
  238. )
  239. done()
  240. })
  241. })
  242. })
  243. })