ProjectLocator.test.mjs 17 KB

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