ProjectEntityHandler.test.mjs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493
  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/ProjectEntityHandler'
  5. vi.mock('../../../../app/src/Features/Errors/Errors.js', () =>
  6. vi.importActual('../../../../app/src/Features/Errors/Errors.js')
  7. )
  8. describe('ProjectEntityHandler', function () {
  9. const projectId = '4eecb1c1bffa66588e0000a1'
  10. const docId = '4eecb1c1bffa66588e0000a2'
  11. beforeEach(async function (ctx) {
  12. ctx.TpdsUpdateSender = {
  13. addDoc: sinon.stub().callsArg(1),
  14. addFile: sinon.stub().callsArg(1),
  15. }
  16. ctx.ProjectModel = class Project {
  17. constructor(options) {
  18. this._id = projectId
  19. this.name = 'project_name_here'
  20. this.rev = 0
  21. this.rootFolder = [this.rootFolder]
  22. }
  23. }
  24. ctx.project = new ctx.ProjectModel()
  25. ctx.ProjectLocator = { findElement: sinon.stub() }
  26. ctx.DocumentUpdaterHandler = {
  27. updateProjectStructure: sinon.stub().yields(),
  28. }
  29. ctx.callback = sinon.stub()
  30. vi.doMock('../../../../app/src/Features/Docstore/DocstoreManager', () => ({
  31. default: (ctx.DocstoreManager = {
  32. promises: {},
  33. }),
  34. }))
  35. vi.doMock(
  36. '../../../../app/src/Features/DocumentUpdater/DocumentUpdaterHandler',
  37. () => ({
  38. default: ctx.DocumentUpdaterHandler,
  39. })
  40. )
  41. vi.doMock('../../../../app/src/models/Project', () => ({
  42. Project: ctx.ProjectModel,
  43. }))
  44. vi.doMock('../../../../app/src/Features/Project/ProjectLocator', () => ({
  45. default: ctx.ProjectLocator,
  46. }))
  47. vi.doMock('../../../../app/src/Features/Project/ProjectGetter', () => ({
  48. default: (ctx.ProjectGetter = { promises: {} }),
  49. }))
  50. vi.doMock(
  51. '../../../../app/src/Features/ThirdPartyDataStore/TpdsUpdateSender',
  52. () => ({
  53. default: ctx.TpdsUpdateSender,
  54. })
  55. )
  56. ctx.ProjectEntityHandler = (await import(modulePath)).default
  57. })
  58. describe('getting folders, docs and files', function () {
  59. beforeEach(function (ctx) {
  60. ctx.project.rootFolder = [
  61. {
  62. docs: [
  63. (ctx.doc1 = {
  64. name: 'doc1',
  65. _id: 'doc1_id',
  66. }),
  67. ],
  68. fileRefs: [
  69. (ctx.file1 = {
  70. rev: 1,
  71. _id: 'file1_id',
  72. name: 'file1',
  73. }),
  74. ],
  75. folders: [
  76. (ctx.folder1 = {
  77. name: 'folder1',
  78. docs: [
  79. (ctx.doc2 = {
  80. name: 'doc2',
  81. _id: 'doc2_id',
  82. }),
  83. ],
  84. fileRefs: [
  85. (ctx.file2 = {
  86. rev: 2,
  87. name: 'file2',
  88. _id: 'file2_id',
  89. }),
  90. ],
  91. folders: [],
  92. }),
  93. ],
  94. },
  95. ]
  96. ctx.ProjectGetter.promises.getProject = sinon.stub().resolves(ctx.project)
  97. })
  98. describe('getAllDocs', function () {
  99. let fetchedDocs
  100. beforeEach(async function (ctx) {
  101. ctx.docs = [
  102. {
  103. _id: ctx.doc1._id,
  104. lines: (ctx.lines1 = ['one']),
  105. rev: (ctx.rev1 = 1),
  106. },
  107. {
  108. _id: ctx.doc2._id,
  109. lines: (ctx.lines2 = ['two']),
  110. rev: (ctx.rev2 = 2),
  111. },
  112. ]
  113. ctx.DocstoreManager.promises.getAllDocs = sinon
  114. .stub()
  115. .resolves(ctx.docs)
  116. fetchedDocs =
  117. await ctx.ProjectEntityHandler.promises.getAllDocs(projectId)
  118. })
  119. it('should get the doc lines and rev from the docstore', function (ctx) {
  120. ctx.DocstoreManager.promises.getAllDocs
  121. .calledWith(projectId)
  122. .should.equal(true)
  123. })
  124. it('should call the callback with the docs with the lines and rev included', function (ctx) {
  125. expect(fetchedDocs).to.deep.equal({
  126. '/doc1': {
  127. _id: ctx.doc1._id,
  128. lines: ctx.lines1,
  129. name: ctx.doc1.name,
  130. rev: ctx.rev1,
  131. folder: ctx.project.rootFolder[0],
  132. },
  133. '/folder1/doc2': {
  134. _id: ctx.doc2._id,
  135. lines: ctx.lines2,
  136. name: ctx.doc2.name,
  137. rev: ctx.rev2,
  138. folder: ctx.folder1,
  139. },
  140. })
  141. })
  142. })
  143. describe('getAllFiles', function () {
  144. let allFiles
  145. beforeEach(async function (ctx) {
  146. ctx.callback = sinon.stub()
  147. allFiles = await ctx.ProjectEntityHandler.promises.getAllFiles(
  148. projectId,
  149. ctx.callback
  150. )
  151. })
  152. it('should call the callback with the files', function (ctx) {
  153. expect(allFiles).to.deep.equal({
  154. '/file1': { ...ctx.file1, folder: ctx.project.rootFolder[0] },
  155. '/folder1/file2': { ...ctx.file2, folder: ctx.folder1 },
  156. })
  157. })
  158. })
  159. describe('getAllDocPathsFromProject', function () {
  160. beforeEach(function (ctx) {
  161. ctx.docs = [
  162. {
  163. _id: ctx.doc1._id,
  164. lines: (ctx.lines1 = ['one']),
  165. rev: (ctx.rev1 = 1),
  166. },
  167. {
  168. _id: ctx.doc2._id,
  169. lines: (ctx.lines2 = ['two']),
  170. rev: (ctx.rev2 = 2),
  171. },
  172. ]
  173. })
  174. it('should call the callback with the path for each docId', function (ctx) {
  175. const expected = {
  176. [ctx.doc1._id]: `/${ctx.doc1.name}`,
  177. [ctx.doc2._id]: `/folder1/${ctx.doc2.name}`,
  178. }
  179. expect(
  180. ctx.ProjectEntityHandler.getAllDocPathsFromProject(
  181. ctx.project,
  182. ctx.callback
  183. )
  184. ).to.deep.equal(expected)
  185. })
  186. })
  187. describe('getDocPathByProjectIdAndDocId', function () {
  188. it('should call the callback with the path for an existing doc id at the root level', async function (ctx) {
  189. const path =
  190. await ctx.ProjectEntityHandler.promises.getDocPathByProjectIdAndDocId(
  191. projectId,
  192. ctx.doc1._id
  193. )
  194. expect(path).to.deep.equal(`/${ctx.doc1.name}`)
  195. })
  196. it('should call the callback with the path for an existing doc id nested within a folder', async function (ctx) {
  197. const path =
  198. await ctx.ProjectEntityHandler.promises.getDocPathByProjectIdAndDocId(
  199. projectId,
  200. ctx.doc2._id
  201. )
  202. expect(path).to.deep.equal(`/folder1/${ctx.doc2.name}`)
  203. })
  204. it('should call the callback with a NotFoundError for a non-existing doc', async function (ctx) {
  205. await expect(
  206. ctx.ProjectEntityHandler.promises.getDocPathByProjectIdAndDocId(
  207. projectId,
  208. 'non-existing-id'
  209. )
  210. ).to.be.rejectedWith(Errors.NotFoundError)
  211. })
  212. it('should call the callback with a NotFoundError for an existing file', async function (ctx) {
  213. await expect(
  214. ctx.ProjectEntityHandler.promises.getDocPathByProjectIdAndDocId(
  215. projectId,
  216. ctx.file1._id
  217. )
  218. ).to.be.rejectedWith(Errors.NotFoundError)
  219. })
  220. })
  221. describe('_getAllFolders', async function () {
  222. let folders
  223. beforeEach(async function (ctx) {
  224. ctx.callback = sinon.stub()
  225. folders =
  226. await ctx.ProjectEntityHandler.promises._getAllFolders(projectId)
  227. })
  228. it('should get the project without the docs lines', function (ctx) {
  229. ctx.ProjectGetter.promises.getProject
  230. .calledWith(projectId)
  231. .should.equal(true)
  232. })
  233. it('should call the callback with the folders', function (ctx) {
  234. expect(folders).to.deep.equal([
  235. { path: '/', folder: ctx.project.rootFolder[0] },
  236. { path: '/folder1', folder: ctx.folder1 },
  237. ])
  238. })
  239. })
  240. describe('_getAllFoldersFromProject', function () {
  241. it('should return the folders', function (ctx) {
  242. expect(
  243. ctx.ProjectEntityHandler._getAllFoldersFromProject(ctx.project)
  244. ).to.deep.equal([
  245. { path: '/', folder: ctx.project.rootFolder[0] },
  246. { path: '/folder1', folder: ctx.folder1 },
  247. ])
  248. })
  249. })
  250. })
  251. describe('with an invalid file tree', function () {
  252. beforeEach(function (ctx) {
  253. ctx.project.rootFolder = [
  254. {
  255. docs: [
  256. (ctx.doc1 = {
  257. name: null, // invalid doc name
  258. _id: 'doc1_id',
  259. }),
  260. ],
  261. fileRefs: [
  262. (ctx.file1 = {
  263. rev: 1,
  264. _id: 'file1_id',
  265. name: null, // invalid file name
  266. }),
  267. ],
  268. folders: [
  269. (ctx.folder1 = {
  270. name: null, // invalid folder name
  271. docs: [
  272. (ctx.doc2 = {
  273. name: 'doc2',
  274. _id: 'doc2_id',
  275. }),
  276. ],
  277. fileRefs: [
  278. (ctx.file2 = {
  279. rev: 2,
  280. name: 'file2',
  281. _id: 'file2_id',
  282. }),
  283. ],
  284. folders: null,
  285. }),
  286. null, // invalid folder
  287. ],
  288. },
  289. ]
  290. ctx.ProjectGetter.promises.getProject = sinon.stub().resolves(ctx.project)
  291. })
  292. describe('getAllDocs', function () {
  293. beforeEach(async function (ctx) {
  294. ctx.docs = [
  295. {
  296. _id: ctx.doc1._id,
  297. lines: (ctx.lines1 = ['one']),
  298. rev: (ctx.rev1 = 1),
  299. },
  300. {
  301. _id: ctx.doc2._id,
  302. lines: (ctx.lines2 = ['two']),
  303. rev: (ctx.rev2 = 2),
  304. },
  305. ]
  306. ctx.DocstoreManager.promises.getAllDocs = sinon
  307. .stub()
  308. .resolves(ctx.docs)
  309. })
  310. it('should call the callback with an error', async function (ctx) {
  311. await expect(ctx.ProjectEntityHandler.promises.getAllDocs(projectId)).to
  312. .be.rejected
  313. })
  314. })
  315. describe('getAllFiles', function () {
  316. it('should call the callback with and error', async function (ctx) {
  317. await expect(ctx.ProjectEntityHandler.promises.getAllFiles(projectId))
  318. .to.be.rejected
  319. })
  320. })
  321. describe('getDocPathByProjectIdAndDocId', function () {
  322. it('should call the callback with an error for an existing doc id at the root level', async function (ctx) {
  323. await expect(
  324. ctx.ProjectEntityHandler.promises.getDocPathByProjectIdAndDocId(
  325. projectId,
  326. ctx.doc1._id
  327. )
  328. ).to.be.rejectedWith(Error)
  329. })
  330. it('should call the callback with an error for an existing doc id nested within a folder', async function (ctx) {
  331. await expect(
  332. ctx.ProjectEntityHandler.promises.getDocPathByProjectIdAndDocId(
  333. projectId,
  334. ctx.doc2._id
  335. )
  336. ).to.be.rejectedWith(Error)
  337. })
  338. it('should call the callback with an error for a non-existing doc', async function (ctx) {
  339. await expect(
  340. ctx.ProjectEntityHandler.promises.getDocPathByProjectIdAndDocId(
  341. projectId,
  342. 'non-existing-id'
  343. )
  344. ).to.be.rejectedWith(Error)
  345. })
  346. it('should call the callback with an error for an existing file', async function (ctx) {
  347. await expect(
  348. ctx.ProjectEntityHandler.promises.getDocPathByProjectIdAndDocId(
  349. projectId,
  350. ctx.file1._id
  351. )
  352. ).to.be.rejectedWith(Error)
  353. })
  354. })
  355. describe('_getAllFolders', function () {
  356. it('should call the callback with an error', async function (ctx) {
  357. await expect(
  358. ctx.ProjectEntityHandler.promises._getAllFolders(projectId)
  359. ).to.be.rejected
  360. })
  361. })
  362. describe('getAllEntities', function () {
  363. beforeEach(function (ctx) {
  364. ctx.ProjectGetter.promises.getProject = sinon
  365. .stub()
  366. .resolves(ctx.project)
  367. })
  368. it('should call the callback with an error', async function (ctx) {
  369. await expect(
  370. ctx.ProjectEntityHandler.promises.getAllEntities(projectId)
  371. ).to.be.rejected
  372. })
  373. })
  374. describe('getAllDocPathsFromProjectById', function () {
  375. it('should call the callback with an error', async function (ctx) {
  376. await expect(
  377. ctx.ProjectEntityHandler.promises.getAllDocPathsFromProjectById(
  378. projectId
  379. )
  380. ).to.be.rejected
  381. })
  382. })
  383. describe('getDocPathFromProjectByDocId', function () {
  384. it('should call the callback with an error', async function (ctx) {
  385. await expect(
  386. ctx.ProjectEntityHandler.promises.getDocPathFromProjectByDocId(
  387. projectId,
  388. ctx.doc1._id
  389. )
  390. ).to.be.rejected
  391. })
  392. })
  393. })
  394. describe('getDoc', function () {
  395. beforeEach(function (ctx) {
  396. ctx.lines = ['mock', 'doc', 'lines']
  397. ctx.rev = 5
  398. ctx.version = 42
  399. ctx.ranges = { mock: 'ranges' }
  400. ctx.callback = sinon.stub()
  401. ctx.DocstoreManager.promises.getDoc = sinon.stub().resolves({
  402. lines: ctx.lines,
  403. rev: ctx.rev,
  404. version: ctx.version,
  405. ranges: ctx.ranges,
  406. })
  407. })
  408. it('should call the callback with the lines, version and rev', async function (ctx) {
  409. const doc = await ctx.ProjectEntityHandler.promises.getDoc(
  410. projectId,
  411. docId
  412. )
  413. ctx.DocstoreManager.promises.getDoc
  414. .calledWith(projectId, docId)
  415. .should.equal(true)
  416. expect(doc).to.exist
  417. })
  418. })
  419. describe('promises.getDoc', function () {
  420. let result
  421. beforeEach(async function (ctx) {
  422. ctx.lines = ['mock', 'doc', 'lines']
  423. ctx.rev = 5
  424. ctx.version = 42
  425. ctx.ranges = { mock: 'ranges' }
  426. ctx.DocstoreManager.promises.getDoc = sinon.stub().resolves({
  427. lines: ctx.lines,
  428. rev: ctx.rev,
  429. version: ctx.version,
  430. ranges: ctx.ranges,
  431. })
  432. result = await ctx.ProjectEntityHandler.promises.getDoc(projectId, docId)
  433. })
  434. it('should call the docstore', function (ctx) {
  435. ctx.DocstoreManager.promises.getDoc
  436. .calledWith(projectId, docId)
  437. .should.equal(true)
  438. })
  439. it('should return the lines, rev, version and ranges', function (ctx) {
  440. expect(result.lines).to.equal(ctx.lines)
  441. expect(result.rev).to.equal(ctx.rev)
  442. expect(result.version).to.equal(ctx.version)
  443. expect(result.ranges).to.equal(ctx.ranges)
  444. })
  445. })
  446. })