BackFillDummyDocMetaTests.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  1. const { exec } = require('child_process')
  2. const { promisify } = require('util')
  3. const { expect } = require('chai')
  4. const logger = require('@overleaf/logger')
  5. const { filterOutput } = require('./helpers/settings')
  6. const { db, ObjectId } = require('../../../app/src/infrastructure/mongodb')
  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 getSecondsFromObjectId(id) {
  12. return id.getTimestamp().getTime() / 1000
  13. }
  14. function getObjectIdFromDate(date) {
  15. const seconds = new Date(date).getTime() / 1000
  16. return ObjectId.createFromTime(seconds)
  17. }
  18. describe('BackFillDummyDocMeta', function () {
  19. let docIds
  20. let projectIds
  21. let stopAtSeconds
  22. beforeEach('create docs', async function () {
  23. docIds = []
  24. docIds[0] = getObjectIdFromDate('2021-04-01T00:00:00.000Z')
  25. docIds[1] = getObjectIdFromDate('2021-04-02T00:00:00.000Z')
  26. docIds[2] = getObjectIdFromDate('2021-04-11T00:00:00.000Z')
  27. docIds[3] = getObjectIdFromDate('2021-04-12T00:00:00.000Z')
  28. docIds[4] = getObjectIdFromDate('2021-04-13T00:00:00.000Z')
  29. docIds[5] = getObjectIdFromDate('2021-04-14T00:00:00.000Z')
  30. docIds[6] = getObjectIdFromDate('2021-04-15T00:00:00.000Z')
  31. docIds[7] = getObjectIdFromDate('2021-04-16T00:01:00.000Z')
  32. docIds[8] = getObjectIdFromDate('2021-04-16T00:02:00.000Z')
  33. docIds[9] = getObjectIdFromDate('2021-04-16T00:03:00.000Z')
  34. docIds[10] = getObjectIdFromDate('2021-04-16T00:04:00.000Z')
  35. docIds[11] = getObjectIdFromDate('2021-04-16T00:05:00.000Z')
  36. projectIds = []
  37. projectIds[0] = getObjectIdFromDate('2021-04-01T00:00:00.000Z')
  38. projectIds[1] = getObjectIdFromDate('2021-04-02T00:00:00.000Z')
  39. projectIds[2] = getObjectIdFromDate('2021-04-11T00:00:00.000Z')
  40. projectIds[3] = getObjectIdFromDate('2021-04-12T00:00:00.000Z')
  41. projectIds[4] = getObjectIdFromDate('2021-04-13T00:00:00.000Z')
  42. projectIds[5] = getObjectIdFromDate('2021-04-14T00:00:00.000Z')
  43. projectIds[6] = getObjectIdFromDate('2021-04-15T00:00:00.000Z')
  44. projectIds[7] = getObjectIdFromDate('2021-04-16T00:01:00.000Z')
  45. projectIds[8] = getObjectIdFromDate('2021-04-16T00:02:00.000Z')
  46. projectIds[9] = getObjectIdFromDate('2021-04-16T00:03:00.000Z')
  47. // two docs in the same project
  48. projectIds[10] = projectIds[9]
  49. projectIds[11] = projectIds[4]
  50. stopAtSeconds = new Date('2021-04-17T00:00:00.000Z').getTime() / 1000
  51. })
  52. const now = new Date()
  53. beforeEach('insert doc stubs into docs collection', async function () {
  54. await db.docs.insertMany([
  55. // incomplete, without deletedDocs context
  56. { _id: docIds[0], project_id: projectIds[0], deleted: true },
  57. { _id: docIds[1], project_id: projectIds[1], deleted: true },
  58. { _id: docIds[2], project_id: projectIds[2], deleted: true },
  59. { _id: docIds[3], project_id: projectIds[3], deleted: true },
  60. // incomplete, with deletedDocs context
  61. { _id: docIds[4], project_id: projectIds[4], deleted: true },
  62. // complete
  63. {
  64. _id: docIds[5],
  65. project_id: projectIds[5],
  66. deleted: true,
  67. name: 'foo.tex',
  68. deletedAt: now,
  69. },
  70. // not deleted
  71. { _id: docIds[6], project_id: projectIds[6] },
  72. // multiple in a single batch
  73. { _id: docIds[7], project_id: projectIds[7], deleted: true },
  74. { _id: docIds[8], project_id: projectIds[8], deleted: true },
  75. { _id: docIds[9], project_id: projectIds[9], deleted: true },
  76. // two docs in one project
  77. { _id: docIds[10], project_id: projectIds[10], deleted: true },
  78. { _id: docIds[11], project_id: projectIds[11], deleted: true },
  79. ])
  80. })
  81. beforeEach('insert deleted project context', async function () {
  82. await db.deletedProjects.insertMany([
  83. // projectIds[0] and projectIds[1] have no entry
  84. // hard-deleted
  85. { deleterData: { deletedProjectId: projectIds[2] } },
  86. // soft-deleted, no entry for doc
  87. {
  88. deleterData: { deletedProjectId: projectIds[3] },
  89. project: { deletedDocs: [] },
  90. },
  91. // soft-deleted, has entry for doc
  92. {
  93. deleterData: { deletedProjectId: projectIds[4] },
  94. project: {
  95. deletedDocs: [
  96. { _id: docIds[4], name: 'main.tex', deletedAt: now },
  97. { _id: docIds[11], name: 'main.tex', deletedAt: now },
  98. ],
  99. },
  100. },
  101. ])
  102. })
  103. let options
  104. async function runScript(dryRun) {
  105. options = {
  106. BATCH_SIZE,
  107. CACHE_SIZE: 100,
  108. DRY_RUN: dryRun,
  109. FIRST_PROJECT_ID: projectIds[0].toString(),
  110. INCREMENT_BY_S: ONE_DAY_IN_S,
  111. STOP_AT_S: stopAtSeconds,
  112. // start right away
  113. LET_USER_DOUBLE_CHECK_INPUTS_FOR: 1,
  114. }
  115. let result
  116. try {
  117. result = await promisify(exec)(
  118. Object.entries(options)
  119. .map(([key, value]) => `${key}=${value}`)
  120. .concat(['node', 'scripts/back_fill_dummy_doc_meta.js'])
  121. .join(' ')
  122. )
  123. } catch (error) {
  124. // dump details like exit code, stdErr and stdOut
  125. logger.error({ error }, 'script failed')
  126. throw error
  127. }
  128. let { stderr: stdErr, stdout: stdOut } = result
  129. stdErr = stdErr.split('\n')
  130. stdOut = stdOut.split('\n').filter(filterOutput)
  131. const oneDayFromProjectId9InSeconds =
  132. getSecondsFromObjectId(projectIds[9]) + ONE_DAY_IN_S
  133. const oneDayFromProjectId9AsObjectId = getObjectIdFromDate(
  134. 1000 * oneDayFromProjectId9InSeconds
  135. )
  136. let overlappingPartStdOut
  137. let overlappingPartStdErr
  138. if (dryRun) {
  139. // In dry-run, the previous id will get processed again as the name has not been updated.
  140. overlappingPartStdOut = [
  141. `Back filling dummy meta data for ["${docIds[9]}","${docIds[10]}"]`,
  142. `Orphaned deleted doc ${docIds[9]} (no deletedProjects entry)`,
  143. `Orphaned deleted doc ${docIds[10]} (no deletedProjects entry)`,
  144. ]
  145. overlappingPartStdErr = [
  146. `Processed 11 until ${oneDayFromProjectId9AsObjectId}`,
  147. ]
  148. } else {
  149. // Outside dry-run, the previous id will not match again as the `name` has been back-filled.
  150. overlappingPartStdOut = [
  151. `Back filling dummy meta data for ["${docIds[10]}"]`,
  152. `Orphaned deleted doc ${docIds[10]} (no deletedProjects entry)`,
  153. ]
  154. overlappingPartStdErr = [
  155. `Processed 10 until ${oneDayFromProjectId9AsObjectId}`,
  156. ]
  157. }
  158. expect(stdOut.filter(filterOutput)).to.deep.equal([
  159. `Back filling dummy meta data for ["${docIds[0]}"]`,
  160. `Orphaned deleted doc ${docIds[0]} (no deletedProjects entry)`,
  161. `Back filling dummy meta data for ["${docIds[1]}"]`,
  162. `Orphaned deleted doc ${docIds[1]} (no deletedProjects entry)`,
  163. `Back filling dummy meta data for ["${docIds[2]}"]`,
  164. `Orphaned deleted doc ${docIds[2]} (failed hard deletion)`,
  165. `Back filling dummy meta data for ["${docIds[3]}"]`,
  166. `Missing deletedDoc for ${docIds[3]}`,
  167. // two docs in the same project
  168. `Back filling dummy meta data for ["${docIds[4]}","${docIds[11]}"]`,
  169. `Found deletedDoc for ${docIds[4]}`,
  170. `Found deletedDoc for ${docIds[11]}`,
  171. // 7,8,9 are on the same day, but exceed the batch size of 2
  172. `Back filling dummy meta data for ["${docIds[7]}","${docIds[8]}","${docIds[9]}"]`,
  173. `Orphaned deleted doc ${docIds[7]} (no deletedProjects entry)`,
  174. `Orphaned deleted doc ${docIds[8]} (no deletedProjects entry)`,
  175. `Orphaned deleted doc ${docIds[9]} (no deletedProjects entry)`,
  176. // Potential double processing
  177. ...overlappingPartStdOut,
  178. '',
  179. ])
  180. expect(stdErr.filter(filterOutput)).to.deep.equal([
  181. `Options: {`,
  182. ` "dryRun": ${options.DRY_RUN},`,
  183. ` "cacheSize": ${options.CACHE_SIZE},`,
  184. ` "firstProjectId": "${options.FIRST_PROJECT_ID}",`,
  185. ` "incrementByS": ${options.INCREMENT_BY_S},`,
  186. ` "batchSize": ${options.BATCH_SIZE},`,
  187. ` "stopAtS": ${options.STOP_AT_S},`,
  188. ` "letUserDoubleCheckInputsFor": ${options.LET_USER_DOUBLE_CHECK_INPUTS_FOR}`,
  189. '}',
  190. 'Waiting for you to double check inputs for 1 ms',
  191. `Processed 1 until ${getObjectIdFromDate('2021-04-02T00:00:00.000Z')}`,
  192. `Processed 2 until ${getObjectIdFromDate('2021-04-03T00:00:00.000Z')}`,
  193. `Processed 2 until ${getObjectIdFromDate('2021-04-04T00:00:00.000Z')}`,
  194. `Processed 2 until ${getObjectIdFromDate('2021-04-05T00:00:00.000Z')}`,
  195. `Processed 2 until ${getObjectIdFromDate('2021-04-06T00:00:00.000Z')}`,
  196. `Processed 2 until ${getObjectIdFromDate('2021-04-07T00:00:00.000Z')}`,
  197. `Processed 2 until ${getObjectIdFromDate('2021-04-08T00:00:00.000Z')}`,
  198. `Processed 2 until ${getObjectIdFromDate('2021-04-09T00:00:00.000Z')}`,
  199. `Processed 2 until ${getObjectIdFromDate('2021-04-10T00:00:00.000Z')}`,
  200. `Processed 2 until ${getObjectIdFromDate('2021-04-11T00:00:00.000Z')}`,
  201. `Processed 3 until ${getObjectIdFromDate('2021-04-12T00:00:00.000Z')}`,
  202. `Processed 4 until ${getObjectIdFromDate('2021-04-13T00:00:00.000Z')}`,
  203. `Processed 6 until ${getObjectIdFromDate('2021-04-14T00:00:00.000Z')}`,
  204. `Processed 6 until ${getObjectIdFromDate('2021-04-15T00:00:00.000Z')}`,
  205. `Processed 6 until ${getObjectIdFromDate('2021-04-16T00:00:00.000Z')}`,
  206. // 7,8,9,10 are on the same day, but exceed the batch size of 3
  207. `Processed 9 until ${projectIds[9]}`,
  208. ...overlappingPartStdErr,
  209. 'Done.',
  210. '',
  211. ])
  212. }
  213. describe('DRY_RUN=true', function () {
  214. beforeEach('run script', async function () {
  215. await runScript(true)
  216. })
  217. it('should leave docs as is', async function () {
  218. const docs = await db.docs.find({}).toArray()
  219. expect(docs).to.deep.equal([
  220. { _id: docIds[0], project_id: projectIds[0], deleted: true },
  221. { _id: docIds[1], project_id: projectIds[1], deleted: true },
  222. { _id: docIds[2], project_id: projectIds[2], deleted: true },
  223. { _id: docIds[3], project_id: projectIds[3], deleted: true },
  224. { _id: docIds[4], project_id: projectIds[4], deleted: true },
  225. {
  226. _id: docIds[5],
  227. project_id: projectIds[5],
  228. deleted: true,
  229. name: 'foo.tex',
  230. deletedAt: now,
  231. },
  232. { _id: docIds[6], project_id: projectIds[6] },
  233. { _id: docIds[7], project_id: projectIds[7], deleted: true },
  234. { _id: docIds[8], project_id: projectIds[8], deleted: true },
  235. { _id: docIds[9], project_id: projectIds[9], deleted: true },
  236. { _id: docIds[10], project_id: projectIds[10], deleted: true },
  237. { _id: docIds[11], project_id: projectIds[11], deleted: true },
  238. ])
  239. })
  240. })
  241. describe('DRY_RUN=false', function () {
  242. beforeEach('run script', async function () {
  243. await runScript(false)
  244. })
  245. it('should back fill name and deletedAt dates into broken docs', async function () {
  246. const docs = await db.docs.find({}).toArray()
  247. expect(docs).to.deep.equal([
  248. {
  249. _id: docIds[0],
  250. project_id: projectIds[0],
  251. deleted: true,
  252. name: DUMMY_NAME,
  253. deletedAt: DUMMY_TIME,
  254. },
  255. {
  256. _id: docIds[1],
  257. project_id: projectIds[1],
  258. deleted: true,
  259. name: DUMMY_NAME,
  260. deletedAt: DUMMY_TIME,
  261. },
  262. {
  263. _id: docIds[2],
  264. project_id: projectIds[2],
  265. deleted: true,
  266. name: DUMMY_NAME,
  267. deletedAt: DUMMY_TIME,
  268. },
  269. {
  270. _id: docIds[3],
  271. project_id: projectIds[3],
  272. deleted: true,
  273. name: DUMMY_NAME,
  274. deletedAt: DUMMY_TIME,
  275. },
  276. {
  277. _id: docIds[4],
  278. project_id: projectIds[4],
  279. deleted: true,
  280. name: 'main.tex',
  281. deletedAt: now,
  282. },
  283. {
  284. _id: docIds[5],
  285. project_id: projectIds[5],
  286. deleted: true,
  287. name: 'foo.tex',
  288. deletedAt: now,
  289. },
  290. { _id: docIds[6], project_id: projectIds[6] },
  291. {
  292. _id: docIds[7],
  293. project_id: projectIds[7],
  294. deleted: true,
  295. name: DUMMY_NAME,
  296. deletedAt: DUMMY_TIME,
  297. },
  298. {
  299. _id: docIds[8],
  300. project_id: projectIds[8],
  301. deleted: true,
  302. name: DUMMY_NAME,
  303. deletedAt: DUMMY_TIME,
  304. },
  305. {
  306. _id: docIds[9],
  307. project_id: projectIds[9],
  308. deleted: true,
  309. name: DUMMY_NAME,
  310. deletedAt: DUMMY_TIME,
  311. },
  312. {
  313. _id: docIds[10],
  314. project_id: projectIds[10],
  315. deleted: true,
  316. name: DUMMY_NAME,
  317. deletedAt: DUMMY_TIME,
  318. },
  319. {
  320. _id: docIds[11],
  321. project_id: projectIds[11],
  322. deleted: true,
  323. name: 'main.tex',
  324. deletedAt: now,
  325. },
  326. ])
  327. })
  328. })
  329. })