MalformedFiletreesTests.mjs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733
  1. import { exec } from 'node:child_process'
  2. import { promisify } from 'node:util'
  3. import { expect } from 'chai'
  4. import logger from '@overleaf/logger'
  5. import { filterOutput } from './helpers/settings.mjs'
  6. import { db, ObjectId } from '../../../app/src/infrastructure/mongodb.mjs'
  7. const lastUpdated = new Date(42)
  8. const lastUpdatedBy = new ObjectId()
  9. const lastUpdatedChanged = new Date(1337)
  10. async function runScriptFind() {
  11. try {
  12. const result = await promisify(exec)(
  13. ['node', 'scripts/find_malformed_filetrees.mjs'].join(' ')
  14. )
  15. return result.stdout.split('\n').filter(filterOutput)
  16. } catch (error) {
  17. logger.error({ error }, 'script failed')
  18. throw error
  19. }
  20. }
  21. async function runScriptFix(instructions) {
  22. const adhocFile = instructions.map(entry => JSON.stringify(entry)).join('\n')
  23. try {
  24. return await promisify(exec)(
  25. [
  26. 'node',
  27. 'scripts/fix_malformed_filetree.mjs',
  28. `--logs=<(echo '${adhocFile}')`,
  29. ].join(' '),
  30. { shell: '/bin/bash' }
  31. )
  32. } catch (error) {
  33. logger.error({ error }, 'fix script failed unexpectedly')
  34. throw error
  35. }
  36. }
  37. const findProjects = () =>
  38. db.projects
  39. .find(
  40. {},
  41. {
  42. projection: {
  43. rootFolder: 1,
  44. _id: 1,
  45. version: 1,
  46. lastUpdated: 1,
  47. lastUpdatedBy: 1,
  48. },
  49. }
  50. )
  51. .toArray()
  52. const projectId = new ObjectId()
  53. const rootFolderId = new ObjectId()
  54. const idDic = {}
  55. const id = key => {
  56. if (!idDic[key]) {
  57. idDic[key] = new ObjectId()
  58. }
  59. return idDic[key]
  60. }
  61. const strId = key => {
  62. return idDic[key].toString()
  63. }
  64. const wellFormedFolder = name => ({
  65. _id: id(name),
  66. name,
  67. folders: [],
  68. docs: [],
  69. fileRefs: [],
  70. })
  71. const wellFormedDoc = name => ({ _id: id(name), name })
  72. const wellFormedFileRef = name => ({ _id: id(name), name, hash: 'h' })
  73. const wellFormedProject = {
  74. _id: projectId,
  75. rootFolder: [
  76. {
  77. _id: rootFolderId,
  78. name: 'rootFolder',
  79. folders: [wellFormedFolder('f00'), wellFormedFolder('f01')],
  80. docs: [wellFormedDoc('d00'), wellFormedDoc('d01')],
  81. fileRefs: [wellFormedFileRef('fr00'), wellFormedFileRef('fr01')],
  82. },
  83. ],
  84. lastUpdated,
  85. lastUpdatedBy,
  86. }
  87. const testCases = [
  88. ...[{}, { rootFolder: undefined }, { rootFolder: '1234' }].map(
  89. (project, idx) => ({
  90. name: `bad rootFolder ${idx + 1}`,
  91. project: { _id: projectId, ...project, lastUpdated, lastUpdatedBy },
  92. expectFind: [
  93. {
  94. _id: null,
  95. projectId: projectId.toString(),
  96. msg: 'bad file-tree path',
  97. reason: 'bad rootFolder',
  98. path: 'rootFolder',
  99. },
  100. ],
  101. // FIXME: This is a bug in the script.
  102. expectFixError: 'Unexpected mongo path: rootFolder',
  103. })
  104. ),
  105. {
  106. name: `missing rootFolder`,
  107. project: { _id: projectId, rootFolder: [], lastUpdated, lastUpdatedBy },
  108. expectFind: [
  109. {
  110. _id: null,
  111. projectId: projectId.toString(),
  112. msg: 'bad file-tree path',
  113. reason: 'missing rootFolder',
  114. path: 'rootFolder.0',
  115. },
  116. ],
  117. expectFixStdout:
  118. '"gracefulShutdownInitiated":false,"processedLines":1,"success":1,"alreadyProcessed":0,"hash":0,"failed":0,"unmatched":0',
  119. expectProject: updatedProject => {
  120. expect(updatedProject.rootFolder[0]._id).to.be.an.instanceOf(ObjectId)
  121. expect(updatedProject).to.deep.equal({
  122. _id: projectId,
  123. rootFolder: [
  124. {
  125. _id: updatedProject.rootFolder[0]._id,
  126. name: 'rootFolder',
  127. folders: [],
  128. fileRefs: [],
  129. docs: [],
  130. },
  131. ],
  132. lastUpdated: lastUpdatedChanged,
  133. lastUpdatedBy: null,
  134. })
  135. },
  136. },
  137. {
  138. name: 'empty folder',
  139. project: {
  140. _id: projectId,
  141. rootFolder: [{ _id: '1234' }],
  142. lastUpdated,
  143. lastUpdatedBy,
  144. },
  145. expectFind: [
  146. { reason: 'bad folder id', path: 'rootFolder.0._id' },
  147. { reason: 'bad folder name', path: 'rootFolder.0.name' },
  148. { reason: 'missing .folders', path: 'rootFolder.0.folders' },
  149. { reason: 'missing .docs', path: 'rootFolder.0.docs' },
  150. { reason: 'missing .fileRefs', path: 'rootFolder.0.fileRefs' },
  151. ].map(entry => ({
  152. ...entry,
  153. _id: '1234',
  154. msg: 'bad file-tree path',
  155. projectId: String(projectId),
  156. })),
  157. // FIXME: This is a bug in the script.
  158. expectFixError: 'Unexpected mongo path: rootFolder.0._id',
  159. },
  160. {
  161. name: 'missing fields',
  162. project: {
  163. _id: projectId,
  164. rootFolder: [{ _id: rootFolderId }],
  165. lastUpdated,
  166. lastUpdatedBy,
  167. },
  168. expectFind: [
  169. { reason: 'bad folder name', path: 'rootFolder.0.name' },
  170. { reason: 'missing .folders', path: 'rootFolder.0.folders' },
  171. { reason: 'missing .docs', path: 'rootFolder.0.docs' },
  172. { reason: 'missing .fileRefs', path: 'rootFolder.0.fileRefs' },
  173. ].map(entry => ({
  174. ...entry,
  175. _id: rootFolderId.toString(),
  176. msg: 'bad file-tree path',
  177. projectId: String(projectId),
  178. })),
  179. expectFixStdout:
  180. '"gracefulShutdownInitiated":false,"processedLines":4,"success":4,"alreadyProcessed":0,"hash":0,"failed":0,"unmatched":0',
  181. expectProject: updatedProject => {
  182. expect(updatedProject).to.deep.equal({
  183. _id: projectId,
  184. rootFolder: [
  185. {
  186. _id: rootFolderId,
  187. docs: [],
  188. fileRefs: [],
  189. folders: [],
  190. name: 'rootFolder',
  191. },
  192. ],
  193. lastUpdated: lastUpdatedChanged,
  194. lastUpdatedBy: null,
  195. })
  196. },
  197. },
  198. {
  199. name: 'bad folder, bad doc, bad fileRef',
  200. project: {
  201. _id: projectId,
  202. rootFolder: [
  203. {
  204. _id: rootFolderId,
  205. name: 'rootFolder',
  206. folders: [null],
  207. docs: [null],
  208. fileRefs: [null, null],
  209. },
  210. ],
  211. lastUpdated,
  212. lastUpdatedBy,
  213. },
  214. expectFind: [
  215. {
  216. path: 'rootFolder.0.folders.0',
  217. reason: 'bad folder',
  218. },
  219. {
  220. path: 'rootFolder.0.docs.0',
  221. reason: 'bad doc',
  222. },
  223. {
  224. path: 'rootFolder.0.fileRefs.0',
  225. reason: 'bad file',
  226. },
  227. {
  228. path: 'rootFolder.0.fileRefs.1',
  229. reason: 'bad file',
  230. },
  231. ].map(entry => ({
  232. ...entry,
  233. _id: rootFolderId.toString(),
  234. projectId: projectId.toString(),
  235. msg: 'bad file-tree path',
  236. })),
  237. expectFixStdout:
  238. '"gracefulShutdownInitiated":false,"processedLines":4,"success":1,"alreadyProcessed":3,"hash":0,"failed":0,"unmatched":0',
  239. expectProject: updatedProject => {
  240. expect(updatedProject).to.deep.equal({
  241. _id: projectId,
  242. rootFolder: [
  243. {
  244. _id: rootFolderId,
  245. name: 'rootFolder',
  246. docs: [],
  247. fileRefs: [],
  248. folders: [],
  249. },
  250. ],
  251. lastUpdated: lastUpdatedChanged,
  252. lastUpdatedBy: null,
  253. })
  254. },
  255. },
  256. {
  257. name: 'bad [folder|doc|fileRef] id',
  258. project: {
  259. _id: projectId,
  260. rootFolder: [
  261. {
  262. _id: rootFolderId,
  263. name: 'rootFolder',
  264. folders: [
  265. { _id: 123, name: 'file-a', folders: [], docs: [], fileRefs: [] },
  266. { name: 'file-b', folders: [], docs: [], fileRefs: [] },
  267. ],
  268. docs: [{ _id: '456', name: 'doc-a' }, { name: 'doc-b' }],
  269. fileRefs: [{ _id: null, name: 'ref-a' }, { name: 'ref-b' }],
  270. },
  271. ],
  272. lastUpdated,
  273. lastUpdatedBy,
  274. },
  275. expectFind: [
  276. { reason: 'bad folder id', path: 'rootFolder.0.folders.0._id', _id: 123 },
  277. { reason: 'bad folder id', path: 'rootFolder.0.folders.1._id' },
  278. { reason: 'bad doc id', path: 'rootFolder.0.docs.0._id', _id: '456' },
  279. { reason: 'bad doc id', path: 'rootFolder.0.docs.1._id' },
  280. { reason: 'bad file id', path: 'rootFolder.0.fileRefs.0._id', _id: null },
  281. { reason: 'bad file id', path: 'rootFolder.0.fileRefs.1._id' },
  282. ].map(entry => ({
  283. ...entry,
  284. projectId: projectId.toString(),
  285. msg: 'bad file-tree path',
  286. })),
  287. expectFixStdout:
  288. '"gracefulShutdownInitiated":false,"processedLines":6,"success":3,"alreadyProcessed":3,"hash":0,"failed":0,"unmatched":0',
  289. expectProject: updatedProject => {
  290. expect(updatedProject).to.deep.equal({
  291. _id: projectId,
  292. rootFolder: [
  293. {
  294. _id: rootFolderId,
  295. name: 'rootFolder',
  296. folders: [
  297. { _id: 123, name: 'file-a', folders: [], docs: [], fileRefs: [] },
  298. {
  299. _id: updatedProject.rootFolder[0].folders[1]._id,
  300. name: 'file-b',
  301. folders: [],
  302. docs: [],
  303. fileRefs: [],
  304. },
  305. ],
  306. docs: [{ _id: '456', name: 'doc-a' }],
  307. fileRefs: [],
  308. },
  309. ],
  310. lastUpdated: lastUpdatedChanged,
  311. lastUpdatedBy: null,
  312. })
  313. },
  314. },
  315. {
  316. name: 'bad [folder|doc|fileRef] name',
  317. project: {
  318. _id: projectId,
  319. rootFolder: [
  320. {
  321. _id: rootFolderId,
  322. name: 'rootFolder',
  323. folders: [
  324. { _id: id('f00'), folders: [], docs: [], fileRefs: [] },
  325. { _id: id('f01'), name: 8, folders: [], docs: [], fileRefs: [] },
  326. ],
  327. docs: [{ _id: id('d00') }, { _id: id('d01'), name: null }],
  328. fileRefs: [
  329. { _id: id('fr00'), hash: 'h' },
  330. { _id: id('fr01'), hash: 'h', name: [] },
  331. ],
  332. },
  333. ],
  334. lastUpdated,
  335. lastUpdatedBy,
  336. },
  337. expectFind: [
  338. {
  339. reason: 'bad folder name',
  340. path: 'rootFolder.0.folders.0.name',
  341. _id: strId('f00'),
  342. },
  343. {
  344. reason: 'bad folder name',
  345. path: 'rootFolder.0.folders.1.name',
  346. _id: strId('f01'),
  347. },
  348. {
  349. reason: 'bad doc name',
  350. path: 'rootFolder.0.docs.0.name',
  351. _id: strId('d00'),
  352. },
  353. {
  354. reason: 'bad doc name',
  355. path: 'rootFolder.0.docs.1.name',
  356. _id: strId('d01'),
  357. },
  358. {
  359. reason: 'bad file name',
  360. path: 'rootFolder.0.fileRefs.0.name',
  361. _id: strId('fr00'),
  362. },
  363. {
  364. reason: 'bad file name',
  365. path: 'rootFolder.0.fileRefs.1.name',
  366. _id: strId('fr01'),
  367. },
  368. ].map(entry => ({
  369. ...entry,
  370. projectId: projectId.toString(),
  371. msg: 'bad file-tree path',
  372. })),
  373. expectFixStdout:
  374. '"gracefulShutdownInitiated":false,"processedLines":6,"success":6,"alreadyProcessed":0,"hash":0,"failed":0,"unmatched":0',
  375. expectProject: updatedProject => {
  376. expect(updatedProject).to.deep.equal({
  377. _id: projectId,
  378. rootFolder: [
  379. {
  380. _id: rootFolderId,
  381. name: 'rootFolder',
  382. folders: [
  383. {
  384. _id: id('f00'),
  385. name: 'untitled',
  386. folders: [],
  387. docs: [],
  388. fileRefs: [],
  389. },
  390. {
  391. _id: id('f01'),
  392. name: 'untitled-1',
  393. folders: [],
  394. docs: [],
  395. fileRefs: [],
  396. },
  397. ],
  398. docs: [
  399. { _id: id('d00'), name: 'untitled' },
  400. { _id: id('d01'), name: 'untitled-1' },
  401. ],
  402. fileRefs: [
  403. { _id: id('fr00'), hash: 'h', name: 'untitled' },
  404. { _id: id('fr01'), hash: 'h', name: 'untitled-1' },
  405. ],
  406. },
  407. ],
  408. lastUpdated: lastUpdatedChanged,
  409. lastUpdatedBy: null,
  410. })
  411. },
  412. },
  413. {
  414. name: 'bad file hash',
  415. project: {
  416. ...wellFormedProject,
  417. rootFolder: [
  418. {
  419. ...wellFormedProject.rootFolder[0],
  420. fileRefs: [
  421. { _id: id('fa'), name: 'ref-a', hash: null },
  422. { _id: id('fb'), name: 'ref-b', hash: {} },
  423. ],
  424. },
  425. ],
  426. lastUpdated,
  427. lastUpdatedBy,
  428. },
  429. expectFind: [
  430. { path: 'rootFolder.0.fileRefs.0.hash', _id: strId('fa') },
  431. { path: 'rootFolder.0.fileRefs.1.hash', _id: strId('fb') },
  432. ].map(entry => ({
  433. ...entry,
  434. projectId: projectId.toString(),
  435. reason: 'bad file hash',
  436. msg: 'bad file-tree path',
  437. })),
  438. expectFixError: new RegExp(
  439. `Missing file hash: ${projectId.toString()}/${id('fa').toString()}`
  440. ),
  441. },
  442. {
  443. name: 'well formed filetrees',
  444. project: wellFormedProject,
  445. expectFind: [],
  446. expectFixStdout:
  447. '"gracefulShutdownInitiated":false,"processedLines":1,"success":0,"alreadyProcessed":0,"hash":0,"failed":0,"unmatched":0',
  448. expectProject: updatedProject => {
  449. expect(updatedProject).to.deep.equal(wellFormedProject)
  450. },
  451. },
  452. {
  453. name: 'bug: shifted arrays in filetree',
  454. project: {
  455. _id: projectId,
  456. rootFolder: [
  457. {
  458. _id: rootFolderId,
  459. name: 'rootFolder',
  460. folders: [null, null, { ...wellFormedFolder('f02'), name: null }],
  461. docs: [null, null, { ...wellFormedDoc('d02'), name: null }],
  462. fileRefs: [null, null, { ...wellFormedFileRef('fr02'), name: null }],
  463. },
  464. ],
  465. lastUpdated,
  466. lastUpdatedBy,
  467. },
  468. expectFind: [
  469. {
  470. _id: rootFolderId.toString(),
  471. path: 'rootFolder.0.folders.0',
  472. reason: 'bad folder',
  473. },
  474. {
  475. _id: rootFolderId.toString(),
  476. path: 'rootFolder.0.folders.1',
  477. reason: 'bad folder',
  478. },
  479. {
  480. _id: strId('f02'),
  481. path: 'rootFolder.0.folders.2.name',
  482. reason: 'bad folder name',
  483. },
  484. {
  485. _id: rootFolderId.toString(),
  486. path: 'rootFolder.0.docs.0',
  487. reason: 'bad doc',
  488. },
  489. {
  490. _id: rootFolderId.toString(),
  491. path: 'rootFolder.0.docs.1',
  492. reason: 'bad doc',
  493. },
  494. {
  495. _id: strId('d02'),
  496. path: 'rootFolder.0.docs.2.name',
  497. reason: 'bad doc name',
  498. },
  499. {
  500. _id: rootFolderId.toString(),
  501. path: 'rootFolder.0.fileRefs.0',
  502. reason: 'bad file',
  503. },
  504. {
  505. _id: rootFolderId.toString(),
  506. path: 'rootFolder.0.fileRefs.1',
  507. reason: 'bad file',
  508. },
  509. {
  510. _id: strId('fr02'),
  511. path: 'rootFolder.0.fileRefs.2.name',
  512. reason: 'bad file name',
  513. },
  514. ].map(entry => ({
  515. ...entry,
  516. projectId: projectId.toString(),
  517. msg: 'bad file-tree path',
  518. })),
  519. expectFixStdout:
  520. '"gracefulShutdownInitiated":false,"processedLines":9,"success":4,"alreadyProcessed":5,"hash":0,"failed":0,"unmatched":0',
  521. expectProject: updatedProject => {
  522. expect(updatedProject).to.deep.equal({
  523. _id: projectId,
  524. rootFolder: [
  525. {
  526. _id: rootFolderId,
  527. name: 'rootFolder',
  528. folders: [{ ...wellFormedFolder('f02'), name: 'untitled' }],
  529. docs: [{ ...wellFormedDoc('d02'), name: 'untitled' }],
  530. fileRefs: [{ ...wellFormedFileRef('fr02'), name: 'untitled' }],
  531. },
  532. ],
  533. lastUpdated: lastUpdatedChanged,
  534. lastUpdatedBy: null,
  535. })
  536. },
  537. },
  538. {
  539. name: 'bug: shifted arrays in filetree folder',
  540. project: {
  541. _id: projectId,
  542. rootFolder: [
  543. {
  544. _id: rootFolderId,
  545. name: 'rootFolder',
  546. folders: [
  547. null,
  548. null,
  549. {
  550. ...wellFormedFolder('f02'),
  551. name: 'folder 1',
  552. folders: [null, null, { ...wellFormedFolder('f022') }],
  553. docs: [null, null, { ...wellFormedDoc('d022'), name: null }],
  554. fileRefs: [
  555. null,
  556. null,
  557. { ...wellFormedFileRef('fr022'), name: null },
  558. ],
  559. },
  560. ],
  561. docs: [],
  562. fileRefs: [],
  563. },
  564. ],
  565. lastUpdated,
  566. lastUpdatedBy,
  567. },
  568. expectFind: [
  569. {
  570. _id: rootFolderId.toString(),
  571. path: 'rootFolder.0.folders.0',
  572. reason: 'bad folder',
  573. },
  574. {
  575. _id: rootFolderId.toString(),
  576. path: 'rootFolder.0.folders.1',
  577. reason: 'bad folder',
  578. },
  579. {
  580. _id: strId('f02'),
  581. path: 'rootFolder.0.folders.2.folders.0',
  582. reason: 'bad folder',
  583. },
  584. {
  585. _id: strId('f02'),
  586. path: 'rootFolder.0.folders.2.folders.1',
  587. reason: 'bad folder',
  588. },
  589. {
  590. _id: strId('f02'),
  591. path: 'rootFolder.0.folders.2.docs.0',
  592. reason: 'bad doc',
  593. },
  594. {
  595. _id: strId('f02'),
  596. path: 'rootFolder.0.folders.2.docs.1',
  597. reason: 'bad doc',
  598. },
  599. {
  600. _id: strId('d022'),
  601. path: 'rootFolder.0.folders.2.docs.2.name',
  602. reason: 'bad doc name',
  603. },
  604. {
  605. _id: strId('f02'),
  606. path: 'rootFolder.0.folders.2.fileRefs.0',
  607. reason: 'bad file',
  608. },
  609. {
  610. _id: strId('f02'),
  611. path: 'rootFolder.0.folders.2.fileRefs.1',
  612. reason: 'bad file',
  613. },
  614. {
  615. _id: strId('fr022'),
  616. path: 'rootFolder.0.folders.2.fileRefs.2.name',
  617. reason: 'bad file name',
  618. },
  619. ].map(entry => ({
  620. ...entry,
  621. projectId: projectId.toString(),
  622. msg: 'bad file-tree path',
  623. })),
  624. expectFixStdout:
  625. '"gracefulShutdownInitiated":false,"processedLines":10,"success":4,"alreadyProcessed":6,"hash":0,"failed":0,"unmatched":0',
  626. expectProject: updatedProject => {
  627. expect(updatedProject).to.deep.equal({
  628. _id: projectId,
  629. rootFolder: [
  630. {
  631. _id: rootFolderId,
  632. name: 'rootFolder',
  633. folders: [
  634. {
  635. ...wellFormedFolder('f02'),
  636. name: 'folder 1',
  637. docs: [
  638. {
  639. ...wellFormedDoc('d022'),
  640. name: 'untitled',
  641. },
  642. ],
  643. fileRefs: [
  644. {
  645. ...wellFormedFileRef('fr022'),
  646. // FIXME: Make the names unique across different file types
  647. name: 'untitled',
  648. },
  649. ],
  650. folders: [
  651. {
  652. ...wellFormedFolder('f022'),
  653. name: 'f022',
  654. folders: [],
  655. docs: [],
  656. fileRefs: [],
  657. },
  658. ],
  659. },
  660. ],
  661. docs: [],
  662. fileRefs: [],
  663. },
  664. ],
  665. lastUpdated: lastUpdatedChanged,
  666. lastUpdatedBy: null,
  667. })
  668. },
  669. },
  670. ]
  671. describe('find_malformed_filetrees and fix_malformed_filetree scripts', function () {
  672. testCases.forEach(
  673. ({
  674. name,
  675. project,
  676. expectFind,
  677. expectFixStdout,
  678. expectFixError,
  679. expectProject,
  680. }) => {
  681. describe(name, function () {
  682. beforeEach(async function () {
  683. await db.projects.insertOne(project)
  684. })
  685. it('finds malformed filetree', async function () {
  686. const stdout = await runScriptFind()
  687. expect(stdout.map(line => JSON.parse(line))).to.deep.equal(expectFind)
  688. })
  689. if (expectFixError) {
  690. it('fails to fix malformed filetrees', async function () {
  691. await expect(runScriptFix(expectFind)).to.be.rejectedWith(
  692. expectFixError
  693. )
  694. })
  695. } else {
  696. it('fixes malformed filetrees', async function () {
  697. const { stdout } = await runScriptFix(expectFind)
  698. expect(expectFixStdout).to.be.a('string')
  699. expect(stdout).to.include(expectFixStdout)
  700. const [updatedProject] = await findProjects()
  701. if (updatedProject.lastUpdated > lastUpdated) {
  702. updatedProject.lastUpdated = lastUpdatedChanged
  703. }
  704. expectProject(updatedProject)
  705. })
  706. }
  707. })
  708. }
  709. )
  710. })