e2e_test_setup.mjs 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. import fs from 'node:fs'
  2. import Path from 'node:path'
  3. import { fileURLToPath } from 'node:url'
  4. import { promiseMapWithLimit } from '@overleaf/promise-utils'
  5. import Settings from '@overleaf/settings'
  6. import { db } from '../app/src/infrastructure/mongodb.js'
  7. import GracefulShutdown from '../app/src/infrastructure/GracefulShutdown.js'
  8. import ProjectDeleter from '../app/src/Features/Project/ProjectDeleter.js'
  9. import SplitTestManager from '../app/src/Features/SplitTests/SplitTestManager.js'
  10. import UserDeleter from '../app/src/Features/User/UserDeleter.js'
  11. import UserRegistrationHandler from '../app/src/Features/User/UserRegistrationHandler.js'
  12. const MONOREPO = Path.dirname(
  13. Path.dirname(Path.dirname(Path.dirname(fileURLToPath(import.meta.url))))
  14. )
  15. /**
  16. * @param {string} email
  17. * @return {Promise<void>}
  18. */
  19. async function createUser(email) {
  20. const user = await UserRegistrationHandler.promises.registerNewUser({
  21. email,
  22. password: process.env.CYPRESS_DEFAULT_PASSWORD,
  23. })
  24. const features = email.startsWith('free+')
  25. ? Settings.defaultFeatures
  26. : Settings.features.professional
  27. await db.users.updateOne(
  28. { _id: user._id },
  29. {
  30. $set: {
  31. // Set admin flag.
  32. isAdmin: email.startsWith('admin+'),
  33. // Disable spell-checking for performance and flakiness reasons.
  34. 'ace.spellCheckLanguage': '',
  35. // Override features.
  36. features,
  37. featuresOverrides: [{ features }],
  38. },
  39. }
  40. )
  41. }
  42. /**
  43. * @param {string} email
  44. * @return {Promise<void>}
  45. */
  46. async function deleteUser(email) {
  47. const user = await db.users.findOne({ email })
  48. if (!user) return
  49. // Soft delete the user.
  50. await UserDeleter.promises.deleteUser(user._id, {
  51. force: true,
  52. ipAddress: '0.0.0.0',
  53. })
  54. // Hard-delete the users projects.
  55. const projects = await db.deletedProjects
  56. .find(
  57. { deletedProjectOwnerId: user._id },
  58. { projection: { deletedProjectId: 1 } }
  59. )
  60. .toArray()
  61. await promiseMapWithLimit(
  62. 10,
  63. projects.map(p => p.deletedProjectId),
  64. ProjectDeleter.promises.expireDeletedProject
  65. )
  66. // Hard-delete the user.
  67. await UserDeleter.promises.expireDeletedUser(user._id)
  68. }
  69. /**
  70. * @param {string} email
  71. * @return {Promise<void>}
  72. */
  73. async function provisionUser(email) {
  74. if (!email.includes('+')) {
  75. throw new Error(
  76. `email=${email} should include the test suite name, e.g. user+project-sharing@example.com`
  77. )
  78. }
  79. await deleteUser(email)
  80. await createUser(email)
  81. }
  82. async function provisionUsers() {
  83. const emails = Settings.recaptcha.trustedUsers
  84. console.log(`> Provisioning ${emails.length} E2E users.`)
  85. await promiseMapWithLimit(5, emails, provisionUser)
  86. }
  87. async function purgeNewUsers() {
  88. const users = await db.users
  89. .find(
  90. { email: Settings.recaptcha.trustedUsersRegex },
  91. { projection: { email: 1 } }
  92. )
  93. .toArray()
  94. console.log(`> Deleting ${users.length} newly created E2E users.`)
  95. await promiseMapWithLimit(
  96. 5,
  97. users.map(user => user.email),
  98. deleteUser
  99. )
  100. }
  101. const SPLIT_TEST_OVERRIDES = [
  102. // disable writefull, oauth registration does not work in dev-env and their banners hide our buttons.
  103. {
  104. name: 'writefull-auto-account-creation',
  105. versions: [
  106. {
  107. versionNumber: 1,
  108. phase: 'release',
  109. active: true,
  110. analyticsEnabled: false,
  111. variants: [{ name: 'enabled', rolloutPercent: 0, rolloutStripes: [] }],
  112. createdAt: new Date(),
  113. },
  114. ],
  115. },
  116. ]
  117. async function provisionSplitTests() {
  118. const backup = Path.join(
  119. MONOREPO,
  120. 'backup',
  121. 'split-tests',
  122. new Date().toISOString() + '.json'
  123. )
  124. console.log(
  125. `> Backing up previous split-tests into ${backup}. You can import them again on https://www.dev-overleaf.com/admin/split-test via the [Import] button.`
  126. )
  127. const splitTests = await SplitTestManager.getRuntimeTests()
  128. await fs.promises.mkdir(Path.dirname(backup), { recursive: true })
  129. await fs.promises.writeFile(
  130. backup,
  131. JSON.stringify(splitTests.sort((a, b) => (a.name > b.name ? 1 : -1)))
  132. )
  133. // Imported from production via https://www.overleaf.com/admin/split-test -> "Copy all split tests" -> "Copy for E2E test setup"
  134. const SPLIT_TESTS = JSON.parse(
  135. await fs.promises.readFile(
  136. Path.join(MONOREPO, 'tools/saas-e2e/split-tests.json')
  137. )
  138. )
  139. console.log(`> Importing ${SPLIT_TESTS.length} split-tests from production.`)
  140. await SplitTestManager.replaceSplitTests(SPLIT_TESTS)
  141. console.log(
  142. `> Importing ${SPLIT_TEST_OVERRIDES.length} split-tests for test compatibility.`
  143. )
  144. await SplitTestManager.mergeSplitTests(SPLIT_TEST_OVERRIDES, true)
  145. }
  146. async function main() {
  147. if (process.env.NODE_ENV !== 'development') {
  148. throw new Error('only available in dev-env')
  149. }
  150. await Promise.all([purgeNewUsers(), provisionUsers(), provisionSplitTests()])
  151. }
  152. await main()
  153. await GracefulShutdown.gracefulShutdown(
  154. {
  155. close(cb) {
  156. cb()
  157. },
  158. },
  159. 'SIGTERM'
  160. )