FileStoreHandlerTests.coffee 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. assert = require("chai").assert
  2. sinon = require('sinon')
  3. chai = require('chai')
  4. should = chai.should()
  5. expect = chai.expect
  6. modulePath = "../../../../app/js/Features/FileStore/FileStoreHandler.js"
  7. SandboxedModule = require('sandboxed-module')
  8. describe "FileStoreHandler", ->
  9. beforeEach ->
  10. @fs =
  11. createReadStream : sinon.stub()
  12. lstat: sinon.stub().callsArgWith(1, null, {
  13. isFile:=> true
  14. isDirectory:-> return false
  15. })
  16. @writeStream =
  17. my:"writeStream"
  18. on: (type, cb)->
  19. if type == "response"
  20. cb({statusCode: 200})
  21. @readStream = {my:"readStream", on: sinon.stub()}
  22. @request = sinon.stub()
  23. @settings = apis:{filestore:{url:"http//filestore.sharelatex.test"}}
  24. @hashValue = "0123456789"
  25. @FileModel = class File
  26. constructor:(options)->
  27. {@name,@hash} = options
  28. @_id = "file_id_here"
  29. @rev = 0
  30. if options.linkedFileData?
  31. @linkedFileData = options.linkedFileData
  32. @handler = SandboxedModule.require modulePath, requires:
  33. "settings-sharelatex":@settings
  34. "request":@request
  35. "logger-sharelatex" : @logger = {log:sinon.stub(), err:sinon.stub()}
  36. "./FileHashManager" : @FileHashManager = { computeHash: sinon.stub().callsArgWith(1, null, @hashValue)}
  37. # FIXME: need to stub File object here
  38. "../../models/File" : File: @FileModel
  39. "fs" : @fs
  40. @file_args = {name: "upload-filename"}
  41. @file_id = "file_id_here"
  42. @project_id = "1312312312"
  43. @fsPath = "uploads/myfile.eps"
  44. @handler._buildUrl = sinon.stub().returns("http://filestore.stubbedBuilder.com")
  45. describe "uploadFileFromDisk", ->
  46. beforeEach ->
  47. @request.returns(@writeStream)
  48. it "should create read stream", (done)->
  49. @fs.createReadStream.returns
  50. pipe:->
  51. on: (type, cb)->
  52. if type == "open"
  53. cb()
  54. @handler.uploadFileFromDisk @project_id, @file_args, @fsPath, =>
  55. @fs.createReadStream.calledWith(@fsPath).should.equal true
  56. done()
  57. it "should pipe the read stream to request", (done)->
  58. @request.returns(@writeStream)
  59. @fs.createReadStream.returns
  60. on: (type, cb)->
  61. if type == "open"
  62. cb()
  63. pipe:(o)=>
  64. @writeStream.should.equal o
  65. done()
  66. @handler.uploadFileFromDisk @project_id, @file_args, @fsPath, =>
  67. it "should pass the correct options to request", (done)->
  68. @fs.createReadStream.returns
  69. pipe:->
  70. on: (type, cb)->
  71. if type == "open"
  72. cb()
  73. @handler.uploadFileFromDisk @project_id, @file_args, @fsPath, =>
  74. @request.args[0][0].method.should.equal "post"
  75. @request.args[0][0].uri.should.equal @handler._buildUrl()
  76. done()
  77. it "builds the correct url", (done)->
  78. @fs.createReadStream.returns
  79. pipe:->
  80. on: (type, cb)->
  81. if type == "open"
  82. cb()
  83. @handler.uploadFileFromDisk @project_id, @file_args, @fsPath, =>
  84. @handler._buildUrl.calledWith(@project_id, @file_id).should.equal true
  85. done()
  86. it 'should callback with the url and fileRef', (done) ->
  87. @fs.createReadStream.returns
  88. pipe:->
  89. on: (type, cb)->
  90. if type == "open"
  91. cb()
  92. @handler.uploadFileFromDisk @project_id, @file_args, @fsPath, (err, url, fileRef) =>
  93. expect(err).to.not.exist
  94. expect(url).to.equal(@handler._buildUrl())
  95. expect(fileRef._id).to.equal(@file_id)
  96. expect(fileRef.hash).to.equal(@hashValue)
  97. done()
  98. describe "symlink", ->
  99. beforeEach ->
  100. @fs.lstat = sinon.stub().callsArgWith(1, null, {
  101. isFile:=> false
  102. isDirectory:-> return false
  103. })
  104. it "should not read file if it is symlink", (done)->
  105. @handler.uploadFileFromDisk @project_id, @file_args, @fsPath, =>
  106. @fs.createReadStream.called.should.equal false
  107. done()
  108. describe "symlink", ->
  109. it "should not read file stat returns nothing", (done)->
  110. @fs.lstat = sinon.stub().callsArgWith(1, null, null)
  111. @handler.uploadFileFromDisk @project_id, @file_args, @fsPath, =>
  112. @fs.createReadStream.called.should.equal false
  113. done()
  114. describe "when upload fails", ->
  115. beforeEach ->
  116. @writeStream.on = (type, cb) ->
  117. if type == "response"
  118. cb({statusCode: 500})
  119. it 'should callback with an error', (done) ->
  120. @fs.createReadStream.callCount = 0
  121. @fs.createReadStream.returns
  122. pipe:->
  123. on: (type, cb)->
  124. if type == "open"
  125. cb()
  126. @handler.uploadFileFromDisk @project_id, @file_args, @fsPath, (err) =>
  127. expect(err).to.exist
  128. expect(err).to.be.instanceof Error
  129. expect(@fs.createReadStream.callCount).to.equal @handler.RETRY_ATTEMPTS
  130. done()
  131. describe "deleteFile", ->
  132. it "should send a delete request to filestore api", (done)->
  133. @request.callsArgWith(1, null)
  134. @handler.deleteFile @project_id, @file_id, (err)=>
  135. assert.equal err, undefined
  136. @request.args[0][0].method.should.equal "delete"
  137. @request.args[0][0].uri.should.equal @handler._buildUrl()
  138. done()
  139. it "should return the error if there is one", (done)->
  140. error = "my error"
  141. @request.callsArgWith(1, error)
  142. @handler.deleteFile @project_id, @file_id, (err)=>
  143. assert.equal err, error
  144. done()
  145. it "builds the correct url", (done)->
  146. @request.callsArgWith(1, null)
  147. @handler.deleteFile @project_id, @file_id, (err)=>
  148. @handler._buildUrl.calledWith(@project_id, @file_id).should.equal true
  149. done()
  150. describe "getFileStream", ->
  151. beforeEach ->
  152. @query = {}
  153. @request.returns(@readStream)
  154. it "should get the stream with the correct params", (done)->
  155. @handler.getFileStream @project_id, @file_id, @query, (err, stream)=>
  156. @request.args[0][0].method.should.equal "get"
  157. @request.args[0][0].uri.should.equal @handler._buildUrl()
  158. done()
  159. it "should get stream from request", (done)->
  160. @handler.getFileStream @project_id, @file_id, @query, (err, stream)=>
  161. stream.should.equal @readStream
  162. done()
  163. it "builds the correct url", (done)->
  164. @handler.getFileStream @project_id, @file_id, @query, (err, stream)=>
  165. @handler._buildUrl.calledWith(@project_id, @file_id).should.equal true
  166. done()
  167. it "should add an error handler", (done) ->
  168. @handler.getFileStream @project_id, @file_id, @query, (err, stream)=>
  169. stream.on.calledWith("error").should.equal true
  170. done()
  171. describe 'when range is specified in query', ->
  172. beforeEach ->
  173. @query = {'range': '0-10'}
  174. it 'should add a range header', (done) ->
  175. @handler.getFileStream @project_id, @file_id, @query, (err, stream)=>
  176. @request.callCount.should.equal 1
  177. headers = @request.firstCall.args[0].headers
  178. expect(headers).to.have.keys('range')
  179. expect(headers['range']).to.equal 'bytes=0-10'
  180. done()
  181. describe 'when range is invalid', ->
  182. ['0-', '-100', 'one-two', 'nonsense'].forEach (r) =>
  183. beforeEach ->
  184. @query = {'range': "#{r}"}
  185. it "should not add a range header for '#{r}'", (done) ->
  186. @handler.getFileStream @project_id, @file_id, @query, (err, stream)=>
  187. @request.callCount.should.equal 1
  188. headers = @request.firstCall.args[0].headers
  189. expect(headers).to.not.have.keys('range')
  190. done()
  191. describe "copyFile", ->
  192. beforeEach ->
  193. @newProject_id = "new project"
  194. @newFile_id = "new file id"
  195. it "should post json", (done)->
  196. @request.callsArgWith(1, null, {statusCode: 200})
  197. @handler.copyFile @project_id, @file_id, @newProject_id, @newFile_id, =>
  198. @request.args[0][0].method.should.equal "put"
  199. @request.args[0][0].uri.should.equal @handler._buildUrl()
  200. @request.args[0][0].json.source.project_id.should.equal @project_id
  201. @request.args[0][0].json.source.file_id.should.equal @file_id
  202. done()
  203. it "builds the correct url", (done)->
  204. @request.callsArgWith(1, null, {statusCode: 200})
  205. @handler.copyFile @project_id, @file_id, @newProject_id, @newFile_id, =>
  206. @handler._buildUrl.calledWith(@newProject_id, @newFile_id).should.equal true
  207. done()
  208. it "returns the url", (done)->
  209. @request.callsArgWith(1, null, {statusCode: 200})
  210. @handler.copyFile @project_id, @file_id, @newProject_id, @newFile_id, (err, url) =>
  211. url.should.equal "http://filestore.stubbedBuilder.com"
  212. done()
  213. it "should return the err", (done)->
  214. error = "errrror"
  215. @request.callsArgWith(1, error)
  216. @handler.copyFile @project_id, @file_id, @newProject_id, @newFile_id, (err)=>
  217. err.should.equal error
  218. done()
  219. it "should return an error for a non-success statusCode", (done)->
  220. @request.callsArgWith(1, null, {statusCode: 500})
  221. @handler.copyFile @project_id, @file_id, @newProject_id, @newFile_id, (err)=>
  222. err.should.be.an('error')
  223. err.message.should.equal 'non-ok response from filestore for copyFile: 500'
  224. done()