BackFillDummyDocMetaTests.mjs 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  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.js'
  7. const DUMMY_NAME = 'unknown.tex'
  8. const DUMMY_TIME = new Date('2021-04-12T00:00:00.000Z')
  9. const ONE_DAY_IN_S = 60 * 60 * 24
  10. const BATCH_SIZE = 3
  11. function getObjectIdFromDate(date) {
  12. const seconds = new Date(date).getTime() / 1000
  13. return ObjectId.createFromTime(seconds)
  14. }
  15. describe('BackFillDummyDocMeta', function () {
  16. let docIds
  17. let projectIds
  18. let stopAtSeconds
  19. beforeEach('create docs', async function () {
  20. docIds = []
  21. docIds[0] = getObjectIdFromDate('2021-04-01T00:00:00.000Z')
  22. docIds[1] = getObjectIdFromDate('2021-04-02T00:00:00.000Z')
  23. docIds[2] = getObjectIdFromDate('2021-04-11T00:00:00.000Z')
  24. docIds[3] = getObjectIdFromDate('2021-04-12T00:00:00.000Z')
  25. docIds[4] = getObjectIdFromDate('2021-04-13T00:00:00.000Z')
  26. docIds[5] = getObjectIdFromDate('2021-04-14T00:00:00.000Z')
  27. docIds[6] = getObjectIdFromDate('2021-04-15T00:00:00.000Z')
  28. docIds[7] = getObjectIdFromDate('2021-04-16T00:01:00.000Z')
  29. docIds[8] = getObjectIdFromDate('2021-04-16T00:02:00.000Z')
  30. docIds[9] = getObjectIdFromDate('2021-04-16T00:03:00.000Z')
  31. docIds[10] = getObjectIdFromDate('2021-04-16T00:04:00.000Z')
  32. docIds[11] = getObjectIdFromDate('2021-04-16T00:05:00.000Z')
  33. projectIds = []
  34. projectIds[0] = getObjectIdFromDate('2021-04-01T00:00:00.000Z')
  35. projectIds[1] = getObjectIdFromDate('2021-04-02T00:00:00.000Z')
  36. projectIds[2] = getObjectIdFromDate('2021-04-11T00:00:00.000Z')
  37. projectIds[3] = getObjectIdFromDate('2021-04-12T00:00:00.000Z')
  38. projectIds[4] = getObjectIdFromDate('2021-04-13T00:00:00.000Z')
  39. projectIds[5] = getObjectIdFromDate('2021-04-14T00:00:00.000Z')
  40. projectIds[6] = getObjectIdFromDate('2021-04-15T00:00:00.000Z')
  41. projectIds[7] = getObjectIdFromDate('2021-04-16T00:01:00.000Z')
  42. projectIds[8] = getObjectIdFromDate('2021-04-16T00:02:00.000Z')
  43. projectIds[9] = getObjectIdFromDate('2021-04-16T00:03:00.000Z')
  44. // two docs in the same project
  45. projectIds[10] = projectIds[9]
  46. projectIds[11] = projectIds[4]
  47. stopAtSeconds = new Date('2021-04-17T00:00:00.000Z').getTime() / 1000
  48. })
  49. const now = new Date()
  50. beforeEach('insert doc stubs into docs collection', async function () {
  51. await db.docs.insertMany([
  52. // incomplete, without deletedDocs context
  53. { _id: docIds[0], project_id: projectIds[0], deleted: true },
  54. { _id: docIds[1], project_id: projectIds[1], deleted: true },
  55. { _id: docIds[2], project_id: projectIds[2], deleted: true },
  56. { _id: docIds[3], project_id: projectIds[3], deleted: true },
  57. // incomplete, with deletedDocs context
  58. { _id: docIds[4], project_id: projectIds[4], deleted: true },
  59. // complete
  60. {
  61. _id: docIds[5],
  62. project_id: projectIds[5],
  63. deleted: true,
  64. name: 'foo.tex',
  65. deletedAt: now,
  66. },
  67. // not deleted
  68. { _id: docIds[6], project_id: projectIds[6] },
  69. // multiple in a single batch
  70. { _id: docIds[7], project_id: projectIds[7], deleted: true },
  71. { _id: docIds[8], project_id: projectIds[8], deleted: true },
  72. { _id: docIds[9], project_id: projectIds[9], deleted: true },
  73. // two docs in one project
  74. { _id: docIds[10], project_id: projectIds[10], deleted: true },
  75. { _id: docIds[11], project_id: projectIds[11], deleted: true },
  76. ])
  77. })
  78. beforeEach('insert deleted project context', async function () {
  79. await db.deletedProjects.insertMany([
  80. // projectIds[0] and projectIds[1] have no entry
  81. // hard-deleted
  82. { deleterData: { deletedProjectId: projectIds[2] } },
  83. // soft-deleted, no entry for doc
  84. {
  85. deleterData: { deletedProjectId: projectIds[3] },
  86. project: { deletedDocs: [] },
  87. },
  88. // soft-deleted, has entry for doc
  89. {
  90. deleterData: { deletedProjectId: projectIds[4] },
  91. project: {
  92. deletedDocs: [
  93. { _id: docIds[4], name: 'main.tex', deletedAt: now },
  94. { _id: docIds[11], name: 'main.tex', deletedAt: now },
  95. ],
  96. },
  97. },
  98. ])
  99. })
  100. let options
  101. async function runScript(dryRun) {
  102. options = {
  103. BATCH_SIZE,
  104. CACHE_SIZE: 100,
  105. DRY_RUN: dryRun,
  106. FIRST_PROJECT_ID: projectIds[0].toString(),
  107. INCREMENT_BY_S: ONE_DAY_IN_S,
  108. STOP_AT_S: stopAtSeconds,
  109. // start right away
  110. LET_USER_DOUBLE_CHECK_INPUTS_FOR: 1,
  111. }
  112. let result
  113. try {
  114. result = await promisify(exec)(
  115. Object.entries(options)
  116. .map(([key, value]) => `${key}=${value}`)
  117. .concat(['node', 'scripts/back_fill_dummy_doc_meta.mjs'])
  118. .join(' ')
  119. )
  120. } catch (error) {
  121. // dump details like exit code, stdErr and stdOut
  122. logger.error({ error }, 'script failed')
  123. throw error
  124. }
  125. let { stderr: stdErr, stdout: stdOut } = result
  126. stdErr = stdErr.split('\n')
  127. stdOut = stdOut.split('\n').filter(filterOutput)
  128. expect(stdOut.filter(filterOutput)).to.include.members([
  129. `Orphaned deleted doc ${docIds[0]} (no deletedProjects entry)`,
  130. `Orphaned deleted doc ${docIds[1]} (no deletedProjects entry)`,
  131. `Orphaned deleted doc ${docIds[2]} (failed hard deletion)`,
  132. `Missing deletedDoc for ${docIds[3]}`,
  133. `Found deletedDoc for ${docIds[4]}`,
  134. `Found deletedDoc for ${docIds[11]}`,
  135. `Orphaned deleted doc ${docIds[7]} (no deletedProjects entry)`,
  136. `Orphaned deleted doc ${docIds[8]} (no deletedProjects entry)`,
  137. `Orphaned deleted doc ${docIds[9]} (no deletedProjects entry)`,
  138. `Orphaned deleted doc ${docIds[10]} (no deletedProjects entry)`,
  139. ])
  140. expect(stdErr.filter(filterOutput)).to.include.members([
  141. `Processed 9 until ${projectIds[9]}`,
  142. 'Done.',
  143. ])
  144. }
  145. describe('DRY_RUN=true', function () {
  146. beforeEach('run script', async function () {
  147. await runScript(true)
  148. })
  149. it('should leave docs as is', async function () {
  150. const docs = await db.docs.find({}).toArray()
  151. expect(docs).to.deep.equal([
  152. { _id: docIds[0], project_id: projectIds[0], deleted: true },
  153. { _id: docIds[1], project_id: projectIds[1], deleted: true },
  154. { _id: docIds[2], project_id: projectIds[2], deleted: true },
  155. { _id: docIds[3], project_id: projectIds[3], deleted: true },
  156. { _id: docIds[4], project_id: projectIds[4], deleted: true },
  157. {
  158. _id: docIds[5],
  159. project_id: projectIds[5],
  160. deleted: true,
  161. name: 'foo.tex',
  162. deletedAt: now,
  163. },
  164. { _id: docIds[6], project_id: projectIds[6] },
  165. { _id: docIds[7], project_id: projectIds[7], deleted: true },
  166. { _id: docIds[8], project_id: projectIds[8], deleted: true },
  167. { _id: docIds[9], project_id: projectIds[9], deleted: true },
  168. { _id: docIds[10], project_id: projectIds[10], deleted: true },
  169. { _id: docIds[11], project_id: projectIds[11], deleted: true },
  170. ])
  171. })
  172. })
  173. describe('DRY_RUN=false', function () {
  174. beforeEach('run script', async function () {
  175. await runScript(false)
  176. })
  177. it('should back fill name and deletedAt dates into broken docs', async function () {
  178. const docs = await db.docs.find({}).toArray()
  179. expect(docs).to.deep.equal([
  180. {
  181. _id: docIds[0],
  182. project_id: projectIds[0],
  183. deleted: true,
  184. name: DUMMY_NAME,
  185. deletedAt: DUMMY_TIME,
  186. },
  187. {
  188. _id: docIds[1],
  189. project_id: projectIds[1],
  190. deleted: true,
  191. name: DUMMY_NAME,
  192. deletedAt: DUMMY_TIME,
  193. },
  194. {
  195. _id: docIds[2],
  196. project_id: projectIds[2],
  197. deleted: true,
  198. name: DUMMY_NAME,
  199. deletedAt: DUMMY_TIME,
  200. },
  201. {
  202. _id: docIds[3],
  203. project_id: projectIds[3],
  204. deleted: true,
  205. name: DUMMY_NAME,
  206. deletedAt: DUMMY_TIME,
  207. },
  208. {
  209. _id: docIds[4],
  210. project_id: projectIds[4],
  211. deleted: true,
  212. name: 'main.tex',
  213. deletedAt: now,
  214. },
  215. {
  216. _id: docIds[5],
  217. project_id: projectIds[5],
  218. deleted: true,
  219. name: 'foo.tex',
  220. deletedAt: now,
  221. },
  222. { _id: docIds[6], project_id: projectIds[6] },
  223. {
  224. _id: docIds[7],
  225. project_id: projectIds[7],
  226. deleted: true,
  227. name: DUMMY_NAME,
  228. deletedAt: DUMMY_TIME,
  229. },
  230. {
  231. _id: docIds[8],
  232. project_id: projectIds[8],
  233. deleted: true,
  234. name: DUMMY_NAME,
  235. deletedAt: DUMMY_TIME,
  236. },
  237. {
  238. _id: docIds[9],
  239. project_id: projectIds[9],
  240. deleted: true,
  241. name: DUMMY_NAME,
  242. deletedAt: DUMMY_TIME,
  243. },
  244. {
  245. _id: docIds[10],
  246. project_id: projectIds[10],
  247. deleted: true,
  248. name: DUMMY_NAME,
  249. deletedAt: DUMMY_TIME,
  250. },
  251. {
  252. _id: docIds[11],
  253. project_id: projectIds[11],
  254. deleted: true,
  255. name: 'main.tex',
  256. deletedAt: now,
  257. },
  258. ])
  259. })
  260. })
  261. })