DeleteOrphanedDocsOnlineCheckTests.js 11 KB

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