ProjectLocatorTests.js 18 KB

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