Преглед изворни кода

Merge pull request #31889 from overleaf/ar-remove-web-smoke-test

[web] remove smoke test

GitOrigin-RevId: 7911b5e800ef466c59131fd739f95b11a587359f
Andrew Rumble пре 5 месеци
родитељ
комит
a92bf982b0

+ 0 - 41
services/web/app/src/Features/HealthCheck/HealthCheckController.mjs

@@ -2,23 +2,10 @@ import RedisWrapper from '../../infrastructure/RedisWrapper.mjs'
 import settings from '@overleaf/settings'
 import logger from '@overleaf/logger'
 import UserGetter from '../User/UserGetter.mjs'
-import SmokeTests from './../../../../test/smoke/src/SmokeTests.mjs'
-
-const { SmokeTestFailure, runSmokeTests } = SmokeTests
 
 const rclient = RedisWrapper.client('health_check')
 
 export default {
-  check(req, res, next) {
-    if (!settings.siteIsOpen || !settings.editorIsOpen) {
-      // always return successful health checks when site is closed
-      res.sendStatus(200)
-    } else {
-      // detach from express for cleaner stack traces
-      setTimeout(() => runSmokeTestsDetached(req, res).catch(next))
-    }
-  },
-
   checkActiveHandles(req, res, next) {
     if (!(settings.maxActiveHandles > 0) || !process._getActiveHandles) {
       return next()
@@ -94,31 +81,3 @@ export default {
     )
   },
 }
-
-async function runSmokeTestsDetached(req, res) {
-  function isAborted() {
-    return req.destroyed
-  }
-  const stats = { start: new Date(), steps: [] }
-  let status, response
-  try {
-    try {
-      await runSmokeTests({ isAborted, stats })
-    } finally {
-      stats.end = new Date()
-      stats.duration = stats.end - stats.start
-    }
-    status = 200
-    response = { stats }
-  } catch (e) {
-    let err = e
-    if (!(e instanceof SmokeTestFailure)) {
-      err = new SmokeTestFailure('low level error', {}, e)
-    }
-    logger.err({ err, stats }, 'health check failed')
-    status = 500
-    response = { stats, error: err.message }
-  }
-  if (isAborted()) return
-  res.status(status).json(response)
-}

+ 0 - 15
services/web/app/src/router.mjs

@@ -1128,11 +1128,6 @@ async function initialize(webRouter, privateApiRouter, publicApiRouter) {
     plainTextResponse(res, res.locals.csrfToken)
   })
 
-  publicApiRouter.get(
-    '/health_check',
-    HealthCheckController.checkActiveHandles,
-    HealthCheckController.check
-  )
   privateApiRouter.get(
     '/health_check',
     HealthCheckController.checkActiveHandles,
@@ -1148,16 +1143,6 @@ async function initialize(webRouter, privateApiRouter, publicApiRouter) {
     HealthCheckController.checkActiveHandles,
     HealthCheckController.checkApi
   )
-  publicApiRouter.get(
-    '/health_check/full',
-    HealthCheckController.checkActiveHandles,
-    HealthCheckController.check
-  )
-  privateApiRouter.get(
-    '/health_check/full',
-    HealthCheckController.checkActiveHandles,
-    HealthCheckController.check
-  )
 
   publicApiRouter.get('/health_check/redis', HealthCheckController.checkRedis)
   privateApiRouter.get('/health_check/redis', HealthCheckController.checkRedis)

+ 0 - 5
services/web/config/settings.defaults.js

@@ -773,12 +773,7 @@ module.exports = {
   // some basic smoke tests to check the core functionality.
   //
   smokeTest: {
-    user: process.env.SMOKE_TEST_USER,
     userId: process.env.SMOKE_TEST_USER_ID,
-    password: process.env.SMOKE_TEST_PASSWORD,
-    projectId: process.env.SMOKE_TEST_PROJECT_ID,
-    rateLimitSubject: process.env.SMOKE_TEST_RATE_LIMIT_SUBJECT || '127.0.0.1',
-    stepTimeout: parseInt(process.env.SMOKE_TEST_STEP_TIMEOUT || '10000', 10),
   },
 
   appName: process.env.APP_NAME || 'Overleaf (Community Edition)',

+ 0 - 96
services/web/test/acceptance/src/HealthCheckControllerTests.mjs

@@ -1,96 +0,0 @@
-import { expect } from 'chai'
-import Settings from '@overleaf/settings'
-import UserHelper from './helpers/User.mjs'
-
-const User = UserHelper.promises
-
-describe('HealthCheckController', function () {
-  describe('SmokeTests', function () {
-    let user, projectId
-    const captchaDisabledBefore = Settings.recaptcha.disabled.login
-
-    beforeEach(async function () {
-      user = new User()
-      await user.login()
-      projectId = await user.createProject('SmokeTest')
-
-      // HACK: Inject the details into the app
-      Settings.smokeTest.userId = user.id
-      Settings.smokeTest.user = user.email
-      Settings.smokeTest.password = user.password
-      Settings.smokeTest.projectId = projectId
-
-      Settings.recaptcha.disabled.login = true
-    })
-    afterEach(function () {
-      Settings.recaptcha.disabled.login = captchaDisabledBefore
-    })
-
-    async function performSmokeTestRequest() {
-      const start = Date.now()
-      const { response, body } = await user.doRequest('GET', {
-        url: '/health_check/full',
-        json: true,
-      })
-      const end = Date.now()
-
-      expect(body).to.exist
-      expect(body.stats).to.exist
-      expect(Date.parse(body.stats.start)).to.be.within(start, start + 1000)
-      expect(Date.parse(body.stats.end)).to.be.within(end - 1000, end)
-
-      expect(body.stats.duration).to.be.within(0, 10000)
-      expect(body.stats.steps).to.be.instanceof(Array)
-      return { response, body }
-    }
-
-    describe('happy path', function () {
-      it('should respond with a 200 and stats', async function () {
-        const { response, body } = await performSmokeTestRequest()
-
-        expect(body.error).to.not.exist
-        expect(response.statusCode).to.equal(200)
-      })
-    })
-
-    describe('when the request is aborted', function () {
-      it('should not crash', async function () {
-        try {
-          await user.doRequest('GET', {
-            timeout: 1,
-            url: '/health_check/full',
-            json: true,
-          })
-        } catch (err) {
-          expect(err.code).to.be.oneOf(['ETIMEDOUT', 'ESOCKETTIMEDOUT'])
-          return
-        }
-        expect.fail('expected request to fail with timeout error')
-      })
-    })
-
-    describe('when the project does not exist', function () {
-      beforeEach(function () {
-        Settings.smokeTest.projectId = '404'
-      })
-      it('should respond with a 500 ', async function () {
-        const { response, body } = await performSmokeTestRequest()
-
-        expect(body.error).to.equal('run.101_loadEditor failed')
-        expect(response.statusCode).to.equal(500)
-      })
-    })
-
-    describe('when the password mismatches', function () {
-      beforeEach(function () {
-        Settings.smokeTest.password = 'foo-bar'
-      })
-      it('should respond with a 500 with mismatching password', async function () {
-        const { response, body } = await performSmokeTestRequest()
-
-        expect(body.error).to.equal('run.002_login failed')
-        expect(response.statusCode).to.equal(500)
-      })
-    })
-  })
-})

+ 0 - 40
services/web/test/smoke/src/README.md

@@ -1,40 +0,0 @@
-# SmokeTests
-
-For the SmokeTests we implemented a Mini-Framework that is tailored for our
-tooling, specifically OError, and does not need a large runner, such as mocha.
-
-The SmokeTests are separated into individual `steps`.
-Each `step` can have a `run` function and a `cleanup` function.
-The former will run in sequence with the other steps, the later in reverse
-order from the finish, or the last failure.
-
-```js
-async function run(ctx) {
-  // do something
-}
-async function cleanup(ctx) {
-  // cleanup something
-}
-module.exports = { cleanup, run }
-```
-
-Steps will get called with a context object with common helpers and details:
-
-- `request` a promisified `request` module with defaults for `baseUrl`,
-  `timeout` and internals for cookie handling.
-- `assertHasStatusCode` a helper for asserting response status codes, pass
-  a response and desired status code. It will throw with OError context set.
-- `getCsrfTokenFor` a helper for retrieving CSRF tokens, pass an endpoint.
-- `processWithTimeout` a helper for awaiting Promises with a timeout, pass
-  `{ work: Promise.resolve(), timeout: 42, message: 'foo timedout' }`
-- `stats` an object for performance tracking.
-- `timeout` the step timeout
-
-Steps should handle timeouts locally to ensure appropriate cleanup of timed out
-actions.
-
-Steps may pass values along to the next steps in returning an object with the
-desired fields from the `run` or `cleanup` function.
-The returned values will overwrite existing details in the `ctx`.
-
-Alpha-numeric sorting of step filenames determines the processing sequence.

+ 0 - 95
services/web/test/smoke/src/SmokeTests.mjs

@@ -1,95 +0,0 @@
-import fs from 'fs'
-import Path from 'path'
-import Settings from '@overleaf/settings'
-import { getCsrfTokenForFactory } from './support/Csrf.mjs'
-import { SmokeTestFailure } from './support/Errors.mjs'
-import {
-  requestFactory,
-  assertHasStatusCode,
-} from './support/requestHelper.mjs'
-import { processWithTimeout } from './support/timeoutHelper.mjs'
-
-const STEP_TIMEOUT = Settings.smokeTest.stepTimeout
-
-const PATH_STEPS = Path.join(import.meta.dirname, './steps')
-const sortedSteps = fs.readdirSync(PATH_STEPS).sort()
-
-const STEPS = []
-
-for (const name of sortedSteps) {
-  const step = (await import(Path.join(PATH_STEPS, name))).default
-  step.name = Path.basename(name, '.mjs')
-  STEPS.push(step)
-}
-
-async function runSmokeTests({ isAborted, stats }) {
-  let lastStep = stats.start
-  function completeStep(key) {
-    const step = Date.now()
-    stats.steps.push({ [key]: step - lastStep })
-    lastStep = step
-  }
-
-  const request = requestFactory({ timeout: STEP_TIMEOUT })
-  const getCsrfTokenFor = getCsrfTokenForFactory({ request })
-  const ctx = {
-    assertHasStatusCode,
-    getCsrfTokenFor,
-    processWithTimeout,
-    request,
-    stats,
-    timeout: STEP_TIMEOUT,
-  }
-  const cleanupSteps = []
-
-  async function runAndTrack(id, fn) {
-    let result
-    try {
-      result = await fn(ctx)
-    } catch (e) {
-      throw new SmokeTestFailure(`${id} failed`, {}, e)
-    } finally {
-      completeStep(id)
-    }
-    Object.assign(ctx, result)
-  }
-
-  completeStep('init')
-
-  let err
-  try {
-    for (const step of STEPS) {
-      if (isAborted()) break
-
-      const { name, run, cleanup } = step
-      if (cleanup) cleanupSteps.unshift({ name, cleanup })
-
-      await runAndTrack(`run.${name}`, run)
-    }
-  } catch (e) {
-    err = e
-  }
-
-  const cleanupErrors = []
-  for (const step of cleanupSteps) {
-    const { name, cleanup } = step
-
-    try {
-      await runAndTrack(`cleanup.${name}`, cleanup)
-    } catch (e) {
-      // keep going with cleanup
-      cleanupErrors.push(e)
-    }
-  }
-
-  if (err) throw err
-  if (cleanupErrors.length) {
-    if (cleanupErrors.length === 1) throw cleanupErrors[0]
-    throw new SmokeTestFailure('multiple cleanup steps failed', {
-      stats,
-      cleanupErrors,
-    })
-  }
-}
-
-export default { runSmokeTests, SmokeTestFailure }

+ 0 - 7
services/web/test/smoke/src/steps/000_getLoginCsrf.mjs

@@ -1,7 +0,0 @@
-async function run({ getCsrfTokenFor }) {
-  const loginCsrfToken = await getCsrfTokenFor('/login')
-
-  return { loginCsrfToken }
-}
-
-export default { run }

+ 0 - 35
services/web/test/smoke/src/steps/001_clearRateLimits.mjs

@@ -1,35 +0,0 @@
-import Settings from '@overleaf/settings'
-import {
-  overleafLoginRateLimiter,
-  openProjectRateLimiter,
-} from '../../../../app/src/infrastructure/RateLimiter.mjs'
-import LoginRateLimiter from '../../../../app/src/Features/Security/LoginRateLimiter.mjs'
-
-async function clearLoginRateLimit() {
-  await LoginRateLimiter.promises.recordSuccessfulLogin(Settings.smokeTest.user)
-}
-
-async function clearOverleafLoginRateLimit() {
-  if (!Settings.overleaf) return
-  await overleafLoginRateLimiter.delete(Settings.smokeTest.rateLimitSubject)
-}
-
-async function clearOpenProjectRateLimit() {
-  await openProjectRateLimiter.delete(
-    `${Settings.smokeTest.projectId}:${Settings.smokeTest.userId}`
-  )
-}
-
-async function run({ processWithTimeout, timeout }) {
-  await processWithTimeout({
-    work: Promise.all([
-      clearLoginRateLimit(),
-      clearOverleafLoginRateLimit(),
-      clearOpenProjectRateLimit(),
-    ]),
-    timeout,
-    message: 'cleanupRateLimits timed out',
-  })
-}
-
-export default { run }

+ 0 - 35
services/web/test/smoke/src/steps/002_login.mjs

@@ -1,35 +0,0 @@
-import Settings from '@overleaf/settings'
-
-async function run({ assertHasStatusCode, loginCsrfToken, request }) {
-  const response = await request('/login', {
-    method: 'POST',
-    json: {
-      _csrf: loginCsrfToken,
-      email: Settings.smokeTest.user,
-      password: Settings.smokeTest.password,
-    },
-  })
-
-  const body = response.body
-  // login success and login failure both receive a status code of 200
-  // see the frontend logic on how to handle the response:
-  //   frontend/js/directives/asyncForm.js -> submitRequest
-  if (body && body.message && body.message.type === 'error') {
-    throw new Error(`login failed: ${body.message.text}`)
-  }
-
-  assertHasStatusCode(response, 200)
-}
-
-async function cleanup({ assertHasStatusCode, getCsrfTokenFor, request }) {
-  const logoutCsrfToken = await getCsrfTokenFor('/project')
-  const response = await request('/logout', {
-    method: 'POST',
-    headers: {
-      'X-CSRF-Token': logoutCsrfToken,
-    },
-  })
-  assertHasStatusCode(response, 302)
-}
-
-export default { cleanup, run }

+ 0 - 14
services/web/test/smoke/src/steps/100_loadProjectDashboard.mjs

@@ -1,14 +0,0 @@
-const TITLE_REGEX =
-  /<title[^>]*>Your projects - .*, Online LaTeX Editor<\/title>/
-
-async function run({ request, assertHasStatusCode }) {
-  const response = await request('/project')
-
-  assertHasStatusCode(response, 200)
-
-  if (!TITLE_REGEX.test(response.body)) {
-    throw new Error('body does not have correct title')
-  }
-}
-
-export default { run }

+ 0 - 16
services/web/test/smoke/src/steps/101_loadEditor.mjs

@@ -1,16 +0,0 @@
-import Settings from '@overleaf/settings'
-
-async function run({ assertHasStatusCode, request }) {
-  const response = await request(`/project/${Settings.smokeTest.projectId}`)
-
-  assertHasStatusCode(response, 200)
-
-  const PROJECT_ID_REGEX = new RegExp(
-    `<meta name="ol-project_id" content="${Settings.smokeTest.projectId}">`
-  )
-  if (!PROJECT_ID_REGEX.test(response.body)) {
-    throw new Error('project page html does not have project_id')
-  }
-}
-
-export default { run }

+ 0 - 23
services/web/test/smoke/src/support/Csrf.mjs

@@ -1,23 +0,0 @@
-import OError from '@overleaf/o-error'
-import { assertHasStatusCode } from './requestHelper.mjs'
-const CSRF_REGEX = /<meta name="ol-csrfToken" content="(.+?)">/
-
-export function _parseCsrf(body) {
-  const match = CSRF_REGEX.exec(body)
-  if (!match) {
-    throw new Error('Cannot find csrfToken in HTML')
-  }
-  return match[1]
-}
-
-export function getCsrfTokenForFactory({ request }) {
-  return async function getCsrfTokenFor(endpoint) {
-    try {
-      const response = await request(endpoint)
-      assertHasStatusCode(response, 200)
-      return _parseCsrf(response.body)
-    } catch (err) {
-      throw new OError('error fetching csrf token', { endpoint }, err)
-    }
-  }
-}

+ 0 - 3
services/web/test/smoke/src/support/Errors.mjs

@@ -1,3 +0,0 @@
-import OError from '@overleaf/o-error'
-
-export class SmokeTestFailure extends OError {}

+ 0 - 54
services/web/test/smoke/src/support/requestHelper.mjs

@@ -1,54 +0,0 @@
-import { Agent } from 'node:http'
-import { createConnection } from 'node:net'
-import { promisify } from 'node:util'
-import OError from '@overleaf/o-error'
-import request from 'request'
-import Settings from '@overleaf/settings'
-
-// send requests to web router if this is the api process
-const OWN_PORT = Settings.port || Settings.internal.web.port || 3000
-const PORT = (Settings.web && Settings.web.web_router_port) || OWN_PORT
-
-// like the curl option `--resolve DOMAIN:PORT:127.0.0.1`
-class LocalhostAgent extends Agent {
-  createConnection(options, callback) {
-    return createConnection(PORT, '127.0.0.1', callback)
-  }
-}
-
-// degrade the 'HttpOnly; Secure;' flags of the cookie
-class InsecureCookieJar extends request.jar().constructor {
-  setCookie(...args) {
-    const cookie = super.setCookie(...args)
-    cookie.secure = false
-    cookie.httpOnly = false
-    return cookie
-  }
-}
-
-export function requestFactory({ timeout }) {
-  return promisify(
-    request.defaults({
-      agent: new LocalhostAgent(),
-      baseUrl: `http://smoke${Settings.cookieDomain}`,
-      headers: {
-        // emulate the header of a https proxy
-        // express wont emit a 'Secure;' cookie on a plain-text connection.
-        'X-Forwarded-Proto': 'https',
-      },
-      jar: new InsecureCookieJar(),
-      timeout,
-    })
-  )
-}
-
-export function assertHasStatusCode(response, expected) {
-  const { statusCode: actual } = response
-  if (actual !== expected) {
-    throw new OError('unexpected response code', {
-      url: response.request.uri.href,
-      actual,
-      expected,
-    })
-  }
-}

+ 0 - 14
services/web/test/smoke/src/support/timeoutHelper.mjs

@@ -1,14 +0,0 @@
-export async function processWithTimeout({ work, timeout, message }) {
-  let workDeadLine
-  function checkInResults() {
-    clearTimeout(workDeadLine)
-  }
-  await Promise.race([
-    new Promise((resolve, reject) => {
-      workDeadLine = setTimeout(() => {
-        reject(new Error(message))
-      }, timeout)
-    }),
-    work.finally(checkInResults),
-  ])
-}

+ 0 - 1
services/web/test/smoke/tsconfig.json

@@ -1 +0,0 @@
-{ "extends": "../../tsconfig.backend.json" }

+ 0 - 2
services/web/tsconfig.backend.json

@@ -4,11 +4,9 @@
     "app/src/**/*",
     "modules/*/app/src/**/*",
     "modules/*/test/acceptance/**/*",
-    "modules/*/test/smoke/**/*",
     "modules/*/test/unit/**/*",
     "scripts/**/*",
     "test/acceptance/**/*",
-    "test/smoke/**/*",
     "test/unit/**/*",
     "types/backend/**/*"
   ]