FileSystemImportManagerTests.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379
  1. const sinon = require('sinon')
  2. const { expect } = require('chai')
  3. const SandboxedModule = require('sandboxed-module')
  4. const { ObjectId } = require('mongodb')
  5. const MODULE_PATH =
  6. '../../../../app/src/Features/Uploads/FileSystemImportManager.js'
  7. describe('FileSystemImportManager', function() {
  8. beforeEach(function() {
  9. this.projectId = new ObjectId()
  10. this.folderId = new ObjectId()
  11. this.newFolderId = new ObjectId()
  12. this.userId = new ObjectId()
  13. this.folderPath = '/path/to/folder'
  14. this.docName = 'test-doc.tex'
  15. this.docPath = `/path/to/folder/${this.docName}`
  16. this.docContent = 'one\ntwo\nthree'
  17. this.docLines = this.docContent.split('\n')
  18. this.fileName = 'test-file.jpg'
  19. this.filePath = `/path/to/folder/${this.fileName}`
  20. this.symlinkName = 'symlink'
  21. this.symlinkPath = `/path/to/${this.symlinkName}`
  22. this.ignoredName = '.DS_Store'
  23. this.ignoredPath = `/path/to/folder/${this.ignoredName}`
  24. this.folderEntries = [this.ignoredName, this.docName, this.fileName]
  25. this.encoding = 'latin1'
  26. this.fileStat = {
  27. isFile: sinon.stub().returns(true),
  28. isDirectory: sinon.stub().returns(false)
  29. }
  30. this.dirStat = {
  31. isFile: sinon.stub().returns(false),
  32. isDirectory: sinon.stub().returns(true)
  33. }
  34. this.symlinkStat = {
  35. isFile: sinon.stub().returns(false),
  36. isDirectory: sinon.stub().returns(false)
  37. }
  38. this.fs = {
  39. promises: {
  40. lstat: sinon.stub(),
  41. readFile: sinon.stub(),
  42. readdir: sinon.stub()
  43. }
  44. }
  45. this.fs.promises.lstat.withArgs(this.filePath).resolves(this.fileStat)
  46. this.fs.promises.lstat.withArgs(this.docPath).resolves(this.fileStat)
  47. this.fs.promises.lstat.withArgs(this.symlinkPath).resolves(this.symlinkStat)
  48. this.fs.promises.lstat.withArgs(this.folderPath).resolves(this.dirStat)
  49. this.fs.promises.readFile
  50. .withArgs(this.docPath, this.encoding)
  51. .resolves(this.docContent)
  52. this.fs.promises.readdir
  53. .withArgs(this.folderPath)
  54. .resolves(this.folderEntries)
  55. this.EditorController = {
  56. promises: {
  57. addDoc: sinon.stub().resolves(),
  58. addFile: sinon.stub().resolves(),
  59. upsertDoc: sinon.stub().resolves(),
  60. upsertFile: sinon.stub().resolves(),
  61. addFolder: sinon.stub().resolves({ _id: this.newFolderId })
  62. }
  63. }
  64. this.FileTypeManager = {
  65. promises: {
  66. isDirectory: sinon.stub().resolves(false),
  67. getType: sinon.stub(),
  68. shouldIgnore: sinon.stub().resolves(false)
  69. }
  70. }
  71. this.FileTypeManager.promises.getType
  72. .withArgs(this.fileName, this.filePath)
  73. .resolves({ binary: true })
  74. this.FileTypeManager.promises.getType
  75. .withArgs(this.docName, this.docPath)
  76. .resolves({ binary: false, encoding: this.encoding })
  77. this.FileTypeManager.promises.isDirectory
  78. .withArgs(this.folderPath)
  79. .resolves(true)
  80. this.FileTypeManager.promises.shouldIgnore
  81. .withArgs(this.ignoredName)
  82. .resolves(true)
  83. this.logger = {
  84. log() {},
  85. err() {}
  86. }
  87. this.FileSystemImportManager = SandboxedModule.require(MODULE_PATH, {
  88. globals: {
  89. console: console
  90. },
  91. requires: {
  92. fs: this.fs,
  93. '../Editor/EditorController': this.EditorController,
  94. './FileTypeManager': this.FileTypeManager,
  95. 'logger-sharelatex': this.logger
  96. }
  97. })
  98. })
  99. describe('addFolderContents', function() {
  100. describe('successfully', function() {
  101. beforeEach(async function() {
  102. await this.FileSystemImportManager.promises.addFolderContents(
  103. this.userId,
  104. this.projectId,
  105. this.folderId,
  106. this.folderPath,
  107. false
  108. )
  109. })
  110. it('should add each file in the folder which is not ignored', function() {
  111. this.EditorController.promises.addDoc.should.have.been.calledWith(
  112. this.projectId,
  113. this.folderId,
  114. this.docName,
  115. this.docLines,
  116. 'upload',
  117. this.userId
  118. )
  119. this.EditorController.promises.addFile.should.have.been.calledWith(
  120. this.projectId,
  121. this.folderId,
  122. this.fileName,
  123. this.filePath,
  124. null,
  125. 'upload',
  126. this.userId
  127. )
  128. })
  129. })
  130. describe('with symlink', function() {
  131. it('should stop with an error', async function() {
  132. await expect(
  133. this.FileSystemImportManager.promises.addFolderContents(
  134. this.userId,
  135. this.projectId,
  136. this.folderId,
  137. this.symlinkPath,
  138. false
  139. )
  140. ).to.be.rejectedWith('path is symlink')
  141. this.EditorController.promises.addFolder.should.not.have.been.called
  142. this.EditorController.promises.addDoc.should.not.have.been.called
  143. this.EditorController.promises.addFile.should.not.have.been.called
  144. })
  145. })
  146. })
  147. describe('addEntity', function() {
  148. describe('with directory', function() {
  149. describe('successfully', function() {
  150. beforeEach(async function() {
  151. await this.FileSystemImportManager.promises.addEntity(
  152. this.userId,
  153. this.projectId,
  154. this.folderId,
  155. this.folderName,
  156. this.folderPath,
  157. false
  158. )
  159. })
  160. it('should add a folder to the project', function() {
  161. this.EditorController.promises.addFolder.should.have.been.calledWith(
  162. this.projectId,
  163. this.folderId,
  164. this.folderName,
  165. 'upload'
  166. )
  167. })
  168. it("should add the folder's contents", function() {
  169. this.EditorController.promises.addDoc.should.have.been.calledWith(
  170. this.projectId,
  171. this.newFolderId,
  172. this.docName,
  173. this.docLines,
  174. 'upload',
  175. this.userId
  176. )
  177. this.EditorController.promises.addFile.should.have.been.calledWith(
  178. this.projectId,
  179. this.newFolderId,
  180. this.fileName,
  181. this.filePath,
  182. null,
  183. 'upload',
  184. this.userId
  185. )
  186. })
  187. })
  188. })
  189. describe('with binary file', function() {
  190. describe('with replace set to false', function() {
  191. beforeEach(async function() {
  192. await this.FileSystemImportManager.promises.addEntity(
  193. this.userId,
  194. this.projectId,
  195. this.folderId,
  196. this.fileName,
  197. this.filePath,
  198. false
  199. )
  200. })
  201. it('should add the file', function() {
  202. this.EditorController.promises.addFile.should.have.been.calledWith(
  203. this.projectId,
  204. this.folderId,
  205. this.fileName,
  206. this.filePath,
  207. null,
  208. 'upload',
  209. this.userId
  210. )
  211. })
  212. })
  213. describe('with replace set to true', function() {
  214. beforeEach(async function() {
  215. await this.FileSystemImportManager.promises.addEntity(
  216. this.userId,
  217. this.projectId,
  218. this.folderId,
  219. this.fileName,
  220. this.filePath,
  221. true
  222. )
  223. })
  224. it('should add the file', function() {
  225. this.EditorController.promises.upsertFile.should.have.been.calledWith(
  226. this.projectId,
  227. this.folderId,
  228. this.fileName,
  229. this.filePath,
  230. null,
  231. 'upload',
  232. this.userId
  233. )
  234. })
  235. })
  236. describe('with text file', function() {
  237. describe('with replace set to false', function() {
  238. beforeEach(async function() {
  239. await this.FileSystemImportManager.promises.addEntity(
  240. this.userId,
  241. this.projectId,
  242. this.folderId,
  243. this.docName,
  244. this.docPath,
  245. false
  246. )
  247. })
  248. it('should insert the doc', function() {
  249. this.EditorController.promises.addDoc.should.have.been.calledWith(
  250. this.projectId,
  251. this.folderId,
  252. this.docName,
  253. this.docLines,
  254. 'upload',
  255. this.userId
  256. )
  257. })
  258. })
  259. describe('with windows line ending', function() {
  260. beforeEach(async function() {
  261. this.docContent = 'one\r\ntwo\r\nthree'
  262. this.docLines = ['one', 'two', 'three']
  263. this.fs.promises.readFile
  264. .withArgs(this.docPath, this.encoding)
  265. .resolves(this.docContent)
  266. await this.FileSystemImportManager.promises.addEntity(
  267. this.userId,
  268. this.projectId,
  269. this.folderId,
  270. this.docName,
  271. this.docPath,
  272. false
  273. )
  274. })
  275. it('should strip the \\r characters before adding', function() {
  276. this.EditorController.promises.addDoc.should.have.been.calledWith(
  277. this.projectId,
  278. this.folderId,
  279. this.docName,
  280. this.docLines,
  281. 'upload',
  282. this.userId
  283. )
  284. })
  285. })
  286. describe('with \r line endings', function() {
  287. beforeEach(async function() {
  288. this.docContent = 'one\rtwo\rthree'
  289. this.docLines = ['one', 'two', 'three']
  290. this.fs.promises.readFile
  291. .withArgs(this.docPath, this.encoding)
  292. .resolves(this.docContent)
  293. await this.FileSystemImportManager.promises.addEntity(
  294. this.userId,
  295. this.projectId,
  296. this.folderId,
  297. this.docName,
  298. this.docPath,
  299. false
  300. )
  301. })
  302. it('should treat the \\r characters as newlines', function() {
  303. this.EditorController.promises.addDoc.should.have.been.calledWith(
  304. this.projectId,
  305. this.folderId,
  306. this.docName,
  307. this.docLines,
  308. 'upload',
  309. this.userId
  310. )
  311. })
  312. })
  313. describe('with replace set to true', function() {
  314. beforeEach(async function() {
  315. await this.FileSystemImportManager.promises.addEntity(
  316. this.userId,
  317. this.projectId,
  318. this.folderId,
  319. this.docName,
  320. this.docPath,
  321. true
  322. )
  323. })
  324. it('should upsert the doc', function() {
  325. this.EditorController.promises.upsertDoc.should.have.been.calledWith(
  326. this.projectId,
  327. this.folderId,
  328. this.docName,
  329. this.docLines,
  330. 'upload',
  331. this.userId
  332. )
  333. })
  334. })
  335. })
  336. })
  337. describe('with symlink', function() {
  338. it('should stop with an error', async function() {
  339. await expect(
  340. this.FileSystemImportManager.promises.addEntity(
  341. this.userId,
  342. this.projectId,
  343. this.folderId,
  344. this.symlinkName,
  345. this.symlinkPath,
  346. false
  347. )
  348. ).to.be.rejectedWith('path is symlink')
  349. this.EditorController.promises.addFolder.should.not.have.been.called
  350. this.EditorController.promises.addDoc.should.not.have.been.called
  351. this.EditorController.promises.addFile.should.not.have.been.called
  352. })
  353. })
  354. })
  355. })