ProjectRootDocManager.test.mjs 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696
  1. import { vi, expect } from 'vitest'
  2. import mongodb from 'mongodb-legacy'
  3. import sinon from 'sinon'
  4. const modulePath =
  5. '../../../../app/src/Features/Project/ProjectRootDocManager.mjs'
  6. const { ObjectId } = mongodb
  7. describe('ProjectRootDocManager', function () {
  8. beforeEach(async function (ctx) {
  9. ctx.project_id = 'project-123'
  10. ctx.docPaths = {}
  11. ctx.docId1 = new ObjectId()
  12. ctx.docId2 = new ObjectId()
  13. ctx.docId3 = new ObjectId()
  14. ctx.docId4 = new ObjectId()
  15. ctx.docPaths[ctx.docId1] = '/chapter1.tex'
  16. ctx.docPaths[ctx.docId2] = '/main.tex'
  17. ctx.docPaths[ctx.docId3] = '/nested/chapter1a.tex'
  18. ctx.docPaths[ctx.docId4] = '/nested/chapter1b.tex'
  19. ctx.sl_req_id = 'sl-req-id-123'
  20. ctx.globbyFiles = ['a.tex', 'b.tex', 'main.tex']
  21. ctx.globby = sinon.stub().resolves(ctx.globbyFiles)
  22. ctx.fs = {
  23. promises: {
  24. readFile: sinon.stub().rejects(new Error('file not found')),
  25. stat: sinon.stub().resolves({ size: 100 }),
  26. },
  27. }
  28. ctx.ProjectEntityHandler = {
  29. promises: {},
  30. }
  31. ctx.ProjectEntityUpdateHandler = {
  32. promises: {},
  33. }
  34. ctx.ProjectGetter = {
  35. promises: {},
  36. }
  37. vi.doMock(
  38. '../../../../app/src/Features/Project/ProjectEntityHandler',
  39. () => ({
  40. default: ctx.ProjectEntityHandler,
  41. })
  42. )
  43. vi.doMock(
  44. '../../../../app/src/Features/Project/ProjectEntityUpdateHandler',
  45. () => ({
  46. default: ctx.ProjectEntityUpdateHandler,
  47. })
  48. )
  49. vi.doMock('../../../../app/src/Features/Project/ProjectGetter', () => ({
  50. default: ctx.ProjectGetter,
  51. }))
  52. vi.doMock('../../../../app/src/infrastructure/GracefulShutdown', () => ({
  53. BackgroundTaskTracker: class {
  54. add() {}
  55. done() {}
  56. },
  57. }))
  58. vi.doMock('globby', () => ({
  59. default: ctx.globby,
  60. }))
  61. vi.doMock('fs', () => ({
  62. default: ctx.fs,
  63. }))
  64. ctx.ProjectRootDocManager = (await import(modulePath)).default
  65. })
  66. describe('setRootDocAutomatically', function () {
  67. beforeEach(function (ctx) {
  68. ctx.ProjectEntityUpdateHandler.promises.setRootDoc = sinon
  69. .stub()
  70. .resolves()
  71. ctx.ProjectEntityUpdateHandler.isPathValidForRootDoc = sinon
  72. .stub()
  73. .returns(true)
  74. })
  75. describe('when there is a suitable root doc', function () {
  76. beforeEach(async function (ctx) {
  77. ctx.docs = {
  78. '/chapter1.tex': {
  79. _id: ctx.docId1,
  80. lines: [
  81. 'something else',
  82. '\\begin{document}',
  83. 'Hello world',
  84. '\\end{document}',
  85. ],
  86. },
  87. '/main.tex': {
  88. _id: ctx.docId2,
  89. lines: [
  90. 'different line',
  91. '\\documentclass{article}',
  92. '\\input{chapter1}',
  93. ],
  94. },
  95. '/nested/chapter1a.tex': {
  96. _id: ctx.docId3,
  97. lines: ['Hello world'],
  98. },
  99. '/nested/chapter1b.tex': {
  100. _id: ctx.docId4,
  101. lines: ['Hello world'],
  102. },
  103. }
  104. ctx.ProjectEntityHandler.promises.getAllDocs = sinon
  105. .stub()
  106. .resolves(ctx.docs)
  107. await ctx.ProjectRootDocManager.promises.setRootDocAutomatically(
  108. ctx.project_id
  109. )
  110. })
  111. it('should check the docs of the project', function (ctx) {
  112. ctx.ProjectEntityHandler.promises.getAllDocs
  113. .calledWith(ctx.project_id)
  114. .should.equal(true)
  115. })
  116. it('should set the root doc to the doc containing a documentclass', function (ctx) {
  117. ctx.ProjectEntityUpdateHandler.promises.setRootDoc
  118. .calledWith(ctx.project_id, ctx.docId2)
  119. .should.equal(true)
  120. })
  121. })
  122. describe('when the root doc is an Rtex file', function () {
  123. beforeEach(async function (ctx) {
  124. ctx.docs = {
  125. '/chapter1.tex': {
  126. _id: ctx.docId1,
  127. lines: ['\\begin{document}', 'Hello world', '\\end{document}'],
  128. },
  129. '/main.Rtex': {
  130. _id: ctx.docId2,
  131. lines: ['\\documentclass{article}', '\\input{chapter1}'],
  132. },
  133. }
  134. ctx.ProjectEntityHandler.promises.getAllDocs = sinon
  135. .stub()
  136. .resolves(ctx.docs)
  137. await ctx.ProjectRootDocManager.promises.setRootDocAutomatically(
  138. ctx.project_id
  139. )
  140. })
  141. it('should set the root doc to the doc containing a documentclass', function (ctx) {
  142. ctx.ProjectEntityUpdateHandler.promises.setRootDoc
  143. .calledWith(ctx.project_id, ctx.docId2)
  144. .should.equal(true)
  145. })
  146. })
  147. describe('when there is no suitable root doc', function () {
  148. beforeEach(async function (ctx) {
  149. ctx.docs = {
  150. '/chapter1.tex': {
  151. _id: ctx.docId1,
  152. lines: ['\\begin{document}', 'Hello world', '\\end{document}'],
  153. },
  154. '/style.bst': {
  155. _id: ctx.docId2,
  156. lines: ['%Example: \\documentclass{article}'],
  157. },
  158. }
  159. ctx.ProjectEntityHandler.promises.getAllDocs = sinon
  160. .stub()
  161. .resolves(ctx.docs)
  162. await ctx.ProjectRootDocManager.promises.setRootDocAutomatically(
  163. ctx.project_id
  164. )
  165. })
  166. it('should not set the root doc to the doc containing a documentclass', function (ctx) {
  167. ctx.ProjectEntityUpdateHandler.promises.setRootDoc.called.should.equal(
  168. false
  169. )
  170. })
  171. })
  172. })
  173. describe('findRootDocFileFromDirectory', function () {
  174. beforeEach(function (ctx) {
  175. ctx.fs.promises.readFile.withArgs('/foo/a.tex').resolves('Hello World!')
  176. ctx.fs.promises.readFile
  177. .withArgs('/foo/b.tex')
  178. .resolves("I'm a little teapot, get me out of here.")
  179. ctx.fs.promises.readFile
  180. .withArgs('/foo/main.tex')
  181. .resolves("Help, I'm trapped in a unit testing factory")
  182. ctx.fs.promises.readFile
  183. .withArgs('/foo/c.tex')
  184. .resolves('Tomato, tomahto.')
  185. ctx.fs.promises.readFile
  186. .withArgs('/foo/a/a.tex')
  187. .resolves('Potato? Potahto. Potootee!')
  188. ctx.documentclassContent = '% test\n\\documentclass\n% test'
  189. })
  190. describe('when there is a file in a subfolder', function () {
  191. beforeEach(function (ctx) {
  192. // have to splice globbyFiles weirdly because of the way the stubbed globby method handles references
  193. ctx.globbyFiles.splice(
  194. 0,
  195. ctx.globbyFiles.length,
  196. 'c.tex',
  197. 'a.tex',
  198. 'a/a.tex',
  199. 'b.tex'
  200. )
  201. })
  202. it('processes the root folder files first, and then the subfolder, in alphabetical order', async function (ctx) {
  203. const { path } =
  204. await ctx.ProjectRootDocManager.promises.findRootDocFileFromDirectory(
  205. '/foo'
  206. )
  207. expect(path).to.equal('a.tex')
  208. sinon.assert.callOrder(
  209. ctx.fs.promises.readFile.withArgs('/foo/a.tex'),
  210. ctx.fs.promises.readFile.withArgs('/foo/b.tex'),
  211. ctx.fs.promises.readFile.withArgs('/foo/c.tex'),
  212. ctx.fs.promises.readFile.withArgs('/foo/a/a.tex')
  213. )
  214. })
  215. it('processes smaller files first', async function (ctx) {
  216. ctx.fs.promises.stat.withArgs('/foo/c.tex').resolves({ size: 1 })
  217. const { path } =
  218. await ctx.ProjectRootDocManager.promises.findRootDocFileFromDirectory(
  219. '/foo'
  220. )
  221. expect(path).to.equal('c.tex')
  222. sinon.assert.callOrder(
  223. ctx.fs.promises.readFile.withArgs('/foo/c.tex'),
  224. ctx.fs.promises.readFile.withArgs('/foo/a.tex'),
  225. ctx.fs.promises.readFile.withArgs('/foo/b.tex'),
  226. ctx.fs.promises.readFile.withArgs('/foo/a/a.tex')
  227. )
  228. })
  229. })
  230. describe('when main.tex contains a documentclass', function () {
  231. beforeEach(function (ctx) {
  232. ctx.fs.promises.readFile
  233. .withArgs('/foo/main.tex')
  234. .resolves(ctx.documentclassContent)
  235. })
  236. it('returns main.tex', async function (ctx) {
  237. const { path, content } =
  238. await ctx.ProjectRootDocManager.promises.findRootDocFileFromDirectory(
  239. '/foo'
  240. )
  241. expect(path).to.equal('main.tex')
  242. expect(content).to.equal(ctx.documentclassContent)
  243. })
  244. it('processes main.text first and stops processing when it finds the content', async function (ctx) {
  245. await ctx.ProjectRootDocManager.promises.findRootDocFileFromDirectory(
  246. '/foo'
  247. )
  248. expect(ctx.fs.promises.readFile).to.be.calledWith('/foo/main.tex')
  249. expect(ctx.fs.promises.readFile).not.to.be.calledWith('/foo/a.tex')
  250. })
  251. })
  252. describe('when main.tex does not contain a line starting with \\documentclass', function () {
  253. beforeEach(function (ctx) {
  254. ctx.fs.promises.readFile.withArgs('/foo/a.tex').resolves('foo')
  255. ctx.fs.promises.readFile.withArgs('/foo/main.tex').resolves('foo')
  256. ctx.fs.promises.readFile.withArgs('/foo/z.tex').resolves('foo')
  257. ctx.fs.promises.readFile
  258. .withArgs('/foo/nested/chapter1a.tex')
  259. .resolves('foo')
  260. })
  261. it('returns the first .tex file from the root folder', async function (ctx) {
  262. ctx.globbyFiles.splice(
  263. 0,
  264. ctx.globbyFiles.length,
  265. 'a.tex',
  266. 'z.tex',
  267. 'nested/chapter1a.tex'
  268. )
  269. const { path, content } =
  270. await ctx.ProjectRootDocManager.promises.findRootDocFileFromDirectory(
  271. '/foo'
  272. )
  273. expect(path).to.equal('a.tex')
  274. expect(content).to.equal('foo')
  275. })
  276. it('returns main.tex file from the root folder', async function (ctx) {
  277. ctx.globbyFiles.splice(
  278. 0,
  279. ctx.globbyFiles.length,
  280. 'a.tex',
  281. 'z.tex',
  282. 'main.tex',
  283. 'nested/chapter1a.tex'
  284. )
  285. const { path, content } =
  286. await ctx.ProjectRootDocManager.promises.findRootDocFileFromDirectory(
  287. '/foo'
  288. )
  289. expect(path).to.equal('main.tex')
  290. expect(content).to.equal('foo')
  291. })
  292. })
  293. describe('when a.tex contains a documentclass', function () {
  294. beforeEach(function (ctx) {
  295. ctx.fs.promises.readFile
  296. .withArgs('/foo/a.tex')
  297. .resolves(ctx.documentclassContent)
  298. })
  299. it('returns a.tex', async function (ctx) {
  300. const { path, content } =
  301. await ctx.ProjectRootDocManager.promises.findRootDocFileFromDirectory(
  302. '/foo'
  303. )
  304. expect(path).to.equal('a.tex')
  305. expect(content).to.equal(ctx.documentclassContent)
  306. })
  307. it('processes main.text first and stops processing when it finds the content', async function (ctx) {
  308. await ctx.ProjectRootDocManager.promises.findRootDocFileFromDirectory(
  309. '/foo'
  310. )
  311. expect(ctx.fs.promises.readFile).to.be.calledWith('/foo/main.tex')
  312. expect(ctx.fs.promises.readFile).to.be.calledWith('/foo/a.tex')
  313. expect(ctx.fs.promises.readFile).not.to.be.calledWith('/foo/b.tex')
  314. })
  315. })
  316. describe('when there is no documentclass', function () {
  317. it('returns with no error', async function (ctx) {
  318. await ctx.ProjectRootDocManager.promises.findRootDocFileFromDirectory(
  319. '/foo'
  320. )
  321. })
  322. it('processes all the files', async function (ctx) {
  323. await ctx.ProjectRootDocManager.promises.findRootDocFileFromDirectory(
  324. '/foo'
  325. )
  326. expect(ctx.fs.promises.readFile).to.be.calledWith('/foo/main.tex')
  327. expect(ctx.fs.promises.readFile).to.be.calledWith('/foo/a.tex')
  328. expect(ctx.fs.promises.readFile).to.be.calledWith('/foo/b.tex')
  329. })
  330. })
  331. describe('when there is an error reading a file', function () {
  332. beforeEach(function (ctx) {
  333. ctx.fs.promises.readFile
  334. .withArgs('/foo/a.tex')
  335. .rejects(new Error('something went wrong'))
  336. })
  337. it('returns an error', async function (ctx) {
  338. let error
  339. try {
  340. await ctx.ProjectRootDocManager.promises.findRootDocFileFromDirectory(
  341. '/foo'
  342. )
  343. } catch (err) {
  344. error = err
  345. }
  346. expect(error).to.exist
  347. })
  348. })
  349. })
  350. describe('setRootDocFromName', function () {
  351. describe('when there is a suitable root doc', function () {
  352. beforeEach(async function (ctx) {
  353. ctx.ProjectEntityHandler.promises.getAllDocPathsFromProjectById = sinon
  354. .stub()
  355. .resolves(ctx.docPaths)
  356. ctx.ProjectEntityUpdateHandler.promises.setRootDoc = sinon
  357. .stub()
  358. .resolves()
  359. await ctx.ProjectRootDocManager.promises.setRootDocFromName(
  360. ctx.project_id,
  361. '/main.tex'
  362. )
  363. })
  364. it('should check the docs of the project', function (ctx) {
  365. ctx.ProjectEntityHandler.promises.getAllDocPathsFromProjectById
  366. .calledWith(ctx.project_id)
  367. .should.equal(true)
  368. })
  369. it('should set the root doc to main.tex', function (ctx) {
  370. ctx.ProjectEntityUpdateHandler.promises.setRootDoc
  371. .calledWith(ctx.project_id, ctx.docId2.toString())
  372. .should.equal(true)
  373. })
  374. })
  375. describe('when there is a suitable root doc but the leading slash is missing', function () {
  376. beforeEach(async function (ctx) {
  377. ctx.ProjectEntityHandler.promises.getAllDocPathsFromProjectById = sinon
  378. .stub()
  379. .resolves(ctx.docPaths)
  380. ctx.ProjectEntityUpdateHandler.promises.setRootDoc = sinon
  381. .stub()
  382. .resolves()
  383. await ctx.ProjectRootDocManager.promises.setRootDocFromName(
  384. ctx.project_id,
  385. 'main.tex'
  386. )
  387. })
  388. it('should check the docs of the project', function (ctx) {
  389. ctx.ProjectEntityHandler.promises.getAllDocPathsFromProjectById
  390. .calledWith(ctx.project_id)
  391. .should.equal(true)
  392. })
  393. it('should set the root doc to main.tex', function (ctx) {
  394. ctx.ProjectEntityUpdateHandler.promises.setRootDoc
  395. .calledWith(ctx.project_id, ctx.docId2.toString())
  396. .should.equal(true)
  397. })
  398. })
  399. describe('when there is a suitable root doc with a basename match', function () {
  400. beforeEach(async function (ctx) {
  401. ctx.ProjectEntityHandler.promises.getAllDocPathsFromProjectById = sinon
  402. .stub()
  403. .resolves(ctx.docPaths)
  404. ctx.ProjectEntityUpdateHandler.promises.setRootDoc = sinon
  405. .stub()
  406. .resolves()
  407. await ctx.ProjectRootDocManager.promises.setRootDocFromName(
  408. ctx.project_id,
  409. 'chapter1a.tex'
  410. )
  411. })
  412. it('should check the docs of the project', function (ctx) {
  413. ctx.ProjectEntityHandler.promises.getAllDocPathsFromProjectById
  414. .calledWith(ctx.project_id)
  415. .should.equal(true)
  416. })
  417. it('should set the root doc using the basename', function (ctx) {
  418. ctx.ProjectEntityUpdateHandler.promises.setRootDoc
  419. .calledWith(ctx.project_id, ctx.docId3.toString())
  420. .should.equal(true)
  421. })
  422. })
  423. describe('when there is a suitable root doc but the filename is in quotes', function () {
  424. beforeEach(async function (ctx) {
  425. ctx.ProjectEntityHandler.promises.getAllDocPathsFromProjectById = sinon
  426. .stub()
  427. .resolves(ctx.docPaths)
  428. ctx.ProjectEntityUpdateHandler.promises.setRootDoc = sinon
  429. .stub()
  430. .resolves()
  431. await ctx.ProjectRootDocManager.promises.setRootDocFromName(
  432. ctx.project_id,
  433. "'main.tex'"
  434. )
  435. })
  436. it('should check the docs of the project', function (ctx) {
  437. ctx.ProjectEntityHandler.promises.getAllDocPathsFromProjectById
  438. .calledWith(ctx.project_id)
  439. .should.equal(true)
  440. })
  441. it('should set the root doc to main.tex', function (ctx) {
  442. ctx.ProjectEntityUpdateHandler.promises.setRootDoc
  443. .calledWith(ctx.project_id, ctx.docId2.toString())
  444. .should.equal(true)
  445. })
  446. })
  447. describe('when there is no suitable root doc', function () {
  448. beforeEach(async function (ctx) {
  449. ctx.ProjectEntityHandler.promises.getAllDocPathsFromProjectById = sinon
  450. .stub()
  451. .resolves(ctx.docPaths)
  452. ctx.ProjectEntityUpdateHandler.promises.setRootDoc = sinon
  453. .stub()
  454. .resolves()
  455. await ctx.ProjectRootDocManager.promises.setRootDocFromName(
  456. ctx.project_id,
  457. 'other.tex'
  458. )
  459. })
  460. it('should not set the root doc', function (ctx) {
  461. ctx.ProjectEntityUpdateHandler.promises.setRootDoc.called.should.equal(
  462. false
  463. )
  464. })
  465. })
  466. })
  467. describe('ensureRootDocumentIsSet', function () {
  468. beforeEach(function (ctx) {
  469. ctx.project = {}
  470. ctx.ProjectGetter.promises.getProject = sinon.stub().resolves(ctx.project)
  471. ctx.ProjectRootDocManager.promises.setRootDocAutomatically = sinon
  472. .stub()
  473. .resolves()
  474. })
  475. describe('when the root doc is set', function () {
  476. beforeEach(async function (ctx) {
  477. ctx.project.rootDoc_id = ctx.docId2
  478. await ctx.ProjectRootDocManager.promises.ensureRootDocumentIsSet(
  479. ctx.project_id
  480. )
  481. })
  482. it('should find the project fetching only the rootDoc_id field', function (ctx) {
  483. ctx.ProjectGetter.promises.getProject
  484. .calledWith(ctx.project_id, { rootDoc_id: 1 })
  485. .should.equal(true)
  486. })
  487. it('should not try to update the project rootDoc_id', function (ctx) {
  488. ctx.ProjectRootDocManager.promises.setRootDocAutomatically.called.should.equal(
  489. false
  490. )
  491. })
  492. })
  493. describe('when the root doc is not set', function () {
  494. beforeEach(async function (ctx) {
  495. await ctx.ProjectRootDocManager.promises.ensureRootDocumentIsSet(
  496. ctx.project_id
  497. )
  498. })
  499. it('should find the project with only the rootDoc_id field', function (ctx) {
  500. ctx.ProjectGetter.promises.getProject
  501. .calledWith(ctx.project_id, { rootDoc_id: 1 })
  502. .should.equal(true)
  503. })
  504. it('should update the project rootDoc_id', function (ctx) {
  505. ctx.ProjectRootDocManager.promises.setRootDocAutomatically
  506. .calledWith(ctx.project_id)
  507. .should.equal(true)
  508. })
  509. })
  510. describe('when the project does not exist', function () {
  511. beforeEach(function (ctx) {
  512. ctx.ProjectGetter.promises.getProject = sinon.stub().resolves(null)
  513. })
  514. it('should reject', async function (ctx) {
  515. await expect(
  516. ctx.ProjectRootDocManager.promises.ensureRootDocumentIsSet(
  517. ctx.project_id
  518. )
  519. ).to.be.rejectedWith('project not found')
  520. })
  521. })
  522. })
  523. describe('ensureRootDocumentIsValid', function () {
  524. beforeEach(function (ctx) {
  525. ctx.project = {}
  526. ctx.ProjectGetter.promises.getProject = sinon.stub().resolves(ctx.project)
  527. ctx.ProjectGetter.promises.getProjectWithoutDocLines = sinon
  528. .stub()
  529. .resolves(ctx.project)
  530. ctx.ProjectEntityUpdateHandler.promises.setRootDoc = sinon
  531. .stub()
  532. .resolves()
  533. ctx.ProjectEntityUpdateHandler.promises.unsetRootDoc = sinon
  534. .stub()
  535. .resolves()
  536. ctx.ProjectRootDocManager.promises.setRootDocAutomatically = sinon
  537. .stub()
  538. .resolves(null)
  539. })
  540. describe('when the root doc is set', function () {
  541. describe('when the root doc is valid', function () {
  542. beforeEach(async function (ctx) {
  543. ctx.project.rootDoc_id = ctx.docId2
  544. ctx.ProjectEntityHandler.promises.getDocPathFromProjectByDocId = sinon
  545. .stub()
  546. .resolves(ctx.docPaths[ctx.docId2])
  547. await ctx.ProjectRootDocManager.promises.ensureRootDocumentIsValid(
  548. ctx.project_id
  549. )
  550. })
  551. it('should find the project without doc lines', function (ctx) {
  552. ctx.ProjectGetter.promises.getProjectWithoutDocLines
  553. .calledWith(ctx.project_id)
  554. .should.equal(true)
  555. })
  556. it('should not try to update the project rootDoc_id', function (ctx) {
  557. ctx.ProjectRootDocManager.promises.setRootDocAutomatically.called.should.equal(
  558. false
  559. )
  560. })
  561. })
  562. describe('when the root doc is not valid', function () {
  563. beforeEach(async function (ctx) {
  564. ctx.project.rootDoc_id = new ObjectId()
  565. ctx.ProjectEntityHandler.promises.getDocPathFromProjectByDocId = sinon
  566. .stub()
  567. .resolves(null)
  568. await ctx.ProjectRootDocManager.promises.ensureRootDocumentIsValid(
  569. ctx.project_id
  570. )
  571. })
  572. it('should find the project without doc lines', function (ctx) {
  573. ctx.ProjectGetter.promises.getProjectWithoutDocLines
  574. .calledWith(ctx.project_id)
  575. .should.equal(true)
  576. })
  577. it('should unset the root doc', function (ctx) {
  578. ctx.ProjectEntityUpdateHandler.promises.unsetRootDoc
  579. .calledWith(ctx.project_id)
  580. .should.equal(true)
  581. })
  582. it('should try to find a new rootDoc', function (ctx) {
  583. ctx.ProjectRootDocManager.promises.setRootDocAutomatically.called.should.equal(
  584. true
  585. )
  586. })
  587. })
  588. })
  589. describe('when the root doc is not set', function () {
  590. beforeEach(async function (ctx) {
  591. await ctx.ProjectRootDocManager.promises.ensureRootDocumentIsValid(
  592. ctx.project_id
  593. )
  594. })
  595. it('should find the project without doc lines', async function (ctx) {
  596. ctx.ProjectGetter.promises.getProjectWithoutDocLines
  597. .calledWith(ctx.project_id)
  598. .should.equal(true)
  599. })
  600. it('should update the project rootDoc_id', function (ctx) {
  601. ctx.ProjectRootDocManager.promises.setRootDocAutomatically
  602. .calledWith(ctx.project_id)
  603. .should.equal(true)
  604. })
  605. })
  606. describe('when the project does not exist', function () {
  607. beforeEach(async function (ctx) {
  608. ctx.ProjectGetter.promises.getProjectWithoutDocLines = sinon
  609. .stub()
  610. .resolves(null)
  611. })
  612. it('should reject', async function (ctx) {
  613. await expect(
  614. ctx.ProjectRootDocManager.promises.ensureRootDocumentIsValid(
  615. ctx.project_id
  616. )
  617. ).to.be.rejectedWith('project not found')
  618. })
  619. })
  620. })
  621. })