ArchiveManagerTests.coffee 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. sinon = require('sinon')
  2. expect = require("chai").expect
  3. chai = require('chai')
  4. should = chai.should()
  5. modulePath = "../../../../app/js/Features/Uploads/ArchiveManager.js"
  6. SandboxedModule = require('sandboxed-module')
  7. events = require "events"
  8. describe "ArchiveManager", ->
  9. beforeEach ->
  10. @logger =
  11. error: sinon.stub()
  12. warn: sinon.stub()
  13. err:->
  14. log: sinon.stub()
  15. @metrics =
  16. Timer: class Timer
  17. done: sinon.stub()
  18. @zipfile = new events.EventEmitter
  19. @zipfile.readEntry = sinon.stub()
  20. @zipfile.close = sinon.stub()
  21. @ArchiveManager = SandboxedModule.require modulePath, requires:
  22. "yauzl": @yauzl = {open: sinon.stub().callsArgWith(2, null, @zipfile)}
  23. "logger-sharelatex": @logger
  24. "metrics-sharelatex": @metrics
  25. "fs": @fs = {}
  26. "fs-extra": @fse = {}
  27. describe "extractZipArchive", ->
  28. beforeEach ->
  29. @source = "/path/to/zip/source.zip"
  30. @destination = "/path/to/zip/destination"
  31. @callback = sinon.stub()
  32. @ArchiveManager._isZipTooLarge = sinon.stub().callsArgWith(1, null, false)
  33. describe "successfully", ->
  34. beforeEach (done) ->
  35. @ArchiveManager.extractZipArchive @source, @destination, done
  36. @zipfile.emit "end"
  37. it "should run yauzl", ->
  38. @yauzl.open.calledWith(@source).should.equal true
  39. it "should time the unzip", ->
  40. @metrics.Timer::done.called.should.equal true
  41. it "should log the unzip", ->
  42. @logger.log.calledWith(sinon.match.any, "unzipping file").should.equal true
  43. describe "with an error in the zip file header", ->
  44. beforeEach (done) ->
  45. @yauzl.open = sinon.stub().callsArgWith(2, new Error("Something went wrong"))
  46. @ArchiveManager.extractZipArchive @source, @destination, (error) =>
  47. @callback(error)
  48. done()
  49. it "should return the callback with an error", ->
  50. @callback.calledWithExactly(new Error("Something went wrong")).should.equal true
  51. it "should log out the error", ->
  52. @logger.error.called.should.equal true
  53. describe "with a zip that is too large", ->
  54. beforeEach (done) ->
  55. @ArchiveManager._isZipTooLarge = sinon.stub().callsArgWith(1, null, true)
  56. @ArchiveManager.extractZipArchive @source, @destination, (error) =>
  57. @callback(error)
  58. done()
  59. it "should return the callback with an error", ->
  60. @callback.calledWithExactly(new Error("zip_too_large")).should.equal true
  61. it "should not call yauzl.open", ->
  62. @yauzl.open.called.should.equal false
  63. describe "with an error in the extracted files", ->
  64. beforeEach (done) ->
  65. @ArchiveManager.extractZipArchive @source, @destination, (error) =>
  66. @callback(error)
  67. done()
  68. @zipfile.emit "error", new Error("Something went wrong")
  69. it "should return the callback with an error", ->
  70. @callback.calledWithExactly(new Error("Something went wrong")).should.equal true
  71. it "should log out the error", ->
  72. @logger.error.called.should.equal true
  73. describe "with a relative extracted file path", ->
  74. beforeEach (done) ->
  75. @zipfile.openReadStream = sinon.stub()
  76. @ArchiveManager.extractZipArchive @source, @destination, (error) =>
  77. @callback(error)
  78. done()
  79. @zipfile.emit "entry", {fileName: "../testfile.txt"}
  80. @zipfile.emit "end"
  81. it "should not write try to read the file entry", ->
  82. @zipfile.openReadStream.called.should.equal false
  83. it "should log out a warning", ->
  84. @logger.warn.called.should.equal true
  85. describe "with an unnormalized extracted file path", ->
  86. beforeEach (done) ->
  87. @zipfile.openReadStream = sinon.stub()
  88. @ArchiveManager.extractZipArchive @source, @destination, (error) =>
  89. @callback(error)
  90. done()
  91. @zipfile.emit "entry", {fileName: "foo/./testfile.txt"}
  92. @zipfile.emit "end"
  93. it "should not write try to read the file entry", ->
  94. @zipfile.openReadStream.called.should.equal false
  95. it "should log out a warning", ->
  96. @logger.warn.called.should.equal true
  97. describe "with a directory entry", ->
  98. beforeEach (done) ->
  99. @zipfile.openReadStream = sinon.stub()
  100. @ArchiveManager.extractZipArchive @source, @destination, (error) =>
  101. @callback(error)
  102. done()
  103. @zipfile.emit "entry", {fileName: "testdir/"}
  104. @zipfile.emit "end"
  105. it "should not write try to read the entry", ->
  106. @zipfile.openReadStream.called.should.equal false
  107. it "should not log out a warning", ->
  108. @logger.warn.called.should.equal false
  109. describe "with an error opening the file read stream", ->
  110. beforeEach (done) ->
  111. @zipfile.openReadStream = sinon.stub().callsArgWith(1, new Error("Something went wrong"))
  112. @writeStream = new events.EventEmitter
  113. @ArchiveManager.extractZipArchive @source, @destination, (error) =>
  114. @callback(error)
  115. done()
  116. @zipfile.emit "entry", {fileName: "testfile.txt"}
  117. @zipfile.emit "end"
  118. it "should return the callback with an error", ->
  119. @callback.calledWithExactly(new Error("Something went wrong")).should.equal true
  120. it "should log out the error", ->
  121. @logger.error.called.should.equal true
  122. it "should close the zipfile", ->
  123. @zipfile.close.called.should.equal true
  124. describe "with an error in the file read stream", ->
  125. beforeEach (done) ->
  126. @readStream = new events.EventEmitter
  127. @readStream.pipe = sinon.stub()
  128. @zipfile.openReadStream = sinon.stub().callsArgWith(1, null, @readStream)
  129. @writeStream = new events.EventEmitter
  130. @fs.createWriteStream = sinon.stub().returns @writeStream
  131. @fse.ensureDir = sinon.stub().callsArg(1)
  132. @ArchiveManager.extractZipArchive @source, @destination, (error) =>
  133. @callback(error)
  134. done()
  135. @zipfile.emit "entry", {fileName: "testfile.txt"}
  136. @readStream.emit "error", new Error("Something went wrong")
  137. @zipfile.emit "end"
  138. it "should return the callback with an error", ->
  139. @callback.calledWithExactly(new Error("Something went wrong")).should.equal true
  140. it "should log out the error", ->
  141. @logger.error.called.should.equal true
  142. it "should close the zipfile", ->
  143. @zipfile.close.called.should.equal true
  144. describe "with an error in the file write stream", ->
  145. beforeEach (done) ->
  146. @readStream = new events.EventEmitter
  147. @readStream.pipe = sinon.stub()
  148. @readStream.unpipe = sinon.stub()
  149. @readStream.destroy = sinon.stub()
  150. @zipfile.openReadStream = sinon.stub().callsArgWith(1, null, @readStream)
  151. @writeStream = new events.EventEmitter
  152. @fs.createWriteStream = sinon.stub().returns @writeStream
  153. @fse.ensureDir = sinon.stub().callsArg(1)
  154. @ArchiveManager.extractZipArchive @source, @destination, (error) =>
  155. @callback(error)
  156. done()
  157. @zipfile.emit "entry", {fileName: "testfile.txt"}
  158. @writeStream.emit "error", new Error("Something went wrong")
  159. @zipfile.emit "end"
  160. it "should return the callback with an error", ->
  161. @callback.calledWithExactly(new Error("Something went wrong")).should.equal true
  162. it "should log out the error", ->
  163. @logger.error.called.should.equal true
  164. it "should unpipe from the readstream", ->
  165. @readStream.unpipe.called.should.equal true
  166. it "should destroy the readstream", ->
  167. @readStream.destroy.called.should.equal true
  168. it "should close the zipfile", ->
  169. @zipfile.close.called.should.equal true
  170. describe "_isZipTooLarge", ->
  171. it "should return false with small output", (done)->
  172. @ArchiveManager._isZipTooLarge @source, (error, isTooLarge) =>
  173. isTooLarge.should.equal false
  174. done()
  175. @zipfile.emit "entry", {uncompressedSize: 109042}
  176. @zipfile.emit "end"
  177. it "should return true with large bytes", (done)->
  178. @ArchiveManager._isZipTooLarge @source, (error, isTooLarge) =>
  179. isTooLarge.should.equal true
  180. done()
  181. @zipfile.emit "entry", {uncompressedSize: 1090000000000000042}
  182. @zipfile.emit "end"
  183. it "should return error on no data", (done)->
  184. @ArchiveManager._isZipTooLarge @source, (error, isTooLarge) =>
  185. expect(error).to.exist
  186. done()
  187. @zipfile.emit "entry", {}
  188. @zipfile.emit "end"
  189. it "should return error if it didn't get a number", (done)->
  190. @ArchiveManager._isZipTooLarge @source, (error, isTooLarge) =>
  191. expect(error).to.exist
  192. done()
  193. @zipfile.emit "entry", {uncompressedSize:"random-error"}
  194. @zipfile.emit "end"
  195. it "should return error if there is no data", (done)->
  196. @ArchiveManager._isZipTooLarge @source, (error, isTooLarge) =>
  197. expect(error).to.exist
  198. done()
  199. @zipfile.emit "end"
  200. describe "findTopLevelDirectory", ->
  201. beforeEach ->
  202. @fs.readdir = sinon.stub()
  203. @fs.stat = sinon.stub()
  204. @directory = "test/directory"
  205. describe "with multiple files", ->
  206. beforeEach ->
  207. @fs.readdir.callsArgWith(1, null, ["multiple", "files"])
  208. @ArchiveManager.findTopLevelDirectory(@directory, @callback)
  209. it "should find the files in the directory", ->
  210. @fs.readdir
  211. .calledWith(@directory)
  212. .should.equal true
  213. it "should return the original directory", ->
  214. @callback
  215. .calledWith(null, @directory)
  216. .should.equal true
  217. describe "with a single file (not folder)", ->
  218. beforeEach ->
  219. @fs.readdir.callsArgWith(1, null, ["foo.tex"])
  220. @fs.stat.callsArgWith(1, null, { isDirectory: () -> false })
  221. @ArchiveManager.findTopLevelDirectory(@directory, @callback)
  222. it "should check if the file is a directory", ->
  223. @fs.stat
  224. .calledWith(@directory + "/foo.tex")
  225. .should.equal true
  226. it "should return the original directory", ->
  227. @callback
  228. .calledWith(null, @directory)
  229. .should.equal true
  230. describe "with a single top-level folder", ->
  231. beforeEach ->
  232. @fs.readdir.callsArgWith(1, null, ["folder"])
  233. @fs.stat.callsArgWith(1, null, { isDirectory: () -> true })
  234. @ArchiveManager.findTopLevelDirectory(@directory, @callback)
  235. it "should check if the file is a directory", ->
  236. @fs.stat
  237. .calledWith(@directory + "/folder")
  238. .should.equal true
  239. it "should return the child directory", ->
  240. @callback
  241. .calledWith(null, @directory + "/folder")
  242. .should.equal true