DeleteOrphanedDocsOnlineCheckTests.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  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 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.js'])
  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. ])
  157. expect(stdErr).to.deep.equal([
  158. ...`Options: ${JSON.stringify(options, null, 2)}`.split('\n'),
  159. 'Waiting for you to double check inputs for 1 ms',
  160. `Processed 1 projects (1 projects with orphaned docs/1 docs deleted) until ${getObjectIdFromDate(
  161. '2021-04-01T00:00:00.000Z'
  162. )}`,
  163. `Processed 2 projects (2 projects with orphaned docs/2 docs deleted) until ${getObjectIdFromDate(
  164. '2021-04-02T00:00:00.000Z'
  165. )}`,
  166. `Processed 2 projects (2 projects with orphaned docs/2 docs deleted) until ${getObjectIdFromDate(
  167. '2021-04-03T00:00:00.000Z'
  168. )}`,
  169. `Processed 2 projects (2 projects with orphaned docs/2 docs deleted) until ${getObjectIdFromDate(
  170. '2021-04-04T00:00:00.000Z'
  171. )}`,
  172. `Processed 2 projects (2 projects with orphaned docs/2 docs deleted) until ${getObjectIdFromDate(
  173. '2021-04-05T00:00:00.000Z'
  174. )}`,
  175. `Processed 2 projects (2 projects with orphaned docs/2 docs deleted) until ${getObjectIdFromDate(
  176. '2021-04-06T00:00:00.000Z'
  177. )}`,
  178. `Processed 2 projects (2 projects with orphaned docs/2 docs deleted) until ${getObjectIdFromDate(
  179. '2021-04-07T00:00:00.000Z'
  180. )}`,
  181. `Processed 2 projects (2 projects with orphaned docs/2 docs deleted) until ${getObjectIdFromDate(
  182. '2021-04-08T00:00:00.000Z'
  183. )}`,
  184. `Processed 2 projects (2 projects with orphaned docs/2 docs deleted) until ${getObjectIdFromDate(
  185. '2021-04-09T00:00:00.000Z'
  186. )}`,
  187. `Processed 2 projects (2 projects with orphaned docs/2 docs deleted) until ${getObjectIdFromDate(
  188. '2021-04-10T00:00:00.000Z'
  189. )}`,
  190. `Processed 3 projects (3 projects with orphaned docs/3 docs deleted) until ${getObjectIdFromDate(
  191. '2021-04-11T00:00:00.000Z'
  192. )}`,
  193. `Processed 4 projects (4 projects with orphaned docs/4 docs deleted) until ${getObjectIdFromDate(
  194. '2021-04-12T00:00:00.000Z'
  195. )}`,
  196. `Processed 5 projects (5 projects with orphaned docs/6 docs deleted) until ${getObjectIdFromDate(
  197. '2021-04-13T00:00:00.000Z'
  198. )}`,
  199. `Processed 6 projects (5 projects with orphaned docs/6 docs deleted) until ${getObjectIdFromDate(
  200. '2021-04-14T00:00:00.000Z'
  201. )}`,
  202. `Processed 7 projects (5 projects with orphaned docs/6 docs deleted) until ${getObjectIdFromDate(
  203. '2021-04-15T00:00:00.000Z'
  204. )}`,
  205. `Processed 7 projects (5 projects with orphaned docs/6 docs deleted) until ${getObjectIdFromDate(
  206. '2021-04-16T00:00:00.000Z'
  207. )}`,
  208. // 7,8,9,10 are on the same day, but exceed the batch size of 3
  209. // Project 9 has two docs.
  210. `Processed 10 projects (8 projects with orphaned docs/10 docs deleted) until ${projectIds[9]}`,
  211. // 10 has as ready been processed as part of the last batch -- same project_id as 9.
  212. `Processed 10 projects (8 projects with orphaned docs/10 docs deleted) until ${oneDayFromProjectId9AsObjectId}`,
  213. 'Done.',
  214. '',
  215. ])
  216. }
  217. describe('DRY_RUN=true', function () {
  218. beforeEach('run script', async function () {
  219. await runScript(true)
  220. })
  221. it('should leave docs as is', async function () {
  222. const docs = await db.docs.find({}).toArray()
  223. expect(docs).to.deep.equal([
  224. { _id: docIds[0], project_id: projectIds[0] },
  225. { _id: docIds[1], project_id: projectIds[1] },
  226. { _id: docIds[2], project_id: projectIds[2] },
  227. { _id: docIds[3], project_id: projectIds[3] },
  228. { _id: docIds[4], project_id: projectIds[4] },
  229. { _id: docIds[5], project_id: projectIds[5] },
  230. { _id: docIds[6], project_id: projectIds[6] },
  231. { _id: docIds[7], project_id: projectIds[7] },
  232. { _id: docIds[8], project_id: projectIds[8] },
  233. { _id: docIds[9], project_id: projectIds[9] },
  234. { _id: docIds[10], project_id: projectIds[10] },
  235. { _id: docIds[11], project_id: projectIds[11] },
  236. ])
  237. })
  238. })
  239. describe('DRY_RUN=false', function () {
  240. beforeEach('run script', async function () {
  241. await runScript(false)
  242. })
  243. it('should deleted all but docs from live/soft-deleted projects', async function () {
  244. const docs = await db.docs.find({}).toArray()
  245. expect(docs).to.deep.equal([
  246. // not orphaned, live
  247. { _id: docIds[5], project_id: projectIds[5] },
  248. // not orphaned, pending hard deletion
  249. { _id: docIds[6], project_id: projectIds[6] },
  250. ])
  251. })
  252. })
  253. })