DeleteOrphanedDocsOnlineCheckTests.mjs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  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 ONE_DAY_IN_S = 60 * 60 * 24
  8. const BATCH_SIZE = 3
  9. function getSecondsFromObjectId(id) {
  10. return id.getTimestamp().getTime() / 1000
  11. }
  12. function getObjectIdFromDate(date) {
  13. const seconds = new Date(date).getTime() / 1000
  14. return ObjectId.createFromTime(seconds)
  15. }
  16. describe('DeleteOrphanedDocsOnlineCheck', function () {
  17. let docIds
  18. let projectIds
  19. let stopAtSeconds
  20. let BATCH_LAST_ID
  21. beforeEach('create docs', async function () {
  22. BATCH_LAST_ID = getObjectIdFromDate('2021-03-31T00:00:00.000Z')
  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. beforeEach('create doc stubs', async function () {
  53. await db.docs.insertMany([
  54. // orphaned
  55. { _id: docIds[0], project_id: projectIds[0] },
  56. { _id: docIds[1], project_id: projectIds[1] },
  57. { _id: docIds[2], project_id: projectIds[2] },
  58. { _id: docIds[3], project_id: projectIds[3] },
  59. // orphaned, failed hard deletion
  60. { _id: docIds[4], project_id: projectIds[4] },
  61. // not orphaned, live
  62. { _id: docIds[5], project_id: projectIds[5] },
  63. // not orphaned, pending hard deletion
  64. { _id: docIds[6], project_id: projectIds[6] },
  65. // multiple in a single batch
  66. { _id: docIds[7], project_id: projectIds[7] },
  67. { _id: docIds[8], project_id: projectIds[8] },
  68. { _id: docIds[9], project_id: projectIds[9] },
  69. // two docs in one project
  70. { _id: docIds[10], project_id: projectIds[10] },
  71. { _id: docIds[11], project_id: projectIds[11] },
  72. ])
  73. })
  74. beforeEach('create project stubs', async function () {
  75. await db.projects.insertMany([
  76. // live
  77. { _id: projectIds[5] },
  78. ])
  79. })
  80. beforeEach('create deleted project stubs', async function () {
  81. await db.deletedProjects.insertMany([
  82. // hard-deleted
  83. { deleterData: { deletedProjectId: projectIds[4] } },
  84. // soft-deleted
  85. {
  86. deleterData: { deletedProjectId: projectIds[6] },
  87. project: { _id: projectIds[6] },
  88. },
  89. ])
  90. })
  91. let options
  92. async function runScript(dryRun) {
  93. options = {
  94. BATCH_LAST_ID,
  95. BATCH_SIZE,
  96. DRY_RUN: dryRun,
  97. INCREMENT_BY_S: ONE_DAY_IN_S,
  98. STOP_AT_S: stopAtSeconds,
  99. // Lower concurrency to 1 for strict sequence of log messages.
  100. READ_CONCURRENCY_SECONDARY: 1,
  101. READ_CONCURRENCY_PRIMARY: 1,
  102. WRITE_CONCURRENCY: 1,
  103. // start right away
  104. LET_USER_DOUBLE_CHECK_INPUTS_FOR: 1,
  105. }
  106. let result
  107. try {
  108. result = await promisify(exec)(
  109. Object.entries(options)
  110. .map(([key, value]) => `${key}=${value}`)
  111. .concat([
  112. // Hide verbose log messages `calling destroy for project in docstore`
  113. 'LOG_LEVEL=error',
  114. // Hide deprecation warnings for calling `db.collection.count`
  115. 'NODE_OPTIONS=--no-deprecation',
  116. ])
  117. .concat(['node', 'scripts/delete_orphaned_docs_online_check.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').filter(filterOutput)
  127. stdOut = stdOut.split('\n').filter(filterOutput)
  128. const oneDayFromProjectId9InSeconds =
  129. getSecondsFromObjectId(projectIds[9]) + ONE_DAY_IN_S
  130. const oneDayFromProjectId9AsObjectId = getObjectIdFromDate(
  131. 1000 * oneDayFromProjectId9InSeconds
  132. )
  133. expect(stdOut).to.deep.equal([
  134. `Checking projects ["${projectIds[0]}"]`,
  135. `Deleted project ${projectIds[0]} has 1 orphaned docs: ["${docIds[0]}"]`,
  136. `Checking projects ["${projectIds[1]}"]`,
  137. `Deleted project ${projectIds[1]} has 1 orphaned docs: ["${docIds[1]}"]`,
  138. `Checking projects ["${projectIds[2]}"]`,
  139. `Deleted project ${projectIds[2]} has 1 orphaned docs: ["${docIds[2]}"]`,
  140. `Checking projects ["${projectIds[3]}"]`,
  141. `Deleted project ${projectIds[3]} has 1 orphaned docs: ["${docIds[3]}"]`,
  142. // Two docs in the same project
  143. `Checking projects ["${projectIds[4]}"]`,
  144. `Deleted project ${projectIds[4]} has 2 orphaned docs: ["${docIds[4]}","${docIds[11]}"]`,
  145. // Project 5 is live
  146. `Checking projects ["${projectIds[5]}"]`,
  147. // Project 6 is soft-deleted
  148. `Checking projects ["${projectIds[6]}"]`,
  149. // 7,8,9 are on the same day, but exceed the batch size of 2
  150. `Checking projects ["${projectIds[7]}","${projectIds[8]}","${projectIds[9]}"]`,
  151. `Deleted project ${projectIds[7]} has 1 orphaned docs: ["${docIds[7]}"]`,
  152. `Deleted project ${projectIds[8]} has 1 orphaned docs: ["${docIds[8]}"]`,
  153. // Two docs in the same project
  154. `Deleted project ${projectIds[9]} has 2 orphaned docs: ["${docIds[9]}","${docIds[10]}"]`,
  155. ])
  156. expect(stdErr).to.deep.equal([
  157. ...`Options: ${JSON.stringify(options, null, 2)}`.split('\n'),
  158. 'Waiting for you to double check inputs for 1 ms',
  159. `Processed 1 projects (1 projects with orphaned docs/1 docs deleted) until ${getObjectIdFromDate(
  160. '2021-04-01T00:00:00.000Z'
  161. )}`,
  162. `Processed 2 projects (2 projects with orphaned docs/2 docs deleted) until ${getObjectIdFromDate(
  163. '2021-04-02T00:00:00.000Z'
  164. )}`,
  165. `Processed 2 projects (2 projects with orphaned docs/2 docs deleted) until ${getObjectIdFromDate(
  166. '2021-04-03T00:00:00.000Z'
  167. )}`,
  168. `Processed 2 projects (2 projects with orphaned docs/2 docs deleted) until ${getObjectIdFromDate(
  169. '2021-04-04T00:00:00.000Z'
  170. )}`,
  171. `Processed 2 projects (2 projects with orphaned docs/2 docs deleted) until ${getObjectIdFromDate(
  172. '2021-04-05T00:00:00.000Z'
  173. )}`,
  174. `Processed 2 projects (2 projects with orphaned docs/2 docs deleted) until ${getObjectIdFromDate(
  175. '2021-04-06T00:00:00.000Z'
  176. )}`,
  177. `Processed 2 projects (2 projects with orphaned docs/2 docs deleted) until ${getObjectIdFromDate(
  178. '2021-04-07T00:00:00.000Z'
  179. )}`,
  180. `Processed 2 projects (2 projects with orphaned docs/2 docs deleted) until ${getObjectIdFromDate(
  181. '2021-04-08T00:00:00.000Z'
  182. )}`,
  183. `Processed 2 projects (2 projects with orphaned docs/2 docs deleted) until ${getObjectIdFromDate(
  184. '2021-04-09T00:00:00.000Z'
  185. )}`,
  186. `Processed 2 projects (2 projects with orphaned docs/2 docs deleted) until ${getObjectIdFromDate(
  187. '2021-04-10T00:00:00.000Z'
  188. )}`,
  189. `Processed 3 projects (3 projects with orphaned docs/3 docs deleted) until ${getObjectIdFromDate(
  190. '2021-04-11T00:00:00.000Z'
  191. )}`,
  192. `Processed 4 projects (4 projects with orphaned docs/4 docs deleted) until ${getObjectIdFromDate(
  193. '2021-04-12T00:00:00.000Z'
  194. )}`,
  195. `Processed 5 projects (5 projects with orphaned docs/6 docs deleted) until ${getObjectIdFromDate(
  196. '2021-04-13T00:00:00.000Z'
  197. )}`,
  198. `Processed 6 projects (5 projects with orphaned docs/6 docs deleted) until ${getObjectIdFromDate(
  199. '2021-04-14T00:00:00.000Z'
  200. )}`,
  201. `Processed 7 projects (5 projects with orphaned docs/6 docs deleted) until ${getObjectIdFromDate(
  202. '2021-04-15T00:00:00.000Z'
  203. )}`,
  204. `Processed 7 projects (5 projects with orphaned docs/6 docs deleted) until ${getObjectIdFromDate(
  205. '2021-04-16T00:00:00.000Z'
  206. )}`,
  207. // 7,8,9,10 are on the same day, but exceed the batch size of 3
  208. // Project 9 has two docs.
  209. `Processed 10 projects (8 projects with orphaned docs/10 docs deleted) until ${projectIds[9]}`,
  210. // 10 has as ready been processed as part of the last batch -- same project_id as 9.
  211. `Processed 10 projects (8 projects with orphaned docs/10 docs deleted) until ${oneDayFromProjectId9AsObjectId}`,
  212. 'Done.',
  213. ])
  214. }
  215. describe('DRY_RUN=true', function () {
  216. beforeEach('run script', async function () {
  217. await runScript(true)
  218. })
  219. it('should leave docs as is', async function () {
  220. const docs = await db.docs.find({}).toArray()
  221. expect(docs).to.deep.equal([
  222. { _id: docIds[0], project_id: projectIds[0] },
  223. { _id: docIds[1], project_id: projectIds[1] },
  224. { _id: docIds[2], project_id: projectIds[2] },
  225. { _id: docIds[3], project_id: projectIds[3] },
  226. { _id: docIds[4], project_id: projectIds[4] },
  227. { _id: docIds[5], project_id: projectIds[5] },
  228. { _id: docIds[6], project_id: projectIds[6] },
  229. { _id: docIds[7], project_id: projectIds[7] },
  230. { _id: docIds[8], project_id: projectIds[8] },
  231. { _id: docIds[9], project_id: projectIds[9] },
  232. { _id: docIds[10], project_id: projectIds[10] },
  233. { _id: docIds[11], project_id: projectIds[11] },
  234. ])
  235. })
  236. })
  237. describe('DRY_RUN=false', function () {
  238. beforeEach('run script', async function () {
  239. await runScript(false)
  240. })
  241. it('should deleted all but docs from live/soft-deleted projects', async function () {
  242. const docs = await db.docs.find({}).toArray()
  243. expect(docs).to.deep.equal([
  244. // not orphaned, live
  245. { _id: docIds[5], project_id: projectIds[5] },
  246. // not orphaned, pending hard deletion
  247. { _id: docIds[6], project_id: projectIds[6] },
  248. ])
  249. })
  250. })
  251. })