ProjectEntityHandlerTests.js 15 KB

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