BackFillDeletedFilesTests.mjs 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. import { exec } from 'child_process'
  2. import { promisify } from 'util'
  3. import { expect } from 'chai'
  4. import logger from '@overleaf/logger'
  5. import { db, ObjectId } from '../../../app/src/infrastructure/mongodb.js'
  6. import UserHelper from './helpers/User.js'
  7. const User = UserHelper.promises
  8. async function getDeletedFiles(projectId) {
  9. return (await db.projects.findOne({ _id: projectId })).deletedFiles
  10. }
  11. async function setDeletedFiles(projectId, deletedFiles) {
  12. await db.projects.updateOne({ _id: projectId }, { $set: { deletedFiles } })
  13. }
  14. async function unsetDeletedFiles(projectId) {
  15. await db.projects.updateOne(
  16. { _id: projectId },
  17. { $unset: { deletedFiles: 1 } }
  18. )
  19. }
  20. describe('BackFillDeletedFiles', function () {
  21. let user, projectId1, projectId2, projectId3, projectId4, projectId5
  22. beforeEach('create projects', async function () {
  23. user = new User()
  24. await user.login()
  25. projectId1 = new ObjectId(await user.createProject('project1'))
  26. projectId2 = new ObjectId(await user.createProject('project2'))
  27. projectId3 = new ObjectId(await user.createProject('project3'))
  28. projectId4 = new ObjectId(await user.createProject('project4'))
  29. projectId5 = new ObjectId(await user.createProject('project5'))
  30. })
  31. let fileId1, fileId2, fileId3, fileId4
  32. beforeEach('create files', function () {
  33. // take a short cut and just allocate file ids
  34. fileId1 = new ObjectId()
  35. fileId2 = new ObjectId()
  36. fileId3 = new ObjectId()
  37. fileId4 = new ObjectId()
  38. })
  39. const otherFileDetails = {
  40. name: 'universe.jpg',
  41. linkedFileData: null,
  42. hash: 'ed19e7d6779b47d8c63f6fa5a21954dcfb6cac00',
  43. deletedAt: new Date(),
  44. __v: 0,
  45. }
  46. let deletedFiles1, deletedFiles2, deletedFiles3
  47. beforeEach('set deletedFiles details', async function () {
  48. deletedFiles1 = [
  49. { _id: fileId1, ...otherFileDetails },
  50. { _id: fileId2, ...otherFileDetails },
  51. ]
  52. deletedFiles2 = [{ _id: fileId3, ...otherFileDetails }]
  53. await setDeletedFiles(projectId1, deletedFiles1)
  54. await setDeletedFiles(projectId2, deletedFiles2)
  55. // a project without deletedFiles entries
  56. await setDeletedFiles(projectId3, [])
  57. // a project without deletedFiles array
  58. await unsetDeletedFiles(projectId4)
  59. // duplicate entry
  60. deletedFiles3 = [
  61. { _id: fileId4, ...otherFileDetails },
  62. { _id: fileId4, ...otherFileDetails },
  63. ]
  64. await setDeletedFiles(projectId5, deletedFiles3)
  65. })
  66. async function runScript(args = []) {
  67. let result
  68. try {
  69. result = await promisify(exec)(
  70. ['LET_USER_DOUBLE_CHECK_INPUTS_FOR=1', 'VERBOSE_LOGGING=true']
  71. .concat(['node', 'scripts/back_fill_deleted_files.mjs'])
  72. .concat(args)
  73. .join(' ')
  74. )
  75. } catch (error) {
  76. // dump details like exit code, stdErr and stdOut
  77. logger.error({ error }, 'script failed')
  78. throw error
  79. }
  80. const { stdout: stdOut } = result
  81. expect(stdOut).to.match(
  82. new RegExp(`Running update on batch with ids .+${projectId1}`)
  83. )
  84. expect(stdOut).to.match(
  85. new RegExp(`Running update on batch with ids .+${projectId2}`)
  86. )
  87. expect(stdOut).to.not.match(
  88. new RegExp(`Running update on batch with ids .+${projectId3}`)
  89. )
  90. expect(stdOut).to.not.match(
  91. new RegExp(`Running update on batch with ids .+${projectId4}`)
  92. )
  93. expect(stdOut).to.match(
  94. new RegExp(`Running update on batch with ids .+${projectId5}`)
  95. )
  96. }
  97. function checkAreFilesBackFilled() {
  98. it('should back fill file and set projectId', async function () {
  99. const docs = await db.deletedFiles
  100. .find({}, { sort: { _id: 1 } })
  101. .toArray()
  102. expect(docs).to.deep.equal([
  103. { _id: fileId1, projectId: projectId1, ...otherFileDetails },
  104. { _id: fileId2, projectId: projectId1, ...otherFileDetails },
  105. { _id: fileId3, projectId: projectId2, ...otherFileDetails },
  106. { _id: fileId4, projectId: projectId5, ...otherFileDetails },
  107. ])
  108. })
  109. }
  110. describe('back fill only', function () {
  111. beforeEach('run script', runScript)
  112. checkAreFilesBackFilled()
  113. it('should leave the deletedFiles as is', async function () {
  114. expect(await getDeletedFiles(projectId1)).to.deep.equal(deletedFiles1)
  115. expect(await getDeletedFiles(projectId2)).to.deep.equal(deletedFiles2)
  116. expect(await getDeletedFiles(projectId5)).to.deep.equal(deletedFiles3)
  117. })
  118. })
  119. describe('back fill and cleanup', function () {
  120. beforeEach('run script with cleanup flag', async function () {
  121. await runScript(['--perform-cleanup'])
  122. })
  123. checkAreFilesBackFilled()
  124. it('should cleanup the deletedFiles', async function () {
  125. expect(await getDeletedFiles(projectId1)).to.deep.equal([])
  126. expect(await getDeletedFiles(projectId2)).to.deep.equal([])
  127. expect(await getDeletedFiles(projectId5)).to.deep.equal([])
  128. })
  129. })
  130. describe('fix partial inserts and cleanup', function () {
  131. beforeEach('simulate one missing insert', async function () {
  132. await setDeletedFiles(projectId1, [deletedFiles1[0]])
  133. })
  134. beforeEach('run script with cleanup flag', async function () {
  135. await runScript(['--perform-cleanup'])
  136. })
  137. beforeEach('add case for one missing file', async function () {
  138. await setDeletedFiles(projectId1, deletedFiles1)
  139. })
  140. beforeEach('add cases for no more files to insert', async function () {
  141. await setDeletedFiles(projectId2, deletedFiles2)
  142. await setDeletedFiles(projectId5, deletedFiles3)
  143. })
  144. beforeEach('fixing partial insert and cleanup', async function () {
  145. await runScript(['--fix-partial-inserts', '--perform-cleanup'])
  146. })
  147. checkAreFilesBackFilled()
  148. it('should cleanup the deletedFiles', async function () {
  149. expect(await getDeletedFiles(projectId1)).to.deep.equal([])
  150. expect(await getDeletedFiles(projectId2)).to.deep.equal([])
  151. expect(await getDeletedFiles(projectId5)).to.deep.equal([])
  152. })
  153. })
  154. })