ProjectLocatorTests.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510
  1. const { expect } = require('chai')
  2. const modulePath = '../../../../app/src/Features/Project/ProjectLocator'
  3. const SandboxedModule = require('sandboxed-module')
  4. const sinon = require('sinon')
  5. const Errors = require('../../../../app/src/Features/Errors/Errors')
  6. const project = { _id: '1234566', rootFolder: [] }
  7. const rootDoc = { name: 'rootDoc', _id: 'das239djd' }
  8. const doc1 = { name: 'otherDoc.txt', _id: 'dsad2ddd' }
  9. const doc2 = { name: 'docname.txt', _id: 'dsad2ddddd' }
  10. const file1 = { name: 'file1', _id: 'dsa9lkdsad' }
  11. const subSubFile = { name: 'subSubFile', _id: 'd1d2dk' }
  12. const subSubDoc = { name: 'subdoc.txt', _id: '321dmdwi' }
  13. const secondSubFolder = {
  14. name: 'secondSubFolder',
  15. _id: 'dsa3e23',
  16. docs: [subSubDoc],
  17. fileRefs: [subSubFile],
  18. folders: [],
  19. }
  20. const subFolder = {
  21. name: 'subFolder',
  22. _id: 'dsadsa93',
  23. folders: [secondSubFolder, null],
  24. docs: [],
  25. fileRefs: [],
  26. }
  27. const subFolder1 = { name: 'subFolder1', _id: '123asdjoij' }
  28. const rootFolder = {
  29. _id: '123sdskd',
  30. docs: [doc1, doc2, null, rootDoc],
  31. fileRefs: [file1],
  32. folders: [subFolder1, subFolder],
  33. }
  34. project.rootFolder[0] = rootFolder
  35. project.rootDoc_id = rootDoc._id
  36. describe('ProjectLocator', function () {
  37. beforeEach(function () {
  38. this.ProjectGetter = {
  39. getProject: sinon.stub().callsArgWith(2, null, project),
  40. }
  41. this.ProjectHelper = {
  42. isArchived: sinon.stub(),
  43. isTrashed: sinon.stub(),
  44. isArchivedOrTrashed: sinon.stub(),
  45. }
  46. this.locator = SandboxedModule.require(modulePath, {
  47. requires: {
  48. '../../models/User': { User: this.User },
  49. './ProjectGetter': this.ProjectGetter,
  50. './ProjectHelper': this.ProjectHelper,
  51. },
  52. })
  53. })
  54. describe('finding a doc', function () {
  55. it('finds one at the root level', async function () {
  56. const { element, path, folder } = await this.locator.promises.findElement(
  57. {
  58. project_id: project._id,
  59. element_id: doc2._id,
  60. type: 'docs',
  61. }
  62. )
  63. element._id.should.equal(doc2._id)
  64. path.fileSystem.should.equal(`/${doc2.name}`)
  65. folder._id.should.equal(project.rootFolder[0]._id)
  66. path.mongo.should.equal('rootFolder.0.docs.1')
  67. })
  68. it('when it is nested', async function () {
  69. const { element, path, folder } = await this.locator.promises.findElement(
  70. {
  71. project_id: project._id,
  72. element_id: subSubDoc._id,
  73. type: 'doc',
  74. }
  75. )
  76. expect(element._id).to.equal(subSubDoc._id)
  77. path.fileSystem.should.equal(
  78. `/${subFolder.name}/${secondSubFolder.name}/${subSubDoc.name}`
  79. )
  80. folder._id.should.equal(secondSubFolder._id)
  81. path.mongo.should.equal('rootFolder.0.folders.1.folders.0.docs.0')
  82. })
  83. it('should give error if element could not be found', async function () {
  84. await expect(
  85. this.locator.promises.findElement({
  86. project_id: project._id,
  87. element_id: 'ddsd432nj42',
  88. type: 'docs',
  89. })
  90. )
  91. .to.eventually.be.rejectedWith(Errors.NotFoundError)
  92. .and.eventually.have.property('message', 'entity not found')
  93. })
  94. })
  95. describe('finding a folder', function () {
  96. it('should return root folder when looking for root folder', async function () {
  97. const { element: foundElement } = await this.locator.promises.findElement(
  98. {
  99. project_id: project._id,
  100. element_id: rootFolder._id,
  101. type: 'folder',
  102. }
  103. )
  104. foundElement._id.should.equal(rootFolder._id)
  105. })
  106. it('when at root', async function () {
  107. const {
  108. element: foundElement,
  109. path,
  110. folder: parentFolder,
  111. } = await this.locator.promises.findElement({
  112. project_id: project._id,
  113. element_id: subFolder._id,
  114. type: 'folder',
  115. })
  116. foundElement._id.should.equal(subFolder._id)
  117. path.fileSystem.should.equal(`/${subFolder.name}`)
  118. parentFolder._id.should.equal(rootFolder._id)
  119. path.mongo.should.equal('rootFolder.0.folders.1')
  120. })
  121. it('when deeply nested', async function () {
  122. const {
  123. element: foundElement,
  124. path,
  125. folder: parentFolder,
  126. } = await this.locator.promises.findElement({
  127. project_id: project._id,
  128. element_id: secondSubFolder._id,
  129. type: 'folder',
  130. })
  131. foundElement._id.should.equal(secondSubFolder._id)
  132. path.fileSystem.should.equal(`/${subFolder.name}/${secondSubFolder.name}`)
  133. parentFolder._id.should.equal(subFolder._id)
  134. path.mongo.should.equal('rootFolder.0.folders.1.folders.0')
  135. })
  136. })
  137. describe('finding a file', function () {
  138. it('when at root', async function () {
  139. const {
  140. element: foundElement,
  141. path,
  142. folder: parentFolder,
  143. } = await this.locator.promises.findElement({
  144. project_id: project._id,
  145. element_id: file1._id,
  146. type: 'fileRefs',
  147. })
  148. foundElement._id.should.equal(file1._id)
  149. path.fileSystem.should.equal(`/${file1.name}`)
  150. parentFolder._id.should.equal(rootFolder._id)
  151. path.mongo.should.equal('rootFolder.0.fileRefs.0')
  152. })
  153. it('when deeply nested', async function () {
  154. const {
  155. element: foundElement,
  156. path,
  157. folder: parentFolder,
  158. } = await this.locator.promises.findElement({
  159. project_id: project._id,
  160. element_id: subSubFile._id,
  161. type: 'fileRefs',
  162. })
  163. foundElement._id.should.equal(subSubFile._id)
  164. path.fileSystem.should.equal(
  165. `/${subFolder.name}/${secondSubFolder.name}/${subSubFile.name}`
  166. )
  167. parentFolder._id.should.equal(secondSubFolder._id)
  168. path.mongo.should.equal('rootFolder.0.folders.1.folders.0.fileRefs.0')
  169. })
  170. })
  171. describe('finding an element with wrong element type', function () {
  172. it('should add an s onto the element type', async function () {
  173. const { element: foundElement } = await this.locator.promises.findElement(
  174. {
  175. project_id: project._id,
  176. element_id: subSubDoc._id,
  177. type: 'doc',
  178. }
  179. )
  180. foundElement._id.should.equal(subSubDoc._id)
  181. })
  182. it('should convert file to fileRefs', async function () {
  183. const { element: foundElement } = await this.locator.promises.findElement(
  184. {
  185. project_id: project._id,
  186. element_id: file1._id,
  187. type: 'fileRefs',
  188. }
  189. )
  190. foundElement._id.should.equal(file1._id)
  191. })
  192. })
  193. describe('should be able to take actual project as well as id', function () {
  194. const doc3 = {
  195. _id: '123dsdj3',
  196. name: 'doc3',
  197. }
  198. const rootFolder2 = {
  199. _id: '123sddedskd',
  200. docs: [doc3],
  201. }
  202. const project2 = {
  203. _id: '1234566',
  204. rootFolder: [rootFolder2],
  205. }
  206. it('should find doc in project', async function () {
  207. const {
  208. element: foundElement,
  209. path,
  210. folder: parentFolder,
  211. } = await this.locator.promises.findElement({
  212. project: project2,
  213. element_id: doc3._id,
  214. type: 'docs',
  215. })
  216. foundElement._id.should.equal(doc3._id)
  217. path.fileSystem.should.equal(`/${doc3.name}`)
  218. parentFolder._id.should.equal(project2.rootFolder[0]._id)
  219. path.mongo.should.equal('rootFolder.0.docs.0')
  220. })
  221. })
  222. describe('finding root doc', function () {
  223. it('should return root doc when passed project', async function () {
  224. const { element: doc } = await this.locator.promises.findRootDoc(project)
  225. doc._id.should.equal(rootDoc._id)
  226. })
  227. it('should return root doc when passed project_id', async function () {
  228. const { element: doc } = await this.locator.promises.findRootDoc(
  229. project._id
  230. )
  231. doc._id.should.equal(rootDoc._id)
  232. })
  233. it('should return null when the project has no rootDoc', async function () {
  234. project.rootDoc_id = null
  235. const { element: rootDoc } =
  236. await this.locator.promises.findRootDoc(project)
  237. expect(rootDoc).to.equal(null)
  238. })
  239. it('should return null when the rootDoc_id no longer exists', async function () {
  240. project.rootDoc_id = 'doesntexist'
  241. const { element: rootDoc } =
  242. await this.locator.promises.findRootDoc(project)
  243. expect(rootDoc).to.equal(null)
  244. })
  245. })
  246. describe('findElementByPath', function () {
  247. it('should take a doc path and return the element for a root level document', async function () {
  248. const path = `${doc1.name}`
  249. const { element, type, folder } =
  250. await this.locator.promises.findElementByPath({
  251. project,
  252. path,
  253. })
  254. element.should.deep.equal(doc1)
  255. expect(type).to.equal('doc')
  256. expect(folder).to.equal(rootFolder)
  257. })
  258. it('should take a doc path and return the element for a root level document with a starting slash', async function () {
  259. const path = `/${doc1.name}`
  260. const { element, type, folder } =
  261. await this.locator.promises.findElementByPath({
  262. project,
  263. path,
  264. })
  265. element.should.deep.equal(doc1)
  266. expect(type).to.equal('doc')
  267. expect(folder).to.equal(rootFolder)
  268. })
  269. it('should take a doc path and return the element for a nested document', async function () {
  270. const path = `${subFolder.name}/${secondSubFolder.name}/${subSubDoc.name}`
  271. const { element, type, folder } =
  272. await this.locator.promises.findElementByPath({
  273. project,
  274. path,
  275. })
  276. element.should.deep.equal(subSubDoc)
  277. expect(type).to.equal('doc')
  278. expect(folder).to.equal(secondSubFolder)
  279. })
  280. it('should take a file path and return the element for a root level document', async function () {
  281. const path = `${file1.name}`
  282. const { element, type, folder } =
  283. await this.locator.promises.findElementByPath({
  284. project,
  285. path,
  286. })
  287. element.should.deep.equal(file1)
  288. expect(type).to.equal('file')
  289. expect(folder).to.equal(rootFolder)
  290. })
  291. it('should take a file path and return the element for a nested document', async function () {
  292. const path = `${subFolder.name}/${secondSubFolder.name}/${subSubFile.name}`
  293. const { element, type, folder } =
  294. await this.locator.promises.findElementByPath({
  295. project,
  296. path,
  297. })
  298. element.should.deep.equal(subSubFile)
  299. expect(type).to.equal('file')
  300. expect(folder).to.equal(secondSubFolder)
  301. })
  302. it('should take a file path and return the element for a nested document case insenstive', async function () {
  303. const path = `${subFolder.name.toUpperCase()}/${secondSubFolder.name.toUpperCase()}/${subSubFile.name.toUpperCase()}`
  304. const { element, type, folder } =
  305. await this.locator.promises.findElementByPath({
  306. project,
  307. path,
  308. })
  309. element.should.deep.equal(subSubFile)
  310. expect(type).to.equal('file')
  311. expect(folder).to.equal(secondSubFolder)
  312. })
  313. it('should not return elements with a case-insensitive match when exactCaseMatch is true', async function () {
  314. const path = `${subFolder.name.toUpperCase()}/${secondSubFolder.name.toUpperCase()}/${subSubFile.name.toUpperCase()}`
  315. await expect(
  316. this.locator.promises.findElementByPath({
  317. project,
  318. path,
  319. exactCaseMatch: true,
  320. })
  321. ).to.eventually.be.rejected
  322. })
  323. it('should take a file path and return the element for a nested folder', async function () {
  324. const path = `${subFolder.name}/${secondSubFolder.name}`
  325. const { element, type, folder } =
  326. await this.locator.promises.findElementByPath({
  327. project,
  328. path,
  329. })
  330. element.should.deep.equal(secondSubFolder)
  331. expect(type).to.equal('folder')
  332. expect(folder).to.equal(subFolder)
  333. })
  334. it('should take a file path and return the root folder', async function () {
  335. const path = '/'
  336. const { element, type, folder } =
  337. await this.locator.promises.findElementByPath({
  338. project,
  339. path,
  340. })
  341. element.should.deep.equal(rootFolder)
  342. expect(type).to.equal('folder')
  343. expect(folder).to.equal(null)
  344. })
  345. it('should return an error if the file can not be found inside know folder', async function () {
  346. const path = `${subFolder.name}/${secondSubFolder.name}/exist.txt`
  347. await expect(this.locator.promises.findElementByPath({ project, path }))
  348. .to.eventually.be.rejected
  349. })
  350. it('should return an error if the file can not be found inside unknown folder', async function () {
  351. const path = 'this/does/not/exist.txt'
  352. await expect(
  353. this.locator.promises.findElementByPath({
  354. project,
  355. path,
  356. })
  357. ).to.eventually.be.rejected
  358. })
  359. describe('where duplicate folder exists', function () {
  360. beforeEach(function () {
  361. this.duplicateFolder = {
  362. name: 'duplicate1',
  363. _id: '1234',
  364. folders: [
  365. {
  366. name: '1',
  367. docs: [{ name: 'main.tex', _id: '456' }],
  368. folders: [],
  369. fileRefs: [],
  370. },
  371. ],
  372. docs: [(this.doc = { name: 'main.tex', _id: '456' })],
  373. fileRefs: [],
  374. }
  375. this.project = {
  376. rootFolder: [
  377. {
  378. folders: [this.duplicateFolder, this.duplicateFolder],
  379. fileRefs: [],
  380. docs: [],
  381. },
  382. ],
  383. }
  384. })
  385. it('should not call the callback more than once', async function () {
  386. const path = `${this.duplicateFolder.name}/${this.doc.name}`
  387. await this.locator.promises.findElementByPath({
  388. project: this.project,
  389. path,
  390. })
  391. }) // mocha will throw exception if done called multiple times
  392. it('should not call the callback more than once when the path is longer than 1 level below the duplicate level', async function () {
  393. const path = `${this.duplicateFolder.name}/1/main.tex`
  394. await this.locator.promises.findElementByPath({
  395. project: this.project,
  396. path,
  397. })
  398. })
  399. }) // mocha will throw exception if done called multiple times
  400. describe('with a null doc', function () {
  401. beforeEach(function () {
  402. this.project = {
  403. rootFolder: [
  404. {
  405. folders: [],
  406. fileRefs: [],
  407. docs: [{ name: 'main.tex' }, null, { name: 'other.tex' }],
  408. },
  409. ],
  410. }
  411. })
  412. it('should not crash with a null', async function () {
  413. const path = '/other.tex'
  414. const { element } = await this.locator.promises.findElementByPath({
  415. project: this.project,
  416. path,
  417. })
  418. element.name.should.equal('other.tex')
  419. })
  420. })
  421. describe('with a null project', function () {
  422. beforeEach(function () {
  423. this.ProjectGetter = { getProject: sinon.stub().callsArg(2) }
  424. })
  425. it('should not crash with a null', async function () {
  426. const path = '/other.tex'
  427. await expect(
  428. this.locator.promises.findElementByPath({
  429. project_id: project._id,
  430. path,
  431. })
  432. ).to.be.rejected
  433. })
  434. })
  435. describe('with a project_id', function () {
  436. it('should take a doc path and return the element for a root level document', async function () {
  437. const path = `${doc1.name}`
  438. const { element, type } = await this.locator.promises.findElementByPath(
  439. {
  440. project_id: project._id,
  441. path,
  442. }
  443. )
  444. this.ProjectGetter.getProject
  445. .calledWith(project._id, { rootFolder: true, rootDoc_id: true })
  446. .should.equal(true)
  447. element.should.deep.equal(doc1)
  448. expect(type).to.equal('doc')
  449. })
  450. })
  451. })
  452. describe('findElementByMongoPath', function () {
  453. it('traverses the file tree like Mongo would do', function () {
  454. const element = this.locator.findElementByMongoPath(
  455. project,
  456. 'rootFolder.0.folders.1.folders.0.fileRefs.0'
  457. )
  458. expect(element).to.equal(subSubFile)
  459. })
  460. it('throws an error if no element is found', function () {
  461. expect(() =>
  462. this.locator.findElementByMongoPath(
  463. project,
  464. 'rootolder.0.folders.0.folders.0.fileRefs.0'
  465. )
  466. ).to.throw
  467. })
  468. })
  469. })