ProjectEntityHandler.test.mjs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497
  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.getProjectWithoutDocLines = sinon
  97. .stub()
  98. .resolves(ctx.project)
  99. })
  100. describe('getAllDocs', function () {
  101. let fetchedDocs
  102. beforeEach(async function (ctx) {
  103. ctx.docs = [
  104. {
  105. _id: ctx.doc1._id,
  106. lines: (ctx.lines1 = ['one']),
  107. rev: (ctx.rev1 = 1),
  108. },
  109. {
  110. _id: ctx.doc2._id,
  111. lines: (ctx.lines2 = ['two']),
  112. rev: (ctx.rev2 = 2),
  113. },
  114. ]
  115. ctx.DocstoreManager.promises.getAllDocs = sinon
  116. .stub()
  117. .resolves(ctx.docs)
  118. fetchedDocs =
  119. await ctx.ProjectEntityHandler.promises.getAllDocs(projectId)
  120. })
  121. it('should get the doc lines and rev from the docstore', function (ctx) {
  122. ctx.DocstoreManager.promises.getAllDocs
  123. .calledWith(projectId)
  124. .should.equal(true)
  125. })
  126. it('should call the callback with the docs with the lines and rev included', function (ctx) {
  127. expect(fetchedDocs).to.deep.equal({
  128. '/doc1': {
  129. _id: ctx.doc1._id,
  130. lines: ctx.lines1,
  131. name: ctx.doc1.name,
  132. rev: ctx.rev1,
  133. folder: ctx.project.rootFolder[0],
  134. },
  135. '/folder1/doc2': {
  136. _id: ctx.doc2._id,
  137. lines: ctx.lines2,
  138. name: ctx.doc2.name,
  139. rev: ctx.rev2,
  140. folder: ctx.folder1,
  141. },
  142. })
  143. })
  144. })
  145. describe('getAllFiles', function () {
  146. let allFiles
  147. beforeEach(async function (ctx) {
  148. ctx.callback = sinon.stub()
  149. allFiles = await ctx.ProjectEntityHandler.promises.getAllFiles(
  150. projectId,
  151. ctx.callback
  152. )
  153. })
  154. it('should call the callback with the files', function (ctx) {
  155. expect(allFiles).to.deep.equal({
  156. '/file1': { ...ctx.file1, folder: ctx.project.rootFolder[0] },
  157. '/folder1/file2': { ...ctx.file2, folder: ctx.folder1 },
  158. })
  159. })
  160. })
  161. describe('getAllDocPathsFromProject', function () {
  162. beforeEach(function (ctx) {
  163. ctx.docs = [
  164. {
  165. _id: ctx.doc1._id,
  166. lines: (ctx.lines1 = ['one']),
  167. rev: (ctx.rev1 = 1),
  168. },
  169. {
  170. _id: ctx.doc2._id,
  171. lines: (ctx.lines2 = ['two']),
  172. rev: (ctx.rev2 = 2),
  173. },
  174. ]
  175. })
  176. it('should call the callback with the path for each docId', function (ctx) {
  177. const expected = {
  178. [ctx.doc1._id]: `/${ctx.doc1.name}`,
  179. [ctx.doc2._id]: `/folder1/${ctx.doc2.name}`,
  180. }
  181. expect(
  182. ctx.ProjectEntityHandler.getAllDocPathsFromProject(
  183. ctx.project,
  184. ctx.callback
  185. )
  186. ).to.deep.equal(expected)
  187. })
  188. })
  189. describe('getDocPathByProjectIdAndDocId', function () {
  190. it('should call the callback with the path for an existing doc id at the root level', async function (ctx) {
  191. const path =
  192. await ctx.ProjectEntityHandler.promises.getDocPathByProjectIdAndDocId(
  193. projectId,
  194. ctx.doc1._id
  195. )
  196. expect(path).to.deep.equal(`/${ctx.doc1.name}`)
  197. })
  198. it('should call the callback with the path for an existing doc id nested within a folder', async function (ctx) {
  199. const path =
  200. await ctx.ProjectEntityHandler.promises.getDocPathByProjectIdAndDocId(
  201. projectId,
  202. ctx.doc2._id
  203. )
  204. expect(path).to.deep.equal(`/folder1/${ctx.doc2.name}`)
  205. })
  206. it('should call the callback with a NotFoundError for a non-existing doc', async function (ctx) {
  207. await expect(
  208. ctx.ProjectEntityHandler.promises.getDocPathByProjectIdAndDocId(
  209. projectId,
  210. 'non-existing-id'
  211. )
  212. ).to.be.rejectedWith(Errors.NotFoundError)
  213. })
  214. it('should call the callback with a NotFoundError for an existing file', async function (ctx) {
  215. await expect(
  216. ctx.ProjectEntityHandler.promises.getDocPathByProjectIdAndDocId(
  217. projectId,
  218. ctx.file1._id
  219. )
  220. ).to.be.rejectedWith(Errors.NotFoundError)
  221. })
  222. })
  223. describe('_getAllFolders', async function () {
  224. let folders
  225. beforeEach(async function (ctx) {
  226. ctx.callback = sinon.stub()
  227. folders =
  228. await ctx.ProjectEntityHandler.promises._getAllFolders(projectId)
  229. })
  230. it('should get the project without the docs lines', function (ctx) {
  231. ctx.ProjectGetter.promises.getProjectWithoutDocLines
  232. .calledWith(projectId)
  233. .should.equal(true)
  234. })
  235. it('should call the callback with the folders', function (ctx) {
  236. expect(folders).to.deep.equal([
  237. { path: '/', folder: ctx.project.rootFolder[0] },
  238. { path: '/folder1', folder: ctx.folder1 },
  239. ])
  240. })
  241. })
  242. describe('_getAllFoldersFromProject', function () {
  243. it('should return the folders', function (ctx) {
  244. expect(
  245. ctx.ProjectEntityHandler._getAllFoldersFromProject(ctx.project)
  246. ).to.deep.equal([
  247. { path: '/', folder: ctx.project.rootFolder[0] },
  248. { path: '/folder1', folder: ctx.folder1 },
  249. ])
  250. })
  251. })
  252. })
  253. describe('with an invalid file tree', function () {
  254. beforeEach(function (ctx) {
  255. ctx.project.rootFolder = [
  256. {
  257. docs: [
  258. (ctx.doc1 = {
  259. name: null, // invalid doc name
  260. _id: 'doc1_id',
  261. }),
  262. ],
  263. fileRefs: [
  264. (ctx.file1 = {
  265. rev: 1,
  266. _id: 'file1_id',
  267. name: null, // invalid file name
  268. }),
  269. ],
  270. folders: [
  271. (ctx.folder1 = {
  272. name: null, // invalid folder name
  273. docs: [
  274. (ctx.doc2 = {
  275. name: 'doc2',
  276. _id: 'doc2_id',
  277. }),
  278. ],
  279. fileRefs: [
  280. (ctx.file2 = {
  281. rev: 2,
  282. name: 'file2',
  283. _id: 'file2_id',
  284. }),
  285. ],
  286. folders: null,
  287. }),
  288. null, // invalid folder
  289. ],
  290. },
  291. ]
  292. ctx.ProjectGetter.promises.getProjectWithoutDocLines = sinon
  293. .stub()
  294. .resolves(ctx.project)
  295. })
  296. describe('getAllDocs', function () {
  297. beforeEach(async function (ctx) {
  298. ctx.docs = [
  299. {
  300. _id: ctx.doc1._id,
  301. lines: (ctx.lines1 = ['one']),
  302. rev: (ctx.rev1 = 1),
  303. },
  304. {
  305. _id: ctx.doc2._id,
  306. lines: (ctx.lines2 = ['two']),
  307. rev: (ctx.rev2 = 2),
  308. },
  309. ]
  310. ctx.DocstoreManager.promises.getAllDocs = sinon
  311. .stub()
  312. .resolves(ctx.docs)
  313. })
  314. it('should call the callback with an error', async function (ctx) {
  315. await expect(ctx.ProjectEntityHandler.promises.getAllDocs(projectId)).to
  316. .be.rejected
  317. })
  318. })
  319. describe('getAllFiles', function () {
  320. it('should call the callback with and error', async function (ctx) {
  321. await expect(ctx.ProjectEntityHandler.promises.getAllFiles(projectId))
  322. .to.be.rejected
  323. })
  324. })
  325. describe('getDocPathByProjectIdAndDocId', function () {
  326. it('should call the callback with an error for an existing doc id at the root level', async function (ctx) {
  327. await expect(
  328. ctx.ProjectEntityHandler.promises.getDocPathByProjectIdAndDocId(
  329. projectId,
  330. ctx.doc1._id
  331. )
  332. ).to.be.rejectedWith(Error)
  333. })
  334. it('should call the callback with an error for an existing doc id nested within a folder', async function (ctx) {
  335. await expect(
  336. ctx.ProjectEntityHandler.promises.getDocPathByProjectIdAndDocId(
  337. projectId,
  338. ctx.doc2._id
  339. )
  340. ).to.be.rejectedWith(Error)
  341. })
  342. it('should call the callback with an error for a non-existing doc', async function (ctx) {
  343. await expect(
  344. ctx.ProjectEntityHandler.promises.getDocPathByProjectIdAndDocId(
  345. projectId,
  346. 'non-existing-id'
  347. )
  348. ).to.be.rejectedWith(Error)
  349. })
  350. it('should call the callback with an error for an existing file', async function (ctx) {
  351. await expect(
  352. ctx.ProjectEntityHandler.promises.getDocPathByProjectIdAndDocId(
  353. projectId,
  354. ctx.file1._id
  355. )
  356. ).to.be.rejectedWith(Error)
  357. })
  358. })
  359. describe('_getAllFolders', function () {
  360. it('should call the callback with an error', async function (ctx) {
  361. await expect(
  362. ctx.ProjectEntityHandler.promises._getAllFolders(projectId)
  363. ).to.be.rejected
  364. })
  365. })
  366. describe('getAllEntities', function () {
  367. beforeEach(function (ctx) {
  368. ctx.ProjectGetter.promises.getProject = sinon
  369. .stub()
  370. .resolves(ctx.project)
  371. })
  372. it('should call the callback with an error', async function (ctx) {
  373. await expect(
  374. ctx.ProjectEntityHandler.promises.getAllEntities(projectId)
  375. ).to.be.rejected
  376. })
  377. })
  378. describe('getAllDocPathsFromProjectById', function () {
  379. it('should call the callback with an error', async function (ctx) {
  380. await expect(
  381. ctx.ProjectEntityHandler.promises.getAllDocPathsFromProjectById(
  382. projectId
  383. )
  384. ).to.be.rejected
  385. })
  386. })
  387. describe('getDocPathFromProjectByDocId', function () {
  388. it('should call the callback with an error', async function (ctx) {
  389. await expect(
  390. ctx.ProjectEntityHandler.promises.getDocPathFromProjectByDocId(
  391. projectId,
  392. ctx.doc1._id
  393. )
  394. ).to.be.rejected
  395. })
  396. })
  397. })
  398. describe('getDoc', function () {
  399. beforeEach(function (ctx) {
  400. ctx.lines = ['mock', 'doc', 'lines']
  401. ctx.rev = 5
  402. ctx.version = 42
  403. ctx.ranges = { mock: 'ranges' }
  404. ctx.callback = sinon.stub()
  405. ctx.DocstoreManager.promises.getDoc = sinon.stub().resolves({
  406. lines: ctx.lines,
  407. rev: ctx.rev,
  408. version: ctx.version,
  409. ranges: ctx.ranges,
  410. })
  411. })
  412. it('should call the callback with the lines, version and rev', async function (ctx) {
  413. const doc = await ctx.ProjectEntityHandler.promises.getDoc(
  414. projectId,
  415. docId
  416. )
  417. ctx.DocstoreManager.promises.getDoc
  418. .calledWith(projectId, docId)
  419. .should.equal(true)
  420. expect(doc).to.exist
  421. })
  422. })
  423. describe('promises.getDoc', function () {
  424. let result
  425. beforeEach(async function (ctx) {
  426. ctx.lines = ['mock', 'doc', 'lines']
  427. ctx.rev = 5
  428. ctx.version = 42
  429. ctx.ranges = { mock: 'ranges' }
  430. ctx.DocstoreManager.promises.getDoc = sinon.stub().resolves({
  431. lines: ctx.lines,
  432. rev: ctx.rev,
  433. version: ctx.version,
  434. ranges: ctx.ranges,
  435. })
  436. result = await ctx.ProjectEntityHandler.promises.getDoc(projectId, docId)
  437. })
  438. it('should call the docstore', function (ctx) {
  439. ctx.DocstoreManager.promises.getDoc
  440. .calledWith(projectId, docId)
  441. .should.equal(true)
  442. })
  443. it('should return the lines, rev, version and ranges', function (ctx) {
  444. expect(result.lines).to.equal(ctx.lines)
  445. expect(result.rev).to.equal(ctx.rev)
  446. expect(result.version).to.equal(ctx.version)
  447. expect(result.ranges).to.equal(ctx.ranges)
  448. })
  449. })
  450. })