FileStoreHandlerTests.js 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. const { expect } = require('chai')
  2. const sinon = require('sinon')
  3. const SandboxedModule = require('sandboxed-module')
  4. const MODULE_PATH = '../../../../app/src/Features/FileStore/FileStoreHandler.js'
  5. describe('FileStoreHandler', function () {
  6. beforeEach(function () {
  7. this.fileSize = 999
  8. this.fs = {
  9. createReadStream: sinon.stub(),
  10. lstat: sinon.stub().callsArgWith(1, null, {
  11. isFile() {
  12. return true
  13. },
  14. isDirectory() {
  15. return false
  16. },
  17. size: this.fileSize,
  18. }),
  19. }
  20. this.writeStream = {
  21. my: 'writeStream',
  22. on(type, fn) {
  23. if (type === 'response') {
  24. fn({ statusCode: 200 })
  25. }
  26. },
  27. }
  28. this.readStream = { my: 'readStream', on: sinon.stub() }
  29. this.request = sinon.stub()
  30. this.request.head = sinon.stub()
  31. this.filestoreUrl = 'http://filestore.overleaf.test'
  32. this.settings = {
  33. apis: { filestore: { url: this.filestoreUrl } },
  34. }
  35. this.hashValue = '0123456789'
  36. this.fileArgs = { name: 'upload-filename' }
  37. this.fileId = 'file_id_here'
  38. this.projectId = '1312312312'
  39. this.historyId = 123
  40. this.hashValue = '2aae6c35c94fcfb415dbe95f408b9ce91ee846ed'
  41. this.fsPath = 'uploads/myfile.eps'
  42. this.getFileUrl = (projectId, fileId) =>
  43. `${this.filestoreUrl}/project/${projectId}/file/${fileId}`
  44. this.getProjectUrl = projectId =>
  45. `${this.filestoreUrl}/project/${projectId}`
  46. this.FileModel = class File {
  47. constructor(options) {
  48. ;({ name: this.name, hash: this.hash } = options)
  49. this._id = 'file_id_here'
  50. this.rev = 0
  51. if (options.linkedFileData != null) {
  52. this.linkedFileData = options.linkedFileData
  53. }
  54. }
  55. }
  56. this.FileHashManager = {
  57. computeHash: sinon.stub().callsArgWith(1, null, this.hashValue),
  58. }
  59. this.HistoryManager = {
  60. uploadBlobFromDisk: sinon.stub().callsArg(4),
  61. }
  62. this.ProjectDetailsHandler = {
  63. getDetails: sinon.stub().callsArgWith(1, null, {
  64. overleaf: { history: { id: this.historyId } },
  65. }),
  66. }
  67. this.Features = {
  68. hasFeature: sinon.stub(),
  69. }
  70. this.handler = SandboxedModule.require(MODULE_PATH, {
  71. requires: {
  72. '@overleaf/settings': this.settings,
  73. request: this.request,
  74. '../History/HistoryManager': this.HistoryManager,
  75. '../Project/ProjectDetailsHandler': this.ProjectDetailsHandler,
  76. './FileHashManager': this.FileHashManager,
  77. '../../infrastructure/Features': this.Features,
  78. // FIXME: need to stub File object here
  79. '../../models/File': {
  80. File: this.FileModel,
  81. },
  82. fs: this.fs,
  83. },
  84. })
  85. })
  86. describe('uploadFileFromDisk', function () {
  87. beforeEach(function () {
  88. this.request.returns(this.writeStream)
  89. })
  90. it('should get the project details', async function () {
  91. this.fs.createReadStream.returns({
  92. pipe() {},
  93. on(type, cb) {
  94. if (type === 'open') {
  95. cb()
  96. }
  97. },
  98. })
  99. await this.handler.promises.uploadFileFromDisk(
  100. this.projectId,
  101. this.fileArgs,
  102. this.fsPath
  103. )
  104. this.ProjectDetailsHandler.getDetails
  105. .calledWith(this.projectId)
  106. .should.equal(true)
  107. })
  108. it('should compute the file hash', async function () {
  109. this.fs.createReadStream.returns({
  110. pipe() {},
  111. on(type, cb) {
  112. if (type === 'open') {
  113. cb()
  114. }
  115. },
  116. })
  117. await this.handler.promises.uploadFileFromDisk(
  118. this.projectId,
  119. this.fileArgs,
  120. this.fsPath
  121. )
  122. this.FileHashManager.computeHash
  123. .calledWith(this.fsPath)
  124. .should.equal(true)
  125. })
  126. it('should upload the file to the history store as a blob', async function () {
  127. this.fs.createReadStream.returns({
  128. pipe() {},
  129. on(type, cb) {
  130. if (type === 'open') {
  131. cb()
  132. }
  133. },
  134. })
  135. await this.handler.promises.uploadFileFromDisk(
  136. this.projectId,
  137. this.fileArgs,
  138. this.fsPath
  139. )
  140. this.HistoryManager.uploadBlobFromDisk
  141. .calledWith(this.historyId, this.hashValue, this.fileSize, this.fsPath)
  142. .should.equal(true)
  143. })
  144. it('should not open file handle', async function () {
  145. await this.handler.promises.uploadFileFromDisk(
  146. this.projectId,
  147. this.fileArgs,
  148. this.fsPath
  149. )
  150. expect(this.fs.createReadStream).to.not.have.been.called
  151. })
  152. it('should not talk to filestore', async function () {
  153. await this.handler.promises.uploadFileFromDisk(
  154. this.projectId,
  155. this.fileArgs,
  156. this.fsPath
  157. )
  158. expect(this.request).to.not.have.been.called
  159. })
  160. it('should resolve with the url and fileRef', async function () {
  161. const { fileRef } = await this.handler.promises.uploadFileFromDisk(
  162. this.projectId,
  163. this.fileArgs,
  164. this.fsPath
  165. )
  166. expect(fileRef._id).to.equal(this.fileId)
  167. expect(fileRef.hash).to.equal(this.hashValue)
  168. })
  169. describe('symlink', function () {
  170. it('should not read file if it is symlink', async function () {
  171. this.fs.lstat = sinon.stub().callsArgWith(1, null, {
  172. isFile() {
  173. return false
  174. },
  175. isDirectory() {
  176. return false
  177. },
  178. })
  179. let error
  180. try {
  181. await this.handler.promises.uploadFileFromDisk(
  182. this.projectId,
  183. this.fileArgs,
  184. this.fsPath
  185. )
  186. } catch (err) {
  187. error = err
  188. }
  189. expect(error).to.exist
  190. this.fs.createReadStream.called.should.equal(false)
  191. })
  192. it('should not read file stat returns nothing', async function () {
  193. this.fs.lstat = sinon.stub().callsArgWith(1, null, null)
  194. let error
  195. try {
  196. await this.handler.promises.uploadFileFromDisk(
  197. this.projectId,
  198. this.fileArgs,
  199. this.fsPath
  200. )
  201. } catch (err) {
  202. error = err
  203. }
  204. expect(error).to.exist
  205. this.fs.createReadStream.called.should.equal(false)
  206. })
  207. })
  208. })
  209. })