Prechádzať zdrojové kódy

Merge pull request #29967 from overleaf/ar/web-remove-mocha-unit-tests

[web] remove mocha unit tests

GitOrigin-RevId: eda753af3470dbd1f385cb0bc3f06d78ade8a764
Andrew Rumble 8 mesiacov pred
rodič
commit
f1e788d9b3
49 zmenil súbory, kde vykonal 295 pridanie a 565 odobranie
  1. 2 5
      services/web/.eslintrc.js
  2. 13 23
      services/web/Makefile
  3. 3 2
      services/web/bin/lint_flag_res_send_usage
  4. 6 36
      services/web/bin/test_unit_run_dir
  5. 39 45
      services/web/modules/launchpad/test/unit/src/LaunchpadController.test.mjs
  6. 3 6
      services/web/package.json
  7. 7 20
      services/web/test/acceptance/src/mocks/MockResponse.mjs
  8. 0 124
      services/web/test/unit/bootstrap.js
  9. 5 2
      services/web/test/unit/bootstrap.mjs
  10. 0 5
      services/web/test/unit/common_bootstrap.js
  11. 1 1
      services/web/test/unit/src/Analytics/AnalyticsController.test.mjs
  12. 2 2
      services/web/test/unit/src/Analytics/AnalyticsManager.test.mjs
  13. 2 2
      services/web/test/unit/src/Analytics/AnalyticsUTMTrackingMiddleware.test.mjs
  14. 2 2
      services/web/test/unit/src/Authentication/AuthenticationController.test.mjs
  15. 1 1
      services/web/test/unit/src/BetaProgram/BetaProgramController.test.mjs
  16. 2 2
      services/web/test/unit/src/Collaborators/CollaboratorsController.test.mjs
  17. 2 2
      services/web/test/unit/src/Collaborators/CollaboratorsInviteController.test.mjs
  18. 2 2
      services/web/test/unit/src/Compile/CompileController.test.mjs
  19. 1 1
      services/web/test/unit/src/Contact/ContactController.test.mjs
  20. 6 7
      services/web/test/unit/src/DocumentUpdater/DocumentUpdaterController.test.mjs
  21. 4 4
      services/web/test/unit/src/Documents/DocumentController.test.mjs
  22. 6 10
      services/web/test/unit/src/Downloads/ProjectDownloadsController.test.mjs
  23. 2 2
      services/web/test/unit/src/Editor/EditorHttpController.test.mjs
  24. 6 6
      services/web/test/unit/src/Errors/HttpErrorHandler.test.mjs
  25. 6 6
      services/web/test/unit/src/FileStore/FileStoreController.test.mjs
  26. 28 28
      services/web/test/unit/src/HelperFiles/AdminAuthorizationHelper.test.mjs
  27. 15 12
      services/web/test/unit/src/Metadata/MetaController.test.mjs
  28. 43 52
      services/web/test/unit/src/PasswordReset/PasswordResetController.test.mjs
  29. 4 4
      services/web/test/unit/src/References/ReferencesController.test.mjs
  30. 4 2
      services/web/test/unit/src/Settings/Settings.test.mjs
  31. 4 4
      services/web/test/unit/src/SplitTests/SplitTestHandler.test.mjs
  32. 4 4
      services/web/test/unit/src/SplitTests/SplitTestMiddleware.test.mjs
  33. 21 23
      services/web/test/unit/src/Subscription/SubscriptionController.test.mjs
  34. 3 3
      services/web/test/unit/src/Subscription/SubscriptionGroupHandler.test.mjs
  35. 4 4
      services/web/test/unit/src/ThirdPartyDataStore/TpdsController.test.mjs
  36. 15 15
      services/web/test/unit/src/TokenAccess/TokenAccessController.test.mjs
  37. 4 4
      services/web/test/unit/src/Uploads/ProjectUploadController.test.mjs
  38. 2 2
      services/web/test/unit/src/User/UserEmailsController.test.mjs
  39. 6 6
      services/web/test/unit/src/User/UserInfoController.test.mjs
  40. 4 4
      services/web/test/unit/src/User/UserPagesController.test.mjs
  41. 2 2
      services/web/test/unit/src/UserMembership/UserMembershipController.test.mjs
  42. 0 38
      services/web/test/unit/src/helpers/MockClient.js
  43. 0 32
      services/web/test/unit/src/helpers/MockRequest.js
  44. 0 0
      services/web/test/unit/src/helpers/MockRequest.mjs
  45. 0 0
      services/web/test/unit/src/helpers/MockResponse.mjs
  46. 2 2
      services/web/test/unit/src/infrastructure/HTTPPermissionsPolicy.test.mjs
  47. 1 1
      services/web/test/unit/src/infrastructure/RequestContentTypeDetection.test.mjs
  48. 4 4
      services/web/test/unit/src/infrastructure/ServeStaticWrapper.test.mjs
  49. 2 1
      services/web/vitest.config.js

+ 2 - 5
services/web/.eslintrc.js

@@ -71,7 +71,7 @@ module.exports = {
       files: ['**/test/**/*.*'],
       files: ['**/test/**/*.*'],
       excludedFiles: [
       excludedFiles: [
         '**/test/unit/src/**/*.test.mjs',
         '**/test/unit/src/**/*.test.mjs',
-        'test/unit/vitest_bootstrap.mjs',
+        'test/unit/bootstrap.mjs',
       ], // exclude vitest files
       ], // exclude vitest files
       plugins: ['mocha', 'chai-expect', 'chai-friendly'],
       plugins: ['mocha', 'chai-expect', 'chai-friendly'],
       env: {
       env: {
@@ -105,10 +105,7 @@ module.exports = {
       },
       },
     },
     },
     {
     {
-      files: [
-        '**/test/unit/src/**/*.test.mjs',
-        'test/unit/vitest_bootstrap.mjs',
-      ],
+      files: ['**/test/unit/src/**/*.test.mjs', 'test/unit/bootstrap.mjs'],
       env: {
       env: {
         jest: true, // best match for vitest API etc.
         jest: true, // best match for vitest API etc.
       },
       },

+ 13 - 23
services/web/Makefile

@@ -101,29 +101,19 @@ test_unit_app: export COMPOSE_PROJECT_NAME=unit_test_$(BUILD_DIR_NAME)
 test_unit_app: mongo_migrations_for_tests
 test_unit_app: mongo_migrations_for_tests
 	$(DOCKER_COMPOSE) run --name unit_test_$(BUILD_DIR_NAME) --rm test_unit
 	$(DOCKER_COMPOSE) run --name unit_test_$(BUILD_DIR_NAME) --rm test_unit
 
 
-test_unit_mocha: export MOCHA_ROOT_SUITE_NAME = Mocha unit tests
-test_unit_mocha: export COMPOSE_PROJECT_NAME=unit_test_mocha_$(BUILD_DIR_NAME)
-test_unit_mocha: mongo_migrations_for_tests
-	$(DOCKER_COMPOSE) run --rm test_unit npm run test:unit:mocha
-
-test_unit_esm: export VITEST_ROOT_SUITE_NAME = ESM unit tests - parallel
-test_unit_esm: export COMPOSE_PROJECT_NAME=unit_test_esm_parallel_$(BUILD_DIR_NAME)
-test_unit_esm: mongo_migrations_for_tests
-	$(DOCKER_COMPOSE) run --rm test_unit npm run test:unit:esm
-
-test_unit_esm_parallel: export VITEST_ROOT_SUITE_NAME = ESM unit tests - parallel
-test_unit_esm_parallel: export COMPOSE_PROJECT_NAME=unit_test_esm_parallel_$(BUILD_DIR_NAME)
-test_unit_esm_parallel: mongo_migrations_for_tests
-	$(DOCKER_COMPOSE) run --rm test_unit npm run test:unit:esm:parallel
-
-test_unit_esm_sequential: export VITEST_ROOT_SUITE_NAME = ESM unit tests - sequential
-test_unit_esm_sequential: export COMPOSE_PROJECT_NAME=unit_test_esm_sequential_$(BUILD_DIR_NAME)
-test_unit_esm_sequential: mongo_migrations_for_tests
-	$(DOCKER_COMPOSE) run --rm test_unit npm run test:unit:esm:sequential
-
-test_unit_esm_watch: export COMPOSE_PROJECT_NAME=unit_test_esm_watch_$(BUILD_DIR_NAME)
-test_unit_esm_watch: mongo_migrations_for_tests
-	$(DOCKER_COMPOSE) run --rm test_unit npm run test:unit:esm:watch
+test_unit_parallel: export VITEST_ROOT_SUITE_NAME = ESM unit tests - parallel
+test_unit_parallel: export COMPOSE_PROJECT_NAME=unit_test_parallel_$(BUILD_DIR_NAME)
+test_unit_parallel: mongo_migrations_for_tests
+	$(DOCKER_COMPOSE) run --rm test_unit npm run test:unit:parallel
+
+test_unit_sequential: export VITEST_ROOT_SUITE_NAME = ESM unit tests - sequential
+test_unit_sequential: export COMPOSE_PROJECT_NAME=unit_test_sequential_$(BUILD_DIR_NAME)
+test_unit_sequential: mongo_migrations_for_tests
+	$(DOCKER_COMPOSE) run --rm test_unit npm run test:unit:sequential
+
+test_unit_watch: export COMPOSE_PROJECT_NAME=unit_test_watch_$(BUILD_DIR_NAME)
+test_unit_watch: mongo_migrations_for_tests
+	$(DOCKER_COMPOSE) run --rm test_unit npm run test:unit:watch
 
 
 TEST_SUITES = $(sort $(filter-out \
 TEST_SUITES = $(sort $(filter-out \
 	$(wildcard test/unit/src/helpers/*), \
 	$(wildcard test/unit/src/helpers/*), \

+ 3 - 2
services/web/bin/lint_flag_res_send_usage

@@ -15,12 +15,13 @@ POTENTIAL_SEND_USAGE=$(\
     --regex "\bsend(" \
     --regex "\bsend(" \
 )
 )
 HELPER_MODULE="app/src/infrastructure/Response.mjs"
 HELPER_MODULE="app/src/infrastructure/Response.mjs"
-if [[ "$POTENTIAL_SEND_USAGE" == "$HELPER_MODULE" ]]; then
+MOCK_MODULE="test/acceptance/src/mocks/MockResponse.mjs"
+if [[ "$POTENTIAL_SEND_USAGE" == "$HELPER_MODULE" ]] || [[ "$file" == "$MOCK_MODULE" ]]; then
   exit 0
   exit 0
 fi
 fi
 
 
 for file in ${POTENTIAL_SEND_USAGE}; do
 for file in ${POTENTIAL_SEND_USAGE}; do
-  if [[ "$file" == "$HELPER_MODULE" ]]; then
+  if [[ "$file" == "$HELPER_MODULE" ]] || [[ "$file" == "$MOCK_MODULE" ]]; then
     continue
     continue
   fi
   fi
 
 

+ 6 - 36
services/web/bin/test_unit_run_dir

@@ -2,19 +2,6 @@
 
 
 declare -a vitest_args=("$@")
 declare -a vitest_args=("$@")
 
 
-has_mocha_test=0
-has_sequential_test=0
-
-for dir_path in "$@"; do
-  if [ -n "$(find "$dir_path" -name "*.js" -type f -print -quit 2>/dev/null)" ]; then
-    has_mocha_test=1
-  fi
-
-  if [ -n "$(find "$dir_path" -name "*.sequential.test.mjs" -type f -print -quit 2>/dev/null)" ]; then
-    has_sequential_test=1
-  fi
-done
-
 if [[ -n "$MOCHA_GREP" ]]; then
 if [[ -n "$MOCHA_GREP" ]]; then
   vitest_args+=("--testNamePattern" "$MOCHA_GREP")
   vitest_args+=("--testNamePattern" "$MOCHA_GREP")
 fi
 fi
@@ -26,41 +13,24 @@ fi
 
 
 echo "Running unit tests in directory: $*"
 echo "Running unit tests in directory: $*"
 
 
-
-npm run test:unit:esm:parallel -- "${vitest_args[@]}"
+npm run test:unit:parallel -- "${vitest_args[@]}"
 vitest_parallel_status=$?
 vitest_parallel_status=$?
-if (( has_sequential_test == 0 )); then
-  echo "No sequential vitest tests found, skipping sequential vitest step."
-  vitest_sequential_status=0
-else
-  npm run test:unit:esm:sequential -- "${vitest_args[@]}"
-  vitest_sequential_status=$?
-fi
 
 
-if (( has_mocha_test == 1 )); then
-  mocha --recursive --timeout 25000 --exit --grep="$MOCHA_GREP" --require test/unit/bootstrap.js --extension=js "$@"
-  mocha_status=$?
-else
-  echo "No mocha tests found, skipping mocha step."
-  mocha_status=0
-fi
+npm run test:unit:sequential -- "${vitest_args[@]}"
+vitest_sequential_status=$?
 
 
-if [ "$mocha_status" -eq 0 ] && [ "$vitest_sequential_status" -eq 0 ] && [ "$vitest_parallel_status" -eq 0 ]; then
+if [ "$vitest_sequential_status" -eq 0 ] && [ "$vitest_parallel_status" -eq 0 ]; then
   exit 0
   exit 0
 fi
 fi
 
 
 # Report status briefly at the end for failures
 # Report status briefly at the end for failures
 
 
-if [ "$mocha_status" -ne 0 ]; then
-  echo "Mocha tests failed with status: $mocha_status"
-fi
-
 if [ "$vitest_parallel_status" -ne 0 ]; then
 if [ "$vitest_parallel_status" -ne 0 ]; then
-  echo "Vitest parallel tests failed with status: $vitest_parallel_status"
+  echo "Parallel tests failed with status: $vitest_parallel_status"
 fi
 fi
 
 
 if [ "$vitest_sequential_status" -ne 0 ]; then
 if [ "$vitest_sequential_status" -ne 0 ]; then
-  echo "Vitest sequential tests failed with status: $vitest_sequential_status"
+  echo "Sequential tests failed with status: $vitest_sequential_status"
 fi
 fi
 
 
 exit 1
 exit 1

+ 39 - 45
services/web/modules/launchpad/test/unit/src/LaunchpadController.test.mjs

@@ -1,7 +1,7 @@
 import { beforeEach, describe, expect, it, vi } from 'vitest'
 import { beforeEach, describe, expect, it, vi } from 'vitest'
 import * as path from 'node:path'
 import * as path from 'node:path'
 import sinon from 'sinon'
 import sinon from 'sinon'
-import MockResponse from '../../../../../test/unit/src/helpers/MockResponse.js'
+import MockResponse from '../../../../../test/unit/src/helpers/MockResponse.mjs'
 
 
 const modulePath = path.join(
 const modulePath = path.join(
   import.meta.dirname,
   import.meta.dirname,
@@ -9,10 +9,6 @@ const modulePath = path.join(
 )
 )
 
 
 describe('LaunchpadController', function () {
 describe('LaunchpadController', function () {
-  // esmock doesn't work well with CommonJS dependencies, global imports for
-  // @overleaf/settings aren't working until that module is migrated to ESM. In the
-  // meantime, the workaround is to set and restore settings values
-
   beforeEach(async function (ctx) {
   beforeEach(async function (ctx) {
     ctx.user = {
     ctx.user = {
       _id: '323123',
       _id: '323123',
@@ -89,7 +85,7 @@ describe('LaunchpadController', function () {
       session: {},
       session: {},
     }
     }
 
 
-    ctx.res = new MockResponse()
+    ctx.res = new MockResponse(vi)
     ctx.res.locals = {
     ctx.res.locals = {
       translate(key) {
       translate(key) {
         return key
         return key
@@ -127,8 +123,8 @@ describe('LaunchpadController', function () {
             import.meta.dirname,
             import.meta.dirname,
             '../../../app/views/launchpad'
             '../../../app/views/launchpad'
           )
           )
-          ctx.res.render.callCount.should.equal(1)
-          expect(ctx.res.render).to.have.been.calledWith(viewPath, {
+          expect(ctx.res.render).toHaveBeenCalledTimes(1)
+          expect(ctx.res.render).toHaveBeenCalledWith(viewPath, {
             adminUserExists: false,
             adminUserExists: false,
             authMethod: 'local',
             authMethod: 'local',
           })
           })
@@ -149,11 +145,11 @@ describe('LaunchpadController', function () {
           ctx.AuthenticationController.setRedirectInSession.callCount.should.equal(
           ctx.AuthenticationController.setRedirectInSession.callCount.should.equal(
             1
             1
           )
           )
-          ctx.res.redirect.calledWith('/login').should.equal(true)
+          expect(ctx.res.redirect).toHaveBeenCalledWith('/login')
         })
         })
 
 
         it('should not render the launchpad page', function (ctx) {
         it('should not render the launchpad page', function (ctx) {
-          ctx.res.render.callCount.should.equal(0)
+          expect(ctx.res.render).not.toHaveBeenCalled()
         })
         })
       })
       })
     })
     })
@@ -185,8 +181,8 @@ describe('LaunchpadController', function () {
             import.meta.dirname,
             import.meta.dirname,
             '../../../app/views/launchpad'
             '../../../app/views/launchpad'
           )
           )
-          ctx.res.render.callCount.should.equal(1)
-          expect(ctx.res.render).to.have.been.calledWith(viewPath, {
+          expect(ctx.res.render).toHaveBeenCalledTimes(1)
+          expect(ctx.res.render).toHaveBeenCalledWith(viewPath, {
             wsUrl: undefined,
             wsUrl: undefined,
             adminUserExists: true,
             adminUserExists: true,
             authMethod: 'local',
             authMethod: 'local',
@@ -207,8 +203,8 @@ describe('LaunchpadController', function () {
         })
         })
 
 
         it('should redirect to restricted page', function (ctx) {
         it('should redirect to restricted page', function (ctx) {
-          ctx.res.redirect.callCount.should.equal(1)
-          ctx.res.redirect.calledWith('/restricted').should.equal(true)
+          expect(ctx.res.redirect).toHaveBeenCalledTimes(1)
+          expect(ctx.res.redirect).toHaveBeenCalledWith('/restricted')
         })
         })
       })
       })
     })
     })
@@ -258,7 +254,7 @@ describe('LaunchpadController', function () {
 
 
     it('should produce a 200 response', async function (ctx) {
     it('should produce a 200 response', async function (ctx) {
       await ctx.LaunchpadController.sendTestEmail(ctx.req, ctx.res, ctx.next)
       await ctx.LaunchpadController.sendTestEmail(ctx.req, ctx.res, ctx.next)
-      ctx.res.json.calledWith({ message: 'email_sent' }).should.equal(true)
+      expect(ctx.res.json).toHaveBeenCalledWith({ message: 'email_sent' })
     })
     })
 
 
     it('should not call next with an error', function (ctx) {
     it('should not call next with an error', function (ctx) {
@@ -300,12 +296,10 @@ describe('LaunchpadController', function () {
 
 
       it('should produce a 400 response', function (ctx) {
       it('should produce a 400 response', function (ctx) {
         ctx.LaunchpadController.sendTestEmail(ctx.req, ctx.res, ctx.next)
         ctx.LaunchpadController.sendTestEmail(ctx.req, ctx.res, ctx.next)
-        ctx.res.status.calledWith(400).should.equal(true)
-        ctx.res.json
-          .calledWith({
-            message: 'no email address supplied',
-          })
-          .should.equal(true)
+        expect(ctx.res.status).toHaveBeenCalledWith(400)
+        expect(ctx.res.json).toHaveBeenCalledWith({
+          message: 'no email address supplied',
+        })
       })
       })
     })
     })
   })
   })
@@ -341,8 +335,8 @@ describe('LaunchpadController', function () {
       })
       })
 
 
       it('should send back a json response', function (ctx) {
       it('should send back a json response', function (ctx) {
-        ctx.res.json.callCount.should.equal(1)
-        expect(ctx.res.json).to.have.been.calledWith({ redir: '/launchpad' })
+        expect(ctx.res.json).toHaveBeenCalledTimes(1)
+        expect(ctx.res.json).toHaveBeenCalledWith({ redir: '/launchpad' })
       })
       })
 
 
       it('should have checked for existing admins', function (ctx) {
       it('should have checked for existing admins', function (ctx) {
@@ -394,8 +388,8 @@ describe('LaunchpadController', function () {
       })
       })
 
 
       it('should send a 400 response', function (ctx) {
       it('should send a 400 response', function (ctx) {
-        ctx.res.sendStatus.callCount.should.equal(1)
-        ctx.res.sendStatus.calledWith(400).should.equal(true)
+        expect(ctx.res.sendStatus).toHaveBeenCalledTimes(1)
+        expect(ctx.res.sendStatus).toHaveBeenCalledWith(400)
       })
       })
 
 
       it('should not check for existing admins', function (ctx) {
       it('should not check for existing admins', function (ctx) {
@@ -427,8 +421,8 @@ describe('LaunchpadController', function () {
       })
       })
 
 
       it('should send a 400 response', function (ctx) {
       it('should send a 400 response', function (ctx) {
-        ctx.res.sendStatus.callCount.should.equal(1)
-        ctx.res.sendStatus.calledWith(400).should.equal(true)
+        expect(ctx.res.sendStatus).toHaveBeenCalledTimes(1)
+        expect(ctx.res.sendStatus).toHaveBeenCalledWith(400)
       })
       })
 
 
       it('should not check for existing admins', function (ctx) {
       it('should not check for existing admins', function (ctx) {
@@ -464,9 +458,9 @@ describe('LaunchpadController', function () {
       })
       })
 
 
       it('should send a 400 response', function (ctx) {
       it('should send a 400 response', function (ctx) {
-        ctx.res.status.callCount.should.equal(1)
-        ctx.res.status.calledWith(400).should.equal(true)
-        ctx.res.json.calledWith({
+        expect(ctx.res.status).toHaveBeenCalledTimes(1)
+        expect(ctx.res.status).toHaveBeenCalledWith(400)
+        expect(ctx.res.json).toHaveBeenCalledWith({
           message: { type: 'error', text: 'bad email' },
           message: { type: 'error', text: 'bad email' },
         })
         })
       })
       })
@@ -500,9 +494,9 @@ describe('LaunchpadController', function () {
       })
       })
 
 
       it('should send a 400 response', function (ctx) {
       it('should send a 400 response', function (ctx) {
-        ctx.res.status.callCount.should.equal(1)
-        ctx.res.status.calledWith(400).should.equal(true)
-        ctx.res.json.calledWith({
+        expect(ctx.res.status).toHaveBeenCalledTimes(1)
+        expect(ctx.res.status).toHaveBeenCalledWith(400)
+        expect(ctx.res.json).toHaveBeenCalledWith({
           message: { type: 'error', text: 'bad password' },
           message: { type: 'error', text: 'bad password' },
         })
         })
       })
       })
@@ -534,8 +528,8 @@ describe('LaunchpadController', function () {
       })
       })
 
 
       it('should send a 403 response', function (ctx) {
       it('should send a 403 response', function (ctx) {
-        ctx.res.status.callCount.should.equal(1)
-        ctx.res.status.calledWith(403).should.equal(true)
+        expect(ctx.res.status).toHaveBeenCalledTimes(1)
+        expect(ctx.res.status).toHaveBeenCalledWith(403)
       })
       })
 
 
       it('should not call registerNewUser', function (ctx) {
       it('should not call registerNewUser', function (ctx) {
@@ -690,8 +684,8 @@ describe('LaunchpadController', function () {
       })
       })
 
 
       it('should send back a json response', function (ctx) {
       it('should send back a json response', function (ctx) {
-        ctx.res.json.callCount.should.equal(1)
-        expect(ctx.res.json).to.have.been.calledWith({ redir: '/launchpad' })
+        expect(ctx.res.json).toHaveBeenCalledTimes(1)
+        expect(ctx.res.json).toHaveBeenCalledWith({ redir: '/launchpad' })
       })
       })
 
 
       it('should have checked for existing admins', function (ctx) {
       it('should have checked for existing admins', function (ctx) {
@@ -757,8 +751,8 @@ describe('LaunchpadController', function () {
       })
       })
 
 
       it('should send back a json response', function (ctx) {
       it('should send back a json response', function (ctx) {
-        ctx.res.json.callCount.should.equal(1)
-        expect(ctx.res.json.lastCall.args[0].email).to.equal(ctx.email)
+        expect(ctx.res.json).toHaveBeenCalledTimes(1)
+        expect(ctx.res.json.mock.lastCall[0].email).to.equal(ctx.email)
       })
       })
 
 
       it('should have checked for existing admins', function (ctx) {
       it('should have checked for existing admins', function (ctx) {
@@ -824,8 +818,8 @@ describe('LaunchpadController', function () {
       })
       })
 
 
       it('should send a 403 response', function (ctx) {
       it('should send a 403 response', function (ctx) {
-        ctx.res.sendStatus.callCount.should.equal(1)
-        ctx.res.sendStatus.calledWith(403).should.equal(true)
+        expect(ctx.res.sendStatus).toHaveBeenCalledTimes(1)
+        expect(ctx.res.sendStatus).toHaveBeenCalledWith(403)
       })
       })
 
 
       it('should not check for existing admins', function (ctx) {
       it('should not check for existing admins', function (ctx) {
@@ -859,8 +853,8 @@ describe('LaunchpadController', function () {
       })
       })
 
 
       it('should send a 400 response', function (ctx) {
       it('should send a 400 response', function (ctx) {
-        ctx.res.sendStatus.callCount.should.equal(1)
-        ctx.res.sendStatus.calledWith(400).should.equal(true)
+        expect(ctx.res.sendStatus).toHaveBeenCalledTimes(1)
+        expect(ctx.res.sendStatus).toHaveBeenCalledWith(400)
       })
       })
 
 
       it('should not check for existing admins', function (ctx) {
       it('should not check for existing admins', function (ctx) {
@@ -894,8 +888,8 @@ describe('LaunchpadController', function () {
       })
       })
 
 
       it('should send a 403 response', function (ctx) {
       it('should send a 403 response', function (ctx) {
-        ctx.res.sendStatus.callCount.should.equal(1)
-        ctx.res.sendStatus.calledWith(403).should.equal(true)
+        expect(ctx.res.sendStatus).toHaveBeenCalledTimes(1)
+        expect(ctx.res.sendStatus).toHaveBeenCalledWith(403)
       })
       })
 
 
       it('should not call registerNewUser', function (ctx) {
       it('should not call registerNewUser', function (ctx) {

+ 3 - 6
services/web/package.json

@@ -13,12 +13,9 @@
     "test:unit:all": "npm run test:unit:run_dir -- test/unit/src modules/*/test/unit/src",
     "test:unit:all": "npm run test:unit:run_dir -- test/unit/src modules/*/test/unit/src",
     "test:unit:all:silent": "npm run test:unit:all -- --reporter dot",
     "test:unit:all:silent": "npm run test:unit:all -- --reporter dot",
     "test:unit:app": "npm run test:unit:run_dir -- test/unit/src",
     "test:unit:app": "npm run test:unit:run_dir -- test/unit/src",
-    "test:unit:mocha": "npm run test:unit:mocha:run_dir -- test/unit/src modules/*/test/unit/src",
-    "test:unit:mocha:run_dir": "mocha --recursive --timeout 25000 --exit --grep=$MOCHA_GREP --require test/unit/bootstrap.js --extension=js",
-    "test:unit:esm": "npm run test:unit:esm:parallel && npm run test:unit:esm:sequential",
-    "test:unit:esm:parallel": "vitest run --project=Parallel",
-    "test:unit:esm:sequential": "vitest run --project=Sequential --no-file-parallelism",
-    "test:unit:esm:watch": "vitest",
+    "test:unit:parallel": "vitest run --project=Parallel",
+    "test:unit:sequential": "vitest run --project=Sequential --no-file-parallelism",
+    "test:unit:watch": "vitest",
     "test:frontend": "NODE_ENV=test TZ=GMT mocha --recursive --timeout 5000 --exit --extension js,jsx,mjs,ts,tsx --grep=$MOCHA_GREP --require test/frontend/bootstrap.js --ignore '**/*.spec.{js,jsx,ts,tsx}' --ignore '**/helpers/**/*.{js,jsx,ts,tsx}' test/frontend modules/*/test/frontend",
     "test:frontend": "NODE_ENV=test TZ=GMT mocha --recursive --timeout 5000 --exit --extension js,jsx,mjs,ts,tsx --grep=$MOCHA_GREP --require test/frontend/bootstrap.js --ignore '**/*.spec.{js,jsx,ts,tsx}' --ignore '**/helpers/**/*.{js,jsx,ts,tsx}' test/frontend modules/*/test/frontend",
     "test:frontend:coverage": "c8 --all --include 'frontend/js' --include 'modules/*/frontend/js' --exclude 'frontend/js/vendor' --reporter=lcov --reporter=text-summary npm run test:frontend",
     "test:frontend:coverage": "c8 --all --include 'frontend/js' --include 'modules/*/frontend/js' --exclude 'frontend/js/vendor' --reporter=lcov --reporter=text-summary npm run test:frontend",
     "test:writefull": "vitest --run --config modules/writefull/frontend/js/integration/vitest.config.ts",
     "test:writefull": "vitest --run --config modules/writefull/frontend/js/integration/vitest.config.ts",

+ 7 - 20
services/web/test/unit/src/helpers/MockResponse.js → services/web/test/acceptance/src/mocks/MockResponse.mjs

@@ -1,19 +1,6 @@
-/* eslint-disable
-    no-return-assign,
-    no-unused-vars,
-*/
-// TODO: This file was created by bulk-decaffeinate.
-// Fix any style issues and re-enable lint.
-/*
- * decaffeinate suggestions:
- * DS102: Remove unnecessary code created because of implicit returns
- * DS206: Consider reworking classes to avoid initClass
- * DS207: Consider shorter variations of null checks
- * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
- */
-const sinon = require('sinon')
-const Path = require('path')
-const contentDisposition = require('content-disposition')
+import sinon from 'sinon'
+import Path from 'node:path'
+import contentDisposition from 'content-disposition'
 
 
 class MockResponse {
 class MockResponse {
   static initClass() {
   static initClass() {
@@ -160,7 +147,7 @@ class MockResponse {
   attachment(filename) {
   attachment(filename) {
     switch (Path.extname(filename)) {
     switch (Path.extname(filename)) {
       case '.csv':
       case '.csv':
-        this.contentType('text/csv; charset=utf-8')
+        this.contentType('text/csv; charset=utf8')
         break
         break
       case '.zip':
       case '.zip':
         this.contentType('application/zip')
         this.contentType('application/zip')
@@ -168,12 +155,12 @@ class MockResponse {
       default:
       default:
         throw new Error('unexpected extension')
         throw new Error('unexpected extension')
     }
     }
-    this.header('Content-Disposition', contentDisposition(filename))
+    this.header('ContentDisposition', contentDisposition(filename))
     return this
     return this
   }
   }
 
 
   contentType(type) {
   contentType(type) {
-    this.header('Content-Type', type)
+    this.header('ContentType', type)
     this.type = type
     this.type = type
     return this
     return this
   }
   }
@@ -184,4 +171,4 @@ class MockResponse {
 }
 }
 MockResponse.initClass()
 MockResponse.initClass()
 
 
-module.exports = MockResponse
+export default MockResponse

+ 0 - 124
services/web/test/unit/bootstrap.js

@@ -1,124 +0,0 @@
-const Path = require('path')
-const sinon = require('sinon')
-require('./common_bootstrap')
-const chai = require('chai')
-
-/*
- * Chai configuration
- */
-
-// add chai.should()
-chai.should()
-
-// Load sinon-chai assertions so expect(stubFn).to.have.been.calledWith('abc')
-// has a nicer failure messages
-chai.use(require('sinon-chai'))
-
-// Load promise support for chai
-chai.use(require('chai-as-promised'))
-
-// Do not truncate assertion errors
-chai.config.truncateThreshold = 0
-/*
- * Global stubs
- */
-const globalStubsSandbox = sinon.createSandbox()
-const globalStubs = {
-  logger: {
-    debug: globalStubsSandbox.stub(),
-    info: globalStubsSandbox.stub(),
-    log: globalStubsSandbox.stub(),
-    warn: globalStubsSandbox.stub(),
-    err: globalStubsSandbox.stub(),
-    error: globalStubsSandbox.stub(),
-    fatal: globalStubsSandbox.stub(),
-  },
-}
-
-/*
- * Sandboxed module configuration
- */
-
-const SandboxedModule = require('sandboxed-module')
-SandboxedModule.configure({
-  ignoreMissing: true,
-  requires: getSandboxedModuleRequires(),
-  globals: {
-    AbortController,
-    AbortSignal,
-    Buffer,
-    Promise,
-    console,
-    process,
-    URL,
-    TextEncoder,
-    TextDecoder,
-  },
-  sourceTransformers: {
-    removeNodePrefix: function (source) {
-      return source.replace(/require\(['"]node:/g, "require('")
-    },
-  },
-})
-
-function getSandboxedModuleRequires() {
-  const requires = {
-    '@overleaf/logger': globalStubs.logger,
-  }
-
-  const internalModules = ['../../app/src/Features/Errors/Errors']
-  const externalLibs = [
-    'async',
-    'bull',
-    'json2csv',
-    'lodash',
-    'marked',
-    'moment',
-    '@overleaf/o-error',
-    'sanitize-html',
-    'sshpk',
-    'xml2js',
-    'mongodb',
-    'mongodb-legacy',
-  ]
-  for (const modulePath of internalModules) {
-    requires[Path.resolve(__dirname, modulePath)] = require(modulePath)
-  }
-  for (const lib of externalLibs) {
-    requires[lib] = require(lib)
-  }
-  return requires
-}
-
-/*
- * Mocha hooks
- */
-
-// sandboxed-module somehow registers every fake module it creates in this
-// module's children array, which uses quite a big amount of memory. We'll take
-// a copy of the module children array and restore it after each test so that
-// the garbage collector has a chance to reclaim the fake modules.
-let initialModuleChildren
-
-exports.mochaHooks = {
-  beforeAll() {
-    // Record initial module children
-    initialModuleChildren = module.children.slice()
-  },
-
-  beforeEach() {
-    // Install logger stub
-    this.logger = globalStubs.logger
-  },
-
-  afterEach() {
-    // Delete leaking sandboxed modules
-    module.children = initialModuleChildren.slice()
-
-    // Reset global stubs
-    globalStubsSandbox.reset()
-
-    // Restore other stubs
-    sinon.restore()
-  },
-}

+ 5 - 2
services/web/test/unit/vitest_bootstrap.mjs → services/web/test/unit/bootstrap.mjs

@@ -1,10 +1,13 @@
-import { chai, vi } from 'vitest'
-import './common_bootstrap.js'
+import { afterEach, beforeEach, chai, vi } from 'vitest'
+import 'sinon-mongoose'
 import sinon from 'sinon'
 import sinon from 'sinon'
 import logger from '@overleaf/logger'
 import logger from '@overleaf/logger'
 import sinonChai from 'sinon-chai'
 import sinonChai from 'sinon-chai'
 import chaiAsPromised from 'chai-as-promised'
 import chaiAsPromised from 'chai-as-promised'
 import mongoose from 'mongoose'
 import mongoose from 'mongoose'
+import mongodb from 'mongodb-legacy'
+
+mongodb.ObjectId.cacheHexString = true
 
 
 /*
 /*
  * Chai configuration
  * Chai configuration

+ 0 - 5
services/web/test/unit/common_bootstrap.js

@@ -1,5 +0,0 @@
-// add support for mongoose in sinon
-require('sinon-mongoose')
-
-// ensure every ObjectId has the id string as a property for correct comparisons
-require('mongodb-legacy').ObjectId.cacheHexString = true

+ 1 - 1
services/web/test/unit/src/Analytics/AnalyticsController.test.mjs

@@ -1,6 +1,6 @@
 import { vi } from 'vitest'
 import { vi } from 'vitest'
 import sinon from 'sinon'
 import sinon from 'sinon'
-import MockResponse from '../helpers/MockResponseVitest.mjs'
+import MockResponse from '../helpers/MockResponse.mjs'
 const modulePath = new URL(
 const modulePath = new URL(
   '../../../../app/src/Features/Analytics/AnalyticsController.mjs',
   '../../../../app/src/Features/Analytics/AnalyticsController.mjs',
   import.meta.url
   import.meta.url

+ 2 - 2
services/web/test/unit/src/Analytics/AnalyticsManager.test.mjs

@@ -1,8 +1,8 @@
 import { vi, assert } from 'vitest'
 import { vi, assert } from 'vitest'
 import path from 'node:path'
 import path from 'node:path'
 import sinon from 'sinon'
 import sinon from 'sinon'
-import MockRequest from '../helpers/MockRequestVitest.mjs'
-import MockResponse from '../helpers/MockResponseVitest.mjs'
+import MockRequest from '../helpers/MockRequest.mjs'
+import MockResponse from '../helpers/MockResponse.mjs'
 import mongodb from 'mongodb-legacy'
 import mongodb from 'mongodb-legacy'
 
 
 const { ObjectId } = mongodb
 const { ObjectId } = mongodb

+ 2 - 2
services/web/test/unit/src/Analytics/AnalyticsUTMTrackingMiddleware.test.mjs

@@ -1,7 +1,7 @@
 import { assert, vi } from 'vitest'
 import { assert, vi } from 'vitest'
 import sinon from 'sinon'
 import sinon from 'sinon'
-import MockRequest from '../helpers/MockRequestVitest.mjs'
-import MockResponse from '../helpers/MockResponseVitest.mjs'
+import MockRequest from '../helpers/MockRequest.mjs'
+import MockResponse from '../helpers/MockResponse.mjs'
 
 
 const MODULE_PATH = new URL(
 const MODULE_PATH = new URL(
   '../../../../app/src/Features/Analytics/AnalyticsUTMTrackingMiddleware',
   '../../../../app/src/Features/Analytics/AnalyticsUTMTrackingMiddleware',

+ 2 - 2
services/web/test/unit/src/Authentication/AuthenticationController.test.mjs

@@ -1,8 +1,8 @@
 import { beforeEach, describe, it, vi, expect } from 'vitest'
 import { beforeEach, describe, it, vi, expect } from 'vitest'
 import sinon from 'sinon'
 import sinon from 'sinon'
 import tk from 'timekeeper'
 import tk from 'timekeeper'
-import MockRequest from '../helpers/MockRequestVitest.mjs'
-import MockResponse from '../helpers/MockResponseVitest.mjs'
+import MockRequest from '../helpers/MockRequest.mjs'
+import MockResponse from '../helpers/MockResponse.mjs'
 import mongodb from 'mongodb-legacy'
 import mongodb from 'mongodb-legacy'
 import AuthenticationErrors from '../../../../app/src/Features/Authentication/AuthenticationErrors.mjs'
 import AuthenticationErrors from '../../../../app/src/Features/Authentication/AuthenticationErrors.mjs'
 const modulePath =
 const modulePath =

+ 1 - 1
services/web/test/unit/src/BetaProgram/BetaProgramController.test.mjs

@@ -1,7 +1,7 @@
 import { expect, vi } from 'vitest'
 import { expect, vi } from 'vitest'
 import path from 'node:path'
 import path from 'node:path'
 import sinon from 'sinon'
 import sinon from 'sinon'
-import MockResponse from '../helpers/MockResponseVitest.mjs'
+import MockResponse from '../helpers/MockResponse.mjs'
 import { fileURLToPath } from 'node:url'
 import { fileURLToPath } from 'node:url'
 
 
 const __dirname = fileURLToPath(new URL('.', import.meta.url))
 const __dirname = fileURLToPath(new URL('.', import.meta.url))

+ 2 - 2
services/web/test/unit/src/Collaborators/CollaboratorsController.test.mjs

@@ -2,8 +2,8 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
 import sinon from 'sinon'
 import sinon from 'sinon'
 import mongodb from 'mongodb-legacy'
 import mongodb from 'mongodb-legacy'
 import Errors from '../../../../app/src/Features/Errors/Errors.js'
 import Errors from '../../../../app/src/Features/Errors/Errors.js'
-import MockRequest from '../helpers/MockRequestVitest.mjs'
-import MockResponse from '../helpers/MockResponseVitest.mjs'
+import MockRequest from '../helpers/MockRequest.mjs'
+import MockResponse from '../helpers/MockResponse.mjs'
 
 
 const ObjectId = mongodb.ObjectId
 const ObjectId = mongodb.ObjectId
 
 

+ 2 - 2
services/web/test/unit/src/Collaborators/CollaboratorsInviteController.test.mjs

@@ -1,7 +1,7 @@
 import { expect, vi } from 'vitest'
 import { expect, vi } from 'vitest'
 import sinon from 'sinon'
 import sinon from 'sinon'
-import MockRequest from '../helpers/MockRequestVitest.mjs'
-import MockResponse from '../helpers/MockResponseVitest.mjs'
+import MockRequest from '../helpers/MockRequest.mjs'
+import MockResponse from '../helpers/MockResponse.mjs'
 import mongodb from 'mongodb-legacy'
 import mongodb from 'mongodb-legacy'
 import Errors from '../../../../app/src/Features/Errors/Errors.js'
 import Errors from '../../../../app/src/Features/Errors/Errors.js'
 import _ from 'lodash'
 import _ from 'lodash'

+ 2 - 2
services/web/test/unit/src/Compile/CompileController.test.mjs

@@ -1,7 +1,7 @@
 import { vi, expect } from 'vitest'
 import { vi, expect } from 'vitest'
 import sinon from 'sinon'
 import sinon from 'sinon'
-import MockRequest from '../helpers/MockRequestVitest.mjs'
-import MockResponse from '../helpers/MockResponseVitest.mjs'
+import MockRequest from '../helpers/MockRequest.mjs'
+import MockResponse from '../helpers/MockResponse.mjs'
 import { Headers } from 'node-fetch'
 import { Headers } from 'node-fetch'
 import { ReadableString } from '@overleaf/stream-utils'
 import { ReadableString } from '@overleaf/stream-utils'
 import { RequestFailedError } from '@overleaf/fetch-utils'
 import { RequestFailedError } from '@overleaf/fetch-utils'

+ 1 - 1
services/web/test/unit/src/Contact/ContactController.test.mjs

@@ -1,6 +1,6 @@
 import { expect, vi } from 'vitest'
 import { expect, vi } from 'vitest'
 import sinon from 'sinon'
 import sinon from 'sinon'
-import MockResponse from '../helpers/MockResponseVitest.mjs'
+import MockResponse from '../helpers/MockResponse.mjs'
 const modulePath = '../../../../app/src/Features/Contacts/ContactController.mjs'
 const modulePath = '../../../../app/src/Features/Contacts/ContactController.mjs'
 
 
 describe('ContactController', function () {
 describe('ContactController', function () {

+ 6 - 7
services/web/test/unit/src/DocumentUpdater/DocumentUpdaterController.test.mjs

@@ -1,6 +1,6 @@
 import { expect, vi } from 'vitest'
 import { expect, vi } from 'vitest'
 import sinon from 'sinon'
 import sinon from 'sinon'
-import MockResponse from '../helpers/MockResponse.js'
+import MockResponse from '../helpers/MockResponse.mjs'
 
 
 const MODULE_PATH =
 const MODULE_PATH =
   '../../../../app/src/Features/DocumentUpdater/DocumentUpdaterController.mjs'
   '../../../../app/src/Features/DocumentUpdater/DocumentUpdaterController.mjs'
@@ -49,7 +49,7 @@ describe('DocumentUpdaterController', function () {
       },
       },
     }
     }
     ctx.lines = ['test', '', 'testing']
     ctx.lines = ['test', '', 'testing']
-    ctx.res = new MockResponse()
+    ctx.res = new MockResponse(vi)
     ctx.next = sinon.stub()
     ctx.next = sinon.stub()
     ctx.doc = { name: 'myfile.tex' }
     ctx.doc = { name: 'myfile.tex' }
   })
   })
@@ -62,7 +62,7 @@ describe('DocumentUpdaterController', function () {
       ctx.ProjectLocator.promises.findElement.resolves({
       ctx.ProjectLocator.promises.findElement.resolves({
         element: ctx.doc,
         element: ctx.doc,
       })
       })
-      ctx.res = new MockResponse()
+      ctx.res = new MockResponse(vi)
     })
     })
 
 
     it('should call the document updater handler with the project_id and doc_id', async function (ctx) {
     it('should call the document updater handler with the project_id and doc_id', async function (ctx) {
@@ -96,10 +96,9 @@ describe('DocumentUpdaterController', function () {
 
 
     it('should set the Content-Disposition header', async function (ctx) {
     it('should set the Content-Disposition header', async function (ctx) {
       await ctx.controller.getDoc(ctx.req, ctx.res)
       await ctx.controller.getDoc(ctx.req, ctx.res)
-      expect(ctx.res.setContentDisposition).to.have.been.calledWith(
-        'attachment',
-        { filename: ctx.doc.name }
-      )
+      expect(ctx.res.setContentDisposition).toHaveBeenCalledWith('attachment', {
+        filename: ctx.doc.name,
+      })
     })
     })
   })
   })
 })
 })

+ 4 - 4
services/web/test/unit/src/Documents/DocumentController.test.mjs

@@ -1,7 +1,7 @@
 import { vi } from 'vitest'
 import { vi } from 'vitest'
 import sinon from 'sinon'
 import sinon from 'sinon'
-import MockRequest from '../helpers/MockRequest.js'
-import MockResponse from '../helpers/MockResponse.js'
+import MockRequest from '../helpers/MockRequest.mjs'
+import MockResponse from '../helpers/MockResponse.mjs'
 import Errors from '../../../../app/src/Features/Errors/Errors.js'
 import Errors from '../../../../app/src/Features/Errors/Errors.js'
 
 
 const MODULE_PATH =
 const MODULE_PATH =
@@ -9,8 +9,8 @@ const MODULE_PATH =
 
 
 describe('DocumentController', function () {
 describe('DocumentController', function () {
   beforeEach(async function (ctx) {
   beforeEach(async function (ctx) {
-    ctx.res = new MockResponse()
-    ctx.req = new MockRequest()
+    ctx.res = new MockResponse(vi)
+    ctx.req = new MockRequest(vi)
     ctx.next = sinon.stub()
     ctx.next = sinon.stub()
     ctx.doc = { _id: 'doc-id-123' }
     ctx.doc = { _id: 'doc-id-123' }
     ctx.doc_lines = ['one', 'two', 'three']
     ctx.doc_lines = ['one', 'two', 'three']

+ 6 - 10
services/web/test/unit/src/Downloads/ProjectDownloadsController.test.mjs

@@ -7,16 +7,16 @@ import { vi } from 'vitest'
  * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  */
  */
 import sinon from 'sinon'
 import sinon from 'sinon'
-import MockRequest from '../helpers/MockRequest.js'
-import MockResponse from '../helpers/MockResponse.js'
+import MockRequest from '../helpers/MockRequest.mjs'
+import MockResponse from '../helpers/MockResponse.mjs'
 const modulePath =
 const modulePath =
   '../../../../app/src/Features/Downloads/ProjectDownloadsController.mjs'
   '../../../../app/src/Features/Downloads/ProjectDownloadsController.mjs'
 
 
 describe('ProjectDownloadsController', function () {
 describe('ProjectDownloadsController', function () {
   beforeEach(async function (ctx) {
   beforeEach(async function (ctx) {
     ctx.project_id = 'project-id-123'
     ctx.project_id = 'project-id-123'
-    ctx.req = new MockRequest()
-    ctx.res = new MockResponse()
+    ctx.req = new MockRequest(vi)
+    ctx.res = new MockResponse(vi)
     ctx.next = sinon.stub()
     ctx.next = sinon.stub()
     ctx.DocumentUpdaterHandler = sinon.stub()
     ctx.DocumentUpdaterHandler = sinon.stub()
 
 
@@ -78,9 +78,7 @@ describe('ProjectDownloadsController', function () {
     })
     })
 
 
     it('should set the correct content type on the request', function (ctx) {
     it('should set the correct content type on the request', function (ctx) {
-      return ctx.res.contentType
-        .calledWith('application/zip')
-        .should.equal(true)
+      expect(ctx.res.contentType).toHaveBeenCalledWith('application/zip')
     })
     })
 
 
     it('should flush the project to mongo', function (ctx) {
     it('should flush the project to mongo', function (ctx) {
@@ -138,9 +136,7 @@ describe('ProjectDownloadsController', function () {
     })
     })
 
 
     it('should set the correct content type on the request', function (ctx) {
     it('should set the correct content type on the request', function (ctx) {
-      return ctx.res.contentType
-        .calledWith('application/zip')
-        .should.equal(true)
+      expect(ctx.res.contentType).toHaveBeenCalledWith('application/zip')
     })
     })
 
 
     it('should flush the projects to mongo', function (ctx) {
     it('should flush the projects to mongo', function (ctx) {

+ 2 - 2
services/web/test/unit/src/Editor/EditorHttpController.test.mjs

@@ -2,8 +2,8 @@ import { beforeEach, describe, it, vi, expect } from 'vitest'
 import sinon from 'sinon'
 import sinon from 'sinon'
 import mongodb from 'mongodb-legacy'
 import mongodb from 'mongodb-legacy'
 import Errors from '../../../../app/src/Features/Errors/Errors.js'
 import Errors from '../../../../app/src/Features/Errors/Errors.js'
-import MockRequest from '../helpers/MockRequestVitest.mjs'
-import MockResponse from '../helpers/MockResponseVitest.mjs'
+import MockRequest from '../helpers/MockRequest.mjs'
+import MockResponse from '../helpers/MockResponse.mjs'
 
 
 const { ObjectId } = mongodb
 const { ObjectId } = mongodb
 
 

+ 6 - 6
services/web/test/unit/src/Errors/HttpErrorHandler.test.mjs

@@ -1,12 +1,12 @@
 import { vi, expect } from 'vitest'
 import { vi, expect } from 'vitest'
-import MockResponse from '../helpers/MockResponse.js'
-import MockRequest from '../helpers/MockRequest.js'
+import MockResponse from '../helpers/MockResponse.mjs'
+import MockRequest from '../helpers/MockRequest.mjs'
 const modulePath = '../../../../app/src/Features/Errors/HttpErrorHandler.mjs'
 const modulePath = '../../../../app/src/Features/Errors/HttpErrorHandler.mjs'
 
 
 describe('HttpErrorHandler', function () {
 describe('HttpErrorHandler', function () {
   beforeEach(async function (ctx) {
   beforeEach(async function (ctx) {
-    ctx.req = new MockRequest()
-    ctx.res = new MockResponse()
+    ctx.req = new MockRequest(vi)
+    ctx.res = new MockResponse(vi)
 
 
     vi.doMock('@overleaf/settings', () => ({
     vi.doMock('@overleaf/settings', () => ({
       default: {
       default: {
@@ -274,8 +274,8 @@ describe('HttpErrorHandler', function () {
       it('should send the error to the logger', function (ctx) {
       it('should send the error to the logger', function (ctx) {
         const error = new Error('message')
         const error = new Error('message')
         ctx.HttpErrorHandler.legacyInternal(ctx.req, ctx.res, 'message', error)
         ctx.HttpErrorHandler.legacyInternal(ctx.req, ctx.res, 'message', error)
-        expect(ctx.req.logger.setLevel).to.have.been.calledWith('error')
-        expect(ctx.req.logger.addFields).to.have.been.calledWith({
+        expect(ctx.req.logger.setLevel).toHaveBeenCalledWith('error')
+        expect(ctx.req.logger.addFields).toHaveBeenCalledWith({
           err: error,
           err: error,
         })
         })
       })
       })

+ 6 - 6
services/web/test/unit/src/FileStore/FileStoreController.test.mjs

@@ -1,7 +1,7 @@
 import { expect, vi } from 'vitest'
 import { expect, vi } from 'vitest'
 import sinon from 'sinon'
 import sinon from 'sinon'
 import Errors from '../../../../app/src/Features/Errors/Errors.js'
 import Errors from '../../../../app/src/Features/Errors/Errors.js'
-import MockResponse from '../helpers/MockResponse.js'
+import MockResponse from '../helpers/MockResponse.mjs'
 
 
 const MODULE_PATH =
 const MODULE_PATH =
   '../../../../app/src/Features/FileStore/FileStoreController.mjs'
   '../../../../app/src/Features/FileStore/FileStoreController.mjs'
@@ -53,7 +53,7 @@ describe('FileStoreController', function () {
         addFields: sinon.stub(),
         addFields: sinon.stub(),
       },
       },
     }
     }
-    ctx.res = new MockResponse()
+    ctx.res = new MockResponse(vi)
     ctx.next = sinon.stub()
     ctx.next = sinon.stub()
     ctx.hash = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
     ctx.hash = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
     ctx.file = { name: 'myfile.png', hash: ctx.hash }
     ctx.file = { name: 'myfile.png', hash: ctx.hash }
@@ -92,7 +92,7 @@ describe('FileStoreController', function () {
 
 
     it('should set the Content-Disposition header', async function (ctx) {
     it('should set the Content-Disposition header', async function (ctx) {
       await ctx.controller.getFile(ctx.req, ctx.res)
       await ctx.controller.getFile(ctx.req, ctx.res)
-      ctx.res.setContentDisposition.should.be.calledWith('attachment', {
+      expect(ctx.res.setContentDisposition).toBeCalledWith('attachment', {
         filename: ctx.file.name,
         filename: ctx.file.name,
       })
       })
     })
     })
@@ -216,8 +216,8 @@ describe('FileStoreController', function () {
           .resolves({ contentLength: expectedFileSize })
           .resolves({ contentLength: expectedFileSize })
 
 
         ctx.res.end = () => {
         ctx.res.end = () => {
-          expect(ctx.res.status.lastCall.args).to.deep.equal([200])
-          expect(ctx.res.header.lastCall.args).to.deep.equal([
+          expect(ctx.res.status.mock.lastCall).to.deep.equal([200])
+          expect(ctx.res.header.mock.lastCall).to.deep.equal([
             'Content-Length',
             'Content-Length',
             expectedFileSize,
             expectedFileSize,
           ])
           ])
@@ -235,7 +235,7 @@ describe('FileStoreController', function () {
         )
         )
 
 
         ctx.res.end = () => {
         ctx.res.end = () => {
-          expect(ctx.res.status.lastCall.args).to.deep.equal([404])
+          expect(ctx.res.status.mock.lastCall).to.deep.equal([404])
           resolve()
           resolve()
         }
         }
 
 

+ 28 - 28
services/web/test/unit/src/HelperFiles/AdminAuthorizationHelper.test.mjs

@@ -1,7 +1,7 @@
 import { vi, expect } from 'vitest'
 import { vi, expect } from 'vitest'
 import sinon from 'sinon'
 import sinon from 'sinon'
-import MockRequest from '../helpers/MockRequest.js'
-import MockResponse from '../helpers/MockResponse.js'
+import MockRequest from '../helpers/MockRequest.mjs'
+import MockResponse from '../helpers/MockResponse.mjs'
 
 
 const modulePath =
 const modulePath =
   '../../../../app/src/Features/Helpers/AdminAuthorizationHelper'
   '../../../../app/src/Features/Helpers/AdminAuthorizationHelper'
@@ -68,8 +68,8 @@ describe('AdminAuthorizationHelper', function () {
     describe('when admin capabilities are not available', function () {
     describe('when admin capabilities are not available', function () {
       describe('user is null', function () {
       describe('user is null', function () {
         beforeEach(async function (ctx) {
         beforeEach(async function (ctx) {
-          ctx.req = new MockRequest()
-          ctx.res = new MockResponse()
+          ctx.req = new MockRequest(vi)
+          ctx.res = new MockResponse(vi)
           ctx.next = sinon.stub()
           ctx.next = sinon.stub()
 
 
           ctx.req.session = {
           ctx.req.session = {
@@ -93,8 +93,8 @@ describe('AdminAuthorizationHelper', function () {
       })
       })
       describe('user is not an admin', function () {
       describe('user is not an admin', function () {
         beforeEach(async function (ctx) {
         beforeEach(async function (ctx) {
-          ctx.req = new MockRequest()
-          ctx.res = new MockResponse()
+          ctx.req = new MockRequest(vi)
+          ctx.res = new MockResponse(vi)
           ctx.next = sinon.stub()
           ctx.next = sinon.stub()
 
 
           ctx.user = {
           ctx.user = {
@@ -122,8 +122,8 @@ describe('AdminAuthorizationHelper', function () {
       })
       })
       describe('user is an admin', function () {
       describe('user is an admin', function () {
         beforeEach(async function (ctx) {
         beforeEach(async function (ctx) {
-          ctx.req = new MockRequest()
-          ctx.res = new MockResponse()
+          ctx.req = new MockRequest(vi)
+          ctx.res = new MockResponse(vi)
           ctx.next = sinon.stub()
           ctx.next = sinon.stub()
 
 
           ctx.user = {
           ctx.user = {
@@ -158,8 +158,8 @@ describe('AdminAuthorizationHelper', function () {
       })
       })
       describe('user is not an admin', function () {
       describe('user is not an admin', function () {
         beforeEach(async function (ctx) {
         beforeEach(async function (ctx) {
-          ctx.req = new MockRequest()
-          ctx.res = new MockResponse()
+          ctx.req = new MockRequest(vi)
+          ctx.res = new MockResponse(vi)
           ctx.next = sinon.stub()
           ctx.next = sinon.stub()
 
 
           ctx.user = {
           ctx.user = {
@@ -187,8 +187,8 @@ describe('AdminAuthorizationHelper', function () {
       })
       })
       describe('user is an admin', function () {
       describe('user is an admin', function () {
         beforeEach(async function (ctx) {
         beforeEach(async function (ctx) {
-          ctx.req = new MockRequest()
-          ctx.res = new MockResponse()
+          ctx.req = new MockRequest(vi)
+          ctx.res = new MockResponse(vi)
           ctx.next = sinon.stub()
           ctx.next = sinon.stub()
 
 
           ctx.user = {
           ctx.user = {
@@ -221,8 +221,8 @@ describe('AdminAuthorizationHelper', function () {
       beforeEach(async function (ctx) {
       beforeEach(async function (ctx) {
         ctx.fireHook.rejects(new Error('Module error'))
         ctx.fireHook.rejects(new Error('Module error'))
 
 
-        ctx.req = new MockRequest()
-        ctx.res = new MockResponse()
+        ctx.req = new MockRequest(vi)
+        ctx.res = new MockResponse(vi)
         ctx.next = sinon.stub()
         ctx.next = sinon.stub()
 
 
         ctx.user = {
         ctx.user = {
@@ -259,8 +259,8 @@ describe('AdminAuthorizationHelper', function () {
   })
   })
   describe('useHasAdminCapability', function () {
   describe('useHasAdminCapability', function () {
     it('adds hasAdminCapability to res.locals', function (ctx) {
     it('adds hasAdminCapability to res.locals', function (ctx) {
-      const req = new MockRequest()
-      const res = new MockResponse()
+      const req = new MockRequest(vi)
+      const res = new MockResponse(vi)
       const next = sinon.stub()
       const next = sinon.stub()
 
 
       ctx.AdminAuthorizationHelper.useHasAdminCapability(req, res, next)
       ctx.AdminAuthorizationHelper.useHasAdminCapability(req, res, next)
@@ -272,8 +272,8 @@ describe('AdminAuthorizationHelper', function () {
     describe('when the user is not an admin', function () {
     describe('when the user is not an admin', function () {
       describe('when req.adminCapabilitiesAvailable is true', function () {
       describe('when req.adminCapabilitiesAvailable is true', function () {
         it('returns false for any capability', function (ctx) {
         it('returns false for any capability', function (ctx) {
-          const req = new MockRequest()
-          const res = new MockResponse()
+          const req = new MockRequest(vi)
+          const res = new MockResponse(vi)
           const next = sinon.stub()
           const next = sinon.stub()
 
 
           req.adminCapabilitiesAvailable = true
           req.adminCapabilitiesAvailable = true
@@ -289,8 +289,8 @@ describe('AdminAuthorizationHelper', function () {
 
 
       describe('when req.adminCapabilitiesAvailable is false', function () {
       describe('when req.adminCapabilitiesAvailable is false', function () {
         it('returns false for any capability', function (ctx) {
         it('returns false for any capability', function (ctx) {
-          const req = new MockRequest()
-          const res = new MockResponse()
+          const req = new MockRequest(vi)
+          const res = new MockResponse(vi)
           const next = sinon.stub()
           const next = sinon.stub()
 
 
           req.adminCapabilitiesAvailable = false
           req.adminCapabilitiesAvailable = false
@@ -306,8 +306,8 @@ describe('AdminAuthorizationHelper', function () {
 
 
       describe('when req.adminCapabilitiesAvailable is undefined', function () {
       describe('when req.adminCapabilitiesAvailable is undefined', function () {
         it('returns false for any capability', function (ctx) {
         it('returns false for any capability', function (ctx) {
-          const req = new MockRequest()
-          const res = new MockResponse()
+          const req = new MockRequest(vi)
+          const res = new MockResponse(vi)
           const next = sinon.stub()
           const next = sinon.stub()
 
 
           req.session.user = { isAdmin: false }
           req.session.user = { isAdmin: false }
@@ -322,8 +322,8 @@ describe('AdminAuthorizationHelper', function () {
     describe('user is an admin', function () {
     describe('user is an admin', function () {
       describe('when req.adminCapabilitiesAvailable is false', function () {
       describe('when req.adminCapabilitiesAvailable is false', function () {
         it('returns true for any capability', function (ctx) {
         it('returns true for any capability', function (ctx) {
-          const req = new MockRequest()
-          const res = new MockResponse()
+          const req = new MockRequest(vi)
+          const res = new MockResponse(vi)
           const next = sinon.stub()
           const next = sinon.stub()
 
 
           req.session.user = { isAdmin: true }
           req.session.user = { isAdmin: true }
@@ -337,8 +337,8 @@ describe('AdminAuthorizationHelper', function () {
 
 
       describe('when req.adminCapabilitiesAvailable is undefined', function () {
       describe('when req.adminCapabilitiesAvailable is undefined', function () {
         it('returns true for any capability', function (ctx) {
         it('returns true for any capability', function (ctx) {
-          const req = new MockRequest()
-          const res = new MockResponse()
+          const req = new MockRequest(vi)
+          const res = new MockResponse(vi)
           const next = sinon.stub()
           const next = sinon.stub()
 
 
           req.session.user = { isAdmin: true }
           req.session.user = { isAdmin: true }
@@ -352,8 +352,8 @@ describe('AdminAuthorizationHelper', function () {
       describe('when req.adminCapabilitiesAvailable is true', function () {
       describe('when req.adminCapabilitiesAvailable is true', function () {
         let req, res, next
         let req, res, next
         beforeEach(function (ctx) {
         beforeEach(function (ctx) {
-          req = new MockRequest()
-          res = new MockResponse()
+          req = new MockRequest(vi)
+          res = new MockResponse(vi)
           next = sinon.stub()
           next = sinon.stub()
 
 
           req.session.user = { isAdmin: true }
           req.session.user = { isAdmin: true }

+ 15 - 12
services/web/test/unit/src/Metadata/MetaController.test.mjs

@@ -1,6 +1,6 @@
 import { expect, vi } from 'vitest'
 import { expect, vi } from 'vitest'
 import sinon from 'sinon'
 import sinon from 'sinon'
-import MockResponse from '../helpers/MockResponse.js'
+import MockResponse from '../helpers/MockResponse.mjs'
 const modulePath = '../../../../app/src/Features/Metadata/MetaController.mjs'
 const modulePath = '../../../../app/src/Features/Metadata/MetaController.mjs'
 
 
 describe('MetaController', function () {
 describe('MetaController', function () {
@@ -45,7 +45,7 @@ describe('MetaController', function () {
         .resolves(projectMeta)
         .resolves(projectMeta)
 
 
       const req = { params: { project_id: 'project-id' } }
       const req = { params: { project_id: 'project-id' } }
-      const res = new MockResponse()
+      const res = new MockResponse(vi)
       const next = sinon.stub()
       const next = sinon.stub()
 
 
       await ctx.MetadataController.getMetadata(req, res, next)
       await ctx.MetadataController.getMetadata(req, res, next)
@@ -53,7 +53,8 @@ describe('MetaController', function () {
       ctx.MetaHandler.promises.getAllMetaForProject.should.have.been.calledWith(
       ctx.MetaHandler.promises.getAllMetaForProject.should.have.been.calledWith(
         'project-id'
         'project-id'
       )
       )
-      res.json.should.have.been.calledOnceWith({
+      expect(res.json).toHaveBeenCalledTimes(1)
+      expect(res.json).toHaveBeenCalledWith({
         projectId: 'project-id',
         projectId: 'project-id',
         projectMeta,
         projectMeta,
       })
       })
@@ -66,7 +67,7 @@ describe('MetaController', function () {
         .throws(new Error('woops'))
         .throws(new Error('woops'))
 
 
       const req = { params: { project_id: 'project-id' } }
       const req = { params: { project_id: 'project-id' } }
-      const res = new MockResponse()
+      const res = new MockResponse(vi)
       const next = sinon.stub()
       const next = sinon.stub()
 
 
       await ctx.MetadataController.getMetadata(req, res, next)
       await ctx.MetadataController.getMetadata(req, res, next)
@@ -74,7 +75,7 @@ describe('MetaController', function () {
       ctx.MetaHandler.promises.getAllMetaForProject.should.have.been.calledWith(
       ctx.MetaHandler.promises.getAllMetaForProject.should.have.been.calledWith(
         'project-id'
         'project-id'
       )
       )
-      res.json.should.not.have.been.called
+      expect(res.json).not.toHaveBeenCalled()
       next.should.have.been.calledWithMatch(error => error instanceof Error)
       next.should.have.been.calledWithMatch(error => error instanceof Error)
     })
     })
   })
   })
@@ -93,7 +94,7 @@ describe('MetaController', function () {
         params: { project_id: 'project-id', doc_id: 'doc-id' },
         params: { project_id: 'project-id', doc_id: 'doc-id' },
         body: { broadcast: true },
         body: { broadcast: true },
       }
       }
-      const res = new MockResponse()
+      const res = new MockResponse(vi)
       const next = sinon.stub()
       const next = sinon.stub()
 
 
       await ctx.MetadataController.broadcastMetadataForDoc(req, res, next)
       await ctx.MetadataController.broadcastMetadataForDoc(req, res, next)
@@ -101,8 +102,9 @@ describe('MetaController', function () {
       ctx.MetaHandler.promises.getMetaForDoc.should.have.been.calledWith(
       ctx.MetaHandler.promises.getMetaForDoc.should.have.been.calledWith(
         'project-id'
         'project-id'
       )
       )
-      res.json.should.not.have.been.called
-      res.sendStatus.should.have.been.calledOnceWith(200)
+      expect(res.json).not.toHaveBeenCalled()
+      expect(res.sendStatus).toHaveBeenCalledTimes(1)
+      expect(res.sendStatus).toHaveBeenCalledWith(200)
       next.should.not.have.been.called
       next.should.not.have.been.called
 
 
       ctx.EditorRealTimeController.emitToRoom.should.have.been.calledOnce
       ctx.EditorRealTimeController.emitToRoom.should.have.been.calledOnce
@@ -127,7 +129,7 @@ describe('MetaController', function () {
         params: { project_id: 'project-id', doc_id: 'doc-id' },
         params: { project_id: 'project-id', doc_id: 'doc-id' },
         body: { broadcast: false },
         body: { broadcast: false },
       }
       }
-      const res = new MockResponse()
+      const res = new MockResponse(vi)
       const next = sinon.stub()
       const next = sinon.stub()
 
 
       await ctx.MetadataController.broadcastMetadataForDoc(req, res, next)
       await ctx.MetadataController.broadcastMetadataForDoc(req, res, next)
@@ -136,7 +138,8 @@ describe('MetaController', function () {
         'project-id'
         'project-id'
       )
       )
       ctx.EditorRealTimeController.emitToRoom.should.not.have.been.called
       ctx.EditorRealTimeController.emitToRoom.should.not.have.been.called
-      res.json.should.have.been.calledOnceWith({
+      expect(res.json).toHaveBeenCalledTimes(1)
+      expect(res.json).toHaveBeenCalledWith({
         docId: 'doc-id',
         docId: 'doc-id',
         meta: docMeta,
         meta: docMeta,
       })
       })
@@ -154,7 +157,7 @@ describe('MetaController', function () {
         params: { project_id: 'project-id', doc_id: 'doc-id' },
         params: { project_id: 'project-id', doc_id: 'doc-id' },
         body: { broadcast: true },
         body: { broadcast: true },
       }
       }
-      const res = new MockResponse()
+      const res = new MockResponse(vi)
       const next = sinon.stub()
       const next = sinon.stub()
 
 
       await ctx.MetadataController.broadcastMetadataForDoc(req, res, next)
       await ctx.MetadataController.broadcastMetadataForDoc(req, res, next)
@@ -162,7 +165,7 @@ describe('MetaController', function () {
       ctx.MetaHandler.promises.getMetaForDoc.should.have.been.calledWith(
       ctx.MetaHandler.promises.getMetaForDoc.should.have.been.calledWith(
         'project-id'
         'project-id'
       )
       )
-      res.json.should.not.have.been.called
+      expect(res.json).not.toHaveBeenCalled()
       next.should.have.been.calledWithMatch(error => error instanceof Error)
       next.should.have.been.calledWithMatch(error => error instanceof Error)
     })
     })
   })
   })

+ 43 - 52
services/web/test/unit/src/PasswordReset/PasswordResetController.test.mjs

@@ -1,6 +1,6 @@
 import { beforeEach, describe, expect, it, vi } from 'vitest'
 import { beforeEach, describe, expect, it, vi } from 'vitest'
 import sinon from 'sinon'
 import sinon from 'sinon'
-import MockResponse from '../helpers/MockResponse.js'
+import MockResponse from '../helpers/MockResponse.mjs'
 
 
 const MODULE_PATH =
 const MODULE_PATH =
   '../../../../app/src/Features/PasswordReset/PasswordResetController.mjs'
   '../../../../app/src/Features/PasswordReset/PasswordResetController.mjs'
@@ -25,7 +25,7 @@ describe('PasswordResetController', function () {
       session: {},
       session: {},
       query: {},
       query: {},
     }
     }
-    ctx.res = new MockResponse()
+    ctx.res = new MockResponse(vi)
 
 
     ctx.settings = {}
     ctx.settings = {}
     ctx.PasswordResetHandler = {
     ctx.PasswordResetHandler = {
@@ -110,21 +110,18 @@ describe('PasswordResetController', function () {
 
 
   describe('requestReset', function () {
   describe('requestReset', function () {
     it('should tell the handler to process that email', async function (ctx) {
     it('should tell the handler to process that email', async function (ctx) {
-      await new Promise(resolve => {
-        ctx.PasswordResetHandler.promises.generateAndEmailResetToken.resolves(
-          'primary'
-        )
-        ctx.res.callback = () => {
-          ctx.res.statusCode.should.equal(200)
-          ctx.res.json.calledWith(sinon.match.has('message')).should.equal(true)
-          expect(
-            ctx.PasswordResetHandler.promises.generateAndEmailResetToken
-              .lastCall.args[0]
-          ).equal(ctx.email)
-          resolve()
-        }
-        ctx.PasswordResetController.requestReset(ctx.req, ctx.res)
-      })
+      ctx.PasswordResetHandler.promises.generateAndEmailResetToken.resolves(
+        'primary'
+      )
+      await ctx.PasswordResetController.requestReset(ctx.req, ctx.res)
+      expect(ctx.res.statusCode).to.equal(200)
+      expect(ctx.res.json).toHaveBeenCalledWith(
+        expect.objectContaining({ message: expect.anything() })
+      )
+      expect(
+        ctx.PasswordResetHandler.promises.generateAndEmailResetToken.lastCall
+          .args[0]
+      ).equal(ctx.email)
     })
     })
 
 
     it('should send a 500 if there is an error', async function (ctx) {
     it('should send a 500 if there is an error', async function (ctx) {
@@ -140,47 +137,41 @@ describe('PasswordResetController', function () {
     })
     })
 
 
     it("should send a 404 if the email doesn't exist", async function (ctx) {
     it("should send a 404 if the email doesn't exist", async function (ctx) {
-      await new Promise(resolve => {
-        ctx.PasswordResetHandler.promises.generateAndEmailResetToken.resolves(
-          null
-        )
-        ctx.res.callback = () => {
-          ctx.res.statusCode.should.equal(404)
-          ctx.res.json.calledWith(sinon.match.has('message')).should.equal(true)
-          resolve()
-        }
-        ctx.PasswordResetController.requestReset(ctx.req, ctx.res)
-      })
+      ctx.PasswordResetHandler.promises.generateAndEmailResetToken.resolves(
+        null
+      )
+
+      await ctx.PasswordResetController.requestReset(ctx.req, ctx.res)
+      expect(ctx.res.statusCode).to.equal(404)
+      expect(ctx.res.json).toHaveBeenCalledWith(
+        expect.objectContaining({ message: expect.anything() })
+      )
     })
     })
 
 
     it('should send a 404 if the email is registered as a secondard email', async function (ctx) {
     it('should send a 404 if the email is registered as a secondard email', async function (ctx) {
-      await new Promise(resolve => {
-        ctx.PasswordResetHandler.promises.generateAndEmailResetToken.resolves(
-          'secondary'
-        )
-        ctx.res.callback = () => {
-          ctx.res.statusCode.should.equal(404)
-          ctx.res.json.calledWith(sinon.match.has('message')).should.equal(true)
-          resolve()
-        }
-        ctx.PasswordResetController.requestReset(ctx.req, ctx.res)
-      })
+      ctx.PasswordResetHandler.promises.generateAndEmailResetToken.resolves(
+        'secondary'
+      )
+
+      await ctx.PasswordResetController.requestReset(ctx.req, ctx.res)
+      expect(ctx.res.statusCode).to.equal(404)
+      expect(ctx.res.json).toHaveBeenCalledWith(
+        expect.objectContaining({ message: expect.anything() })
+      )
     })
     })
 
 
     it('should normalize the email address', async function (ctx) {
     it('should normalize the email address', async function (ctx) {
-      await new Promise(resolve => {
-        ctx.email = '  UPperCaseEMAILWithSpacesAround@example.Com '
-        ctx.req.body.email = ctx.email
-        ctx.PasswordResetHandler.promises.generateAndEmailResetToken.resolves(
-          'primary'
-        )
-        ctx.res.callback = () => {
-          ctx.res.statusCode.should.equal(200)
-          ctx.res.json.calledWith(sinon.match.has('message')).should.equal(true)
-          resolve()
-        }
-        ctx.PasswordResetController.requestReset(ctx.req, ctx.res)
-      })
+      ctx.email = '  UPperCaseEMAILWithSpacesAround@example.Com '
+      ctx.req.body.email = ctx.email
+      ctx.PasswordResetHandler.promises.generateAndEmailResetToken.resolves(
+        'primary'
+      )
+
+      await ctx.PasswordResetController.requestReset(ctx.req, ctx.res)
+      expect(ctx.res.statusCode).to.equal(200)
+      expect(ctx.res.json).toHaveBeenCalledWith(
+        expect.objectContaining({ message: expect.anything() })
+      )
     })
     })
   })
   })
 
 

+ 4 - 4
services/web/test/unit/src/References/ReferencesController.test.mjs

@@ -1,7 +1,7 @@
 import { vi } from 'vitest'
 import { vi } from 'vitest'
 import sinon from 'sinon'
 import sinon from 'sinon'
-import MockRequest from '../helpers/MockRequest.js'
-import MockResponse from '../helpers/MockResponse.js'
+import MockRequest from '../helpers/MockRequest.mjs'
+import MockResponse from '../helpers/MockResponse.mjs'
 const modulePath =
 const modulePath =
   '../../../../app/src/Features/References/ReferencesController'
   '../../../../app/src/Features/References/ReferencesController'
 
 
@@ -25,13 +25,13 @@ describe('ReferencesController', function () {
     )
     )
 
 
     ctx.controller = (await import(modulePath)).default
     ctx.controller = (await import(modulePath)).default
-    ctx.req = new MockRequest()
+    ctx.req = new MockRequest(vi)
     ctx.req.params.Project_id = ctx.projectId
     ctx.req.params.Project_id = ctx.projectId
     ctx.req.body = {
     ctx.req.body = {
       docIds: (ctx.docIds = ['aaa', 'bbb']),
       docIds: (ctx.docIds = ['aaa', 'bbb']),
       shouldBroadcast: false,
       shouldBroadcast: false,
     }
     }
-    ctx.res = new MockResponse()
+    ctx.res = new MockResponse(vi)
     ctx.res.json = sinon.stub()
     ctx.res.json = sinon.stub()
     ctx.res.sendStatus = sinon.stub()
     ctx.res.sendStatus = sinon.stub()
     ctx.next = sinon.stub()
     ctx.next = sinon.stub()

+ 4 - 2
services/web/test/unit/src/Settings/SettingsTests.js → services/web/test/unit/src/Settings/Settings.test.mjs

@@ -1,5 +1,7 @@
-const chai = require('chai')
-const { expect } = chai
+import { expect } from 'vitest'
+import { createRequire } from 'node:module'
+
+const require = createRequire(import.meta.url)
 
 
 function clearSettingsCache() {
 function clearSettingsCache() {
   const monorepoPath = require
   const monorepoPath = require

+ 4 - 4
services/web/test/unit/src/SplitTests/SplitTestHandler.test.mjs

@@ -2,8 +2,8 @@ import { vi, assert, expect } from 'vitest'
 import Path from 'node:path'
 import Path from 'node:path'
 import sinon from 'sinon'
 import sinon from 'sinon'
 import mongodb from 'mongodb-legacy'
 import mongodb from 'mongodb-legacy'
-import MockRequest from '../helpers/MockRequest.js'
-import MockResponse from '../helpers/MockResponse.js'
+import MockRequest from '../helpers/MockRequest.mjs'
+import MockResponse from '../helpers/MockResponse.mjs'
 
 
 const { ObjectId } = mongodb
 const { ObjectId } = mongodb
 
 
@@ -131,8 +131,8 @@ describe('SplitTestHandler', function () {
 
 
     ctx.SplitTestHandler = (await import(MODULE_PATH)).default
     ctx.SplitTestHandler = (await import(MODULE_PATH)).default
 
 
-    ctx.req = new MockRequest()
-    ctx.res = new MockResponse()
+    ctx.req = new MockRequest(vi)
+    ctx.res = new MockResponse(vi)
   })
   })
 
 
   describe('with an existing user', function () {
   describe('with an existing user', function () {

+ 4 - 4
services/web/test/unit/src/SplitTests/SplitTestMiddleware.test.mjs

@@ -1,7 +1,7 @@
 import { vi } from 'vitest'
 import { vi } from 'vitest'
 import sinon from 'sinon'
 import sinon from 'sinon'
-import MockResponse from '../helpers/MockResponse.js'
-import MockRequest from '../helpers/MockRequest.js'
+import MockResponse from '../helpers/MockResponse.mjs'
+import MockRequest from '../helpers/MockRequest.mjs'
 
 
 const modulePath = '../../../../app/src/Features/SplitTests/SplitTestMiddleware'
 const modulePath = '../../../../app/src/Features/SplitTests/SplitTestMiddleware'
 
 
@@ -20,8 +20,8 @@ describe('SplitTestMiddleware', function () {
 
 
     ctx.SplitTestMiddleware = (await import(modulePath)).default
     ctx.SplitTestMiddleware = (await import(modulePath)).default
 
 
-    ctx.req = new MockRequest()
-    ctx.res = new MockResponse()
+    ctx.req = new MockRequest(vi)
+    ctx.res = new MockResponse(vi)
     ctx.next = sinon.stub()
     ctx.next = sinon.stub()
   })
   })
 
 

+ 21 - 23
services/web/test/unit/src/Subscription/SubscriptionController.test.mjs

@@ -1,7 +1,7 @@
 import { vi, assert, expect } from 'vitest'
 import { vi, assert, expect } from 'vitest'
 import sinon from 'sinon'
 import sinon from 'sinon'
-import MockRequest from '../helpers/MockRequest.js'
-import MockResponse from '../helpers/MockResponse.js'
+import MockRequest from '../helpers/MockRequest.mjs'
+import MockResponse from '../helpers/MockResponse.mjs'
 import SubscriptionErrors from '../../../../app/src/Features/Subscription/Errors.mjs'
 import SubscriptionErrors from '../../../../app/src/Features/Subscription/Errors.mjs'
 import SubscriptionHelper from '../../../../app/src/Features/Subscription/SubscriptionHelper.mjs'
 import SubscriptionHelper from '../../../../app/src/Features/Subscription/SubscriptionHelper.mjs'
 import { AI_ADD_ON_CODE } from '../../../../app/src/Features/Subscription/AiHelper.mjs'
 import { AI_ADD_ON_CODE } from '../../../../app/src/Features/Subscription/AiHelper.mjs'
@@ -375,8 +375,8 @@ describe('SubscriptionController', function () {
 
 
     ctx.SubscriptionController = (await import(modulePath)).default
     ctx.SubscriptionController = (await import(modulePath)).default
 
 
-    ctx.res = new MockResponse()
-    ctx.req = new MockRequest()
+    ctx.res = new MockResponse(vi)
+    ctx.req = new MockRequest(vi)
     ctx.req.body = {}
     ctx.req.body = {}
     ctx.req.query = { planCode: '123123' }
     ctx.req.query = { planCode: '123123' }
 
 
@@ -658,8 +658,8 @@ describe('SubscriptionController', function () {
 
 
   describe('pauseSubscription', function () {
   describe('pauseSubscription', function () {
     it('should throw an error if no pause length is provided', async function (ctx) {
     it('should throw an error if no pause length is provided', async function (ctx) {
-      ctx.res = new MockResponse()
-      ctx.req = new MockRequest()
+      ctx.res = new MockResponse(vi)
+      ctx.req = new MockRequest(vi)
       ctx.next = sinon.stub()
       ctx.next = sinon.stub()
       await expect(
       await expect(
         ctx.SubscriptionController.pauseSubscription(ctx.req, ctx.res, ctx.next)
         ctx.SubscriptionController.pauseSubscription(ctx.req, ctx.res, ctx.next)
@@ -667,8 +667,8 @@ describe('SubscriptionController', function () {
     })
     })
 
 
     it('should throw an error if an invalid pause length is provided', async function (ctx) {
     it('should throw an error if an invalid pause length is provided', async function (ctx) {
-      ctx.res = new MockResponse()
-      ctx.req = new MockRequest()
+      ctx.res = new MockResponse(vi)
+      ctx.req = new MockRequest(vi)
       ctx.req.params = { pauseCycles: '-10' }
       ctx.req.params = { pauseCycles: '-10' }
       ctx.next = sinon.stub()
       ctx.next = sinon.stub()
       await ctx.SubscriptionController.pauseSubscription(
       await ctx.SubscriptionController.pauseSubscription(
@@ -680,8 +680,8 @@ describe('SubscriptionController', function () {
     })
     })
 
 
     it('should return a 200 when requesting a pause', async function (ctx) {
     it('should return a 200 when requesting a pause', async function (ctx) {
-      ctx.res = new MockResponse()
-      ctx.req = new MockRequest()
+      ctx.res = new MockResponse(vi)
+      ctx.req = new MockRequest(vi)
       ctx.req.params = { pauseCycles: '3' }
       ctx.req.params = { pauseCycles: '3' }
       ctx.next = sinon.stub()
       ctx.next = sinon.stub()
       await ctx.SubscriptionController.pauseSubscription(
       await ctx.SubscriptionController.pauseSubscription(
@@ -695,8 +695,8 @@ describe('SubscriptionController', function () {
 
 
   describe('resumeSubscription', function () {
   describe('resumeSubscription', function () {
     it('should return a 200 when resuming a subscription', async function (ctx) {
     it('should return a 200 when resuming a subscription', async function (ctx) {
-      ctx.res = new MockResponse()
-      ctx.req = new MockRequest()
+      ctx.res = new MockResponse(vi)
+      ctx.req = new MockRequest(vi)
       ctx.next = sinon.stub()
       ctx.next = sinon.stub()
       await ctx.SubscriptionController.resumeSubscription(
       await ctx.SubscriptionController.resumeSubscription(
         ctx.req,
         ctx.req,
@@ -1066,7 +1066,7 @@ describe('SubscriptionController', function () {
       expect(
       expect(
         ctx.FeaturesUpdater.promises.refreshFeatures
         ctx.FeaturesUpdater.promises.refreshFeatures
       ).to.have.been.calledWith(ctx.user._id, 'add-on-purchase')
       ).to.have.been.calledWith(ctx.user._id, 'add-on-purchase')
-      expect(ctx.res.sendStatus).to.have.been.calledWith(200)
+      expect(ctx.res.sendStatus).toHaveBeenCalledWith(200)
     })
     })
 
 
     it('should handle PaymentActionRequiredError and return 402 with details', async function (ctx) {
     it('should handle PaymentActionRequiredError and return 402 with details', async function (ctx) {
@@ -1079,14 +1079,12 @@ describe('SubscriptionController', function () {
 
 
       await ctx.SubscriptionController.purchaseAddon(ctx.req, ctx.res, ctx.next)
       await ctx.SubscriptionController.purchaseAddon(ctx.req, ctx.res, ctx.next)
 
 
-      ctx.res.status.calledWith(402).should.equal(true)
-      ctx.res.json
-        .calledWith({
-          message: 'Payment action required',
-          clientSecret: 'secret123',
-          publicKey: 'pubkey456',
-        })
-        .should.equal(true)
+      expect(ctx.res.status).toHaveBeenCalledWith(402)
+      expect(ctx.res.json).toHaveBeenCalledWith({
+        message: 'Payment action required',
+        clientSecret: 'secret123',
+        publicKey: 'pubkey456',
+      })
 
 
       expect(ctx.FeaturesUpdater.promises.refreshFeatures).to.not.have.been
       expect(ctx.FeaturesUpdater.promises.refreshFeatures).to.not.have.been
         .called
         .called
@@ -1503,10 +1501,10 @@ describe('SubscriptionController', function () {
 
 
   describe('previewAddonPurchase', function () {
   describe('previewAddonPurchase', function () {
     beforeEach(function (ctx) {
     beforeEach(function (ctx) {
-      ctx.req = new MockRequest()
+      ctx.req = new MockRequest(vi)
       ctx.req.params = { addOnCode: 'assistant' }
       ctx.req.params = { addOnCode: 'assistant' }
       ctx.req.query = { purchaseReferrer: 'fake-referrer' }
       ctx.req.query = { purchaseReferrer: 'fake-referrer' }
-      ctx.res = new MockResponse()
+      ctx.res = new MockResponse(vi)
 
 
       ctx.Modules.promises.hooks.fire
       ctx.Modules.promises.hooks.fire
         .withArgs('getPaymentMethod')
         .withArgs('getPaymentMethod')

+ 3 - 3
services/web/test/unit/src/Subscription/SubscriptionGroupHandler.test.mjs

@@ -1,7 +1,7 @@
 import { vi, expect } from 'vitest'
 import { vi, expect } from 'vitest'
 import mongodb from 'mongodb-legacy'
 import mongodb from 'mongodb-legacy'
 import sinon from 'sinon'
 import sinon from 'sinon'
-import MockRequest from '../helpers/MockRequest.js'
+import MockRequest from '../helpers/MockRequest.mjs'
 import { InvalidEmailError } from '../../../../app/src/Features/Errors/Errors.js'
 import { InvalidEmailError } from '../../../../app/src/Features/Errors/Errors.js'
 
 
 const { ObjectId } = mongodb
 const { ObjectId } = mongodb
@@ -416,7 +416,7 @@ describe('SubscriptionGroupHandler', function () {
 
 
   describe('getUsersGroupSubscriptionDetails', function () {
   describe('getUsersGroupSubscriptionDetails', function () {
     beforeEach(function (ctx) {
     beforeEach(function (ctx) {
-      ctx.req = new MockRequest()
+      ctx.req = new MockRequest(vi)
       ctx.PlansLocator.findLocalPlanInSettings = sinon.stub().returns({
       ctx.PlansLocator.findLocalPlanInSettings = sinon.stub().returns({
         ...ctx.localPlanInSettings,
         ...ctx.localPlanInSettings,
         canUseFlexibleLicensing: true,
         canUseFlexibleLicensing: true,
@@ -459,7 +459,7 @@ describe('SubscriptionGroupHandler', function () {
 
 
   describe('add seats subscription change', function () {
   describe('add seats subscription change', function () {
     beforeEach(function (ctx) {
     beforeEach(function (ctx) {
-      ctx.req = new MockRequest()
+      ctx.req = new MockRequest(vi)
       Object.assign(ctx.req.body, { adding: ctx.adding })
       Object.assign(ctx.req.body, { adding: ctx.adding })
       ctx.PlansLocator.findLocalPlanInSettings = sinon.stub().returns({
       ctx.PlansLocator.findLocalPlanInSettings = sinon.stub().returns({
         ...ctx.localPlanInSettings,
         ...ctx.localPlanInSettings,

+ 4 - 4
services/web/test/unit/src/ThirdPartyDataStore/TpdsController.test.mjs

@@ -2,8 +2,8 @@ import { expect, vi } from 'vitest'
 import mongodb from 'mongodb-legacy'
 import mongodb from 'mongodb-legacy'
 import sinon from 'sinon'
 import sinon from 'sinon'
 import Errors from '../../../../app/src/Features/Errors/Errors.js'
 import Errors from '../../../../app/src/Features/Errors/Errors.js'
-import MockResponse from '../helpers/MockResponse.js'
-import MockRequest from '../helpers/MockRequest.js'
+import MockResponse from '../helpers/MockResponse.mjs'
+import MockRequest from '../helpers/MockRequest.mjs'
 
 
 const ObjectId = mongodb.ObjectId
 const ObjectId = mongodb.ObjectId
 
 
@@ -120,8 +120,8 @@ describe('TpdsController', function () {
   describe('creating a project', function () {
   describe('creating a project', function () {
     it('should yield the new projects id', async function (ctx) {
     it('should yield the new projects id', async function (ctx) {
       await new Promise(resolve => {
       await new Promise(resolve => {
-        const res = new MockResponse()
-        const req = new MockRequest()
+        const res = new MockResponse(vi)
+        const req = new MockRequest(vi)
         req.params.user_id = ctx.user_id
         req.params.user_id = ctx.user_id
         req.body = { projectName: 'foo' }
         req.body = { projectName: 'foo' }
         res.callback = err => {
         res.callback = err => {

+ 15 - 15
services/web/test/unit/src/TokenAccess/TokenAccessController.test.mjs

@@ -1,8 +1,8 @@
 import { expect, vi } from 'vitest'
 import { expect, vi } from 'vitest'
 import sinon from 'sinon'
 import sinon from 'sinon'
 import mongodb from 'mongodb-legacy'
 import mongodb from 'mongodb-legacy'
-import MockRequest from '../helpers/MockRequest.js'
-import MockResponse from '../helpers/MockResponse.js'
+import MockRequest from '../helpers/MockRequest.mjs'
+import MockResponse from '../helpers/MockResponse.mjs'
 import PrivilegeLevels from '../../../../app/src/Features/Authorization/PrivilegeLevels.mjs'
 import PrivilegeLevels from '../../../../app/src/Features/Authorization/PrivilegeLevels.mjs'
 import UrlHelper from '../../../../app/src/Features/Helpers/UrlHelper.mjs'
 import UrlHelper from '../../../../app/src/Features/Helpers/UrlHelper.mjs'
 
 
@@ -23,8 +23,8 @@ describe('TokenAccessController', function () {
       tokenAccessReadAndWrite_refs: [],
       tokenAccessReadAndWrite_refs: [],
       tokenAccessReadOnly_refs: [],
       tokenAccessReadOnly_refs: [],
     }
     }
-    ctx.req = new MockRequest()
-    ctx.res = new MockResponse()
+    ctx.req = new MockRequest(vi)
+    ctx.res = new MockResponse(vi)
     ctx.next = sinon.stub().returns()
     ctx.next = sinon.stub().returns()
 
 
     ctx.Settings = {
     ctx.Settings = {
@@ -567,7 +567,7 @@ describe('TokenAccessController', function () {
         })
         })
 
 
         it('redirects to restricted', function (ctx) {
         it('redirects to restricted', function (ctx) {
-          expect(ctx.res.json).to.have.been.calledWith({
+          expect(ctx.res.json).toHaveBeenCalledWith({
             redirect: '/restricted',
             redirect: '/restricted',
             anonWriteAccessDenied: true,
             anonWriteAccessDenied: true,
           })
           })
@@ -609,7 +609,7 @@ describe('TokenAccessController', function () {
         })
         })
 
 
         it('redirects to project', function (ctx) {
         it('redirects to project', function (ctx) {
-          expect(ctx.res.json).to.have.been.calledWith({
+          expect(ctx.res.json).toHaveBeenCalledWith({
             redirect: `/project/${ctx.project._id}`,
             redirect: `/project/${ctx.project._id}`,
             grantAnonymousAccess: 'readAndWrite',
             grantAnonymousAccess: 'readAndWrite',
           })
           })
@@ -655,7 +655,7 @@ describe('TokenAccessController', function () {
           })
           })
         })
         })
         it('returns v1 import data', function (ctx) {
         it('returns v1 import data', function (ctx) {
-          expect(ctx.res.json).to.have.been.calledWith({
+          expect(ctx.res.json).toHaveBeenCalledWith({
             v1Import: {
             v1Import: {
               status: 'canDownloadZip',
               status: 'canDownloadZip',
               projectId: ctx.token,
               projectId: ctx.token,
@@ -698,7 +698,7 @@ describe('TokenAccessController', function () {
           })
           })
         })
         })
         it('returns 404', function (ctx) {
         it('returns 404', function (ctx) {
-          expect(ctx.res.sendStatus).to.have.been.calledWith(404)
+          expect(ctx.res.sendStatus).toHaveBeenCalledWith(404)
         })
         })
         it('checks the hash prefix and includes log data', function (ctx) {
         it('checks the hash prefix and includes log data', function (ctx) {
           expect(
           expect(
@@ -768,7 +768,7 @@ describe('TokenAccessController', function () {
           ctx.req,
           ctx.req,
           ctx.res
           ctx.res
         )
         )
-        expect(ctx.res.json).to.have.been.calledWith({
+        expect(ctx.res.json).toHaveBeenCalledWith({
           redirect: `${ctx.Settings.adminUrl}/#prefix`,
           redirect: `${ctx.Settings.adminUrl}/#prefix`,
         })
         })
       })
       })
@@ -838,7 +838,7 @@ describe('TokenAccessController', function () {
       ctx.req.params = { token: ctx.token }
       ctx.req.params = { token: ctx.token }
       ctx.req.body = { tokenHashPrefix: '#prefix' }
       ctx.req.body = { tokenHashPrefix: '#prefix' }
       ctx.TokenAccessController.grantTokenAccessReadAndWrite(ctx.req, ctx.res)
       ctx.TokenAccessController.grantTokenAccessReadAndWrite(ctx.req, ctx.res)
-      expect(ctx.res.sendStatus).to.have.been.calledWith(400)
+      expect(ctx.res.sendStatus).toHaveBeenCalledWith(400)
     })
     })
   })
   })
 
 
@@ -930,7 +930,7 @@ describe('TokenAccessController', function () {
       ctx.req.params = { token: ctx.token }
       ctx.req.params = { token: ctx.token }
       ctx.req.body = { tokenHashPrefix: '#prefix' }
       ctx.req.body = { tokenHashPrefix: '#prefix' }
       ctx.TokenAccessController.grantTokenAccessReadOnly(ctx.req, ctx.res)
       ctx.TokenAccessController.grantTokenAccessReadOnly(ctx.req, ctx.res)
-      expect(ctx.res.sendStatus).to.have.been.calledWith(400)
+      expect(ctx.res.sendStatus).toHaveBeenCalledWith(400)
     })
     })
 
 
     describe('anonymous users', function () {
     describe('anonymous users', function () {
@@ -949,7 +949,7 @@ describe('TokenAccessController', function () {
       })
       })
 
 
       it('allows anonymous users and checks the token hash', function (ctx) {
       it('allows anonymous users and checks the token hash', function (ctx) {
-        expect(ctx.res.json).to.have.been.calledWith({
+        expect(ctx.res.json).toHaveBeenCalledWith({
           redirect: `/project/${ctx.project._id}`,
           redirect: `/project/${ctx.project._id}`,
           grantAnonymousAccess: 'readOnly',
           grantAnonymousAccess: 'readOnly',
         })
         })
@@ -1183,7 +1183,7 @@ describe('TokenAccessController', function () {
             ctx.user._id,
             ctx.user._id,
             PrivilegeLevels.READ_AND_WRITE
             PrivilegeLevels.READ_AND_WRITE
           )
           )
-          expect(ctx.res.sendStatus).to.have.been.calledWith(204)
+          expect(ctx.res.sendStatus).toHaveBeenCalledWith(204)
         })
         })
       })
       })
     })
     })
@@ -1223,7 +1223,7 @@ describe('TokenAccessController', function () {
             PrivilegeLevels.READ_ONLY,
             PrivilegeLevels.READ_ONLY,
             { pendingEditor: true }
             { pendingEditor: true }
           )
           )
-          expect(ctx.res.sendStatus).to.have.been.calledWith(204)
+          expect(ctx.res.sendStatus).toHaveBeenCalledWith(204)
         })
         })
       })
       })
     })
     })
@@ -1250,7 +1250,7 @@ describe('TokenAccessController', function () {
         expect(
         expect(
           ctx.TokenAccessHandler.promises.moveReadAndWriteUserToReadOnly
           ctx.TokenAccessHandler.promises.moveReadAndWriteUserToReadOnly
         ).to.have.been.calledWith(ctx.user._id, ctx.project._id)
         ).to.have.been.calledWith(ctx.user._id, ctx.project._id)
-        expect(ctx.res.sendStatus).to.have.been.calledWith(204)
+        expect(ctx.res.sendStatus).toHaveBeenCalledWith(204)
       })
       })
 
 
       it('writes a project audit log', function (ctx) {
       it('writes a project audit log', function (ctx) {

+ 4 - 4
services/web/test/unit/src/Uploads/ProjectUploadController.test.mjs

@@ -7,8 +7,8 @@
  */
  */
 import { expect, vi } from 'vitest'
 import { expect, vi } from 'vitest'
 import sinon from 'sinon'
 import sinon from 'sinon'
-import MockRequest from '../helpers/MockRequest.js'
-import MockResponse from '../helpers/MockResponse.js'
+import MockRequest from '../helpers/MockRequest.mjs'
+import MockResponse from '../helpers/MockResponse.mjs'
 import ArchiveErrors from '../../../../app/src/Features/Uploads/ArchiveErrors.mjs'
 import ArchiveErrors from '../../../../app/src/Features/Uploads/ArchiveErrors.mjs'
 
 
 const modulePath =
 const modulePath =
@@ -17,8 +17,8 @@ const modulePath =
 describe('ProjectUploadController', function () {
 describe('ProjectUploadController', function () {
   beforeEach(async function (ctx) {
   beforeEach(async function (ctx) {
     let Timer
     let Timer
-    ctx.req = new MockRequest()
-    ctx.res = new MockResponse()
+    ctx.req = new MockRequest(vi)
+    ctx.res = new MockResponse(vi)
     ctx.user_id = 'user-id-123'
     ctx.user_id = 'user-id-123'
     ctx.metrics = {
     ctx.metrics = {
       Timer: (Timer = (function () {
       Timer: (Timer = (function () {

+ 2 - 2
services/web/test/unit/src/User/UserEmailsController.test.mjs

@@ -1,7 +1,7 @@
 import { vi, assert, expect } from 'vitest'
 import { vi, assert, expect } from 'vitest'
 import { setTimeout } from 'node:timers/promises'
 import { setTimeout } from 'node:timers/promises'
-import MockRequest from '../helpers/MockRequestVitest.mjs'
-import MockResponse from '../helpers/MockResponseVitest.mjs'
+import MockRequest from '../helpers/MockRequest.mjs'
+import MockResponse from '../helpers/MockResponse.mjs'
 import Errors from '../../../../app/src/Features/Errors/Errors.js'
 import Errors from '../../../../app/src/Features/Errors/Errors.js'
 
 
 const modulePath = '../../../../app/src/Features/User/UserEmailsController.mjs'
 const modulePath = '../../../../app/src/Features/User/UserEmailsController.mjs'

+ 6 - 6
services/web/test/unit/src/User/UserInfoController.test.mjs

@@ -1,7 +1,7 @@
 import { vi, expect } from 'vitest'
 import { vi, expect } from 'vitest'
 import sinon from 'sinon'
 import sinon from 'sinon'
-import MockResponse from '../helpers/MockResponse.js'
-import MockRequest from '../helpers/MockRequest.js'
+import MockResponse from '../helpers/MockResponse.mjs'
+import MockRequest from '../helpers/MockRequest.mjs'
 import mongodb from 'mongodb-legacy'
 import mongodb from 'mongodb-legacy'
 
 
 const modulePath = '../../../../app/src/Features/User/UserInfoController.mjs'
 const modulePath = '../../../../app/src/Features/User/UserInfoController.mjs'
@@ -45,8 +45,8 @@ describe('UserInfoController', function () {
 
 
     ctx.UserInfoController = (await import(modulePath)).default
     ctx.UserInfoController = (await import(modulePath)).default
 
 
-    ctx.req = new MockRequest()
-    ctx.res = new MockResponse()
+    ctx.req = new MockRequest(vi)
+    ctx.res = new MockResponse(vi)
     ctx.next = sinon.stub()
     ctx.next = sinon.stub()
   })
   })
 
 
@@ -192,8 +192,8 @@ describe('UserInfoController', function () {
       })
       })
 
 
       it('should return the features as JSON', function (ctx) {
       it('should return the features as JSON', function (ctx) {
-        expect(ctx.res.json.callCount).to.equal(1)
-        expect(ctx.res.json.calledWith(ctx.features)).to.equal(true)
+        expect(ctx.res.json).toHaveBeenCalledTimes(1)
+        expect(ctx.res.json).toHaveBeenCalledWith(ctx.features)
       })
       })
     })
     })
 
 

+ 4 - 4
services/web/test/unit/src/User/UserPagesController.test.mjs

@@ -1,8 +1,8 @@
 import { expect, vi } from 'vitest'
 import { expect, vi } from 'vitest'
 import assert from 'node:assert'
 import assert from 'node:assert'
 import sinon from 'sinon'
 import sinon from 'sinon'
-import MockResponse from '../helpers/MockResponse.js'
-import MockRequest from '../helpers/MockRequest.js'
+import MockResponse from '../helpers/MockResponse.mjs'
+import MockRequest from '../helpers/MockRequest.mjs'
 
 
 const modulePath = '../../../../app/src/Features/User/UserPagesController'
 const modulePath = '../../../../app/src/Features/User/UserPagesController'
 
 
@@ -154,10 +154,10 @@ describe('UserPagesController', function () {
     }))
     }))
 
 
     ctx.UserPagesController = (await import(modulePath)).default
     ctx.UserPagesController = (await import(modulePath)).default
-    ctx.req = new MockRequest()
+    ctx.req = new MockRequest(vi)
     ctx.req.capabilitySet = new Set()
     ctx.req.capabilitySet = new Set()
     ctx.req.session.user = ctx.user
     ctx.req.session.user = ctx.user
-    ctx.res = new MockResponse()
+    ctx.res = new MockResponse(vi)
   })
   })
 
 
   describe('registerPage', function () {
   describe('registerPage', function () {

+ 2 - 2
services/web/test/unit/src/UserMembership/UserMembershipController.test.mjs

@@ -1,6 +1,6 @@
 import { expect, vi, describe, it, beforeEach } from 'vitest'
 import { expect, vi, describe, it, beforeEach } from 'vitest'
-import MockRequest from '../helpers/MockRequestVitest.mjs'
-import MockResponse from '../helpers/MockResponseVitest.mjs'
+import MockRequest from '../helpers/MockRequest.mjs'
+import MockResponse from '../helpers/MockResponse.mjs'
 import EntityConfigs from '../../../../app/src/Features/UserMembership/UserMembershipEntityConfigs.mjs'
 import EntityConfigs from '../../../../app/src/Features/UserMembership/UserMembershipEntityConfigs.mjs'
 import Errors from '../../../../app/src/Features/Errors/Errors.js'
 import Errors from '../../../../app/src/Features/Errors/Errors.js'
 import UserMembershipErrors from '../../../../app/src/Features/UserMembership/UserMembershipErrors.mjs'
 import UserMembershipErrors from '../../../../app/src/Features/UserMembership/UserMembershipErrors.mjs'

+ 0 - 38
services/web/test/unit/src/helpers/MockClient.js

@@ -1,38 +0,0 @@
-/* eslint-disable
-    no-unused-vars,
-*/
-// TODO: This file was created by bulk-decaffeinate.
-// Fix any style issues and re-enable lint.
-/*
- * decaffeinate suggestions:
- * DS102: Remove unnecessary code created because of implicit returns
- * DS207: Consider shorter variations of null checks
- * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
- */
-let MockClient
-const sinon = require('sinon')
-
-let idCounter = 0
-
-module.exports = MockClient = class MockClient {
-  constructor() {
-    this.attributes = {}
-    this.join = sinon.stub()
-    this.emit = sinon.stub()
-    this.disconnect = sinon.stub()
-    this.id = idCounter++
-  }
-
-  set(key, value, callback) {
-    this.attributes[key] = value
-    if (callback != null) {
-      return callback()
-    }
-  }
-
-  get(key, callback) {
-    return callback(null, this.attributes[key])
-  }
-
-  disconnect() {}
-}

+ 0 - 32
services/web/test/unit/src/helpers/MockRequest.js

@@ -1,32 +0,0 @@
-const sinon = require('sinon')
-
-class MockRequest {
-  constructor() {
-    this.session = { destroy() {} }
-
-    this.ip = '42.42.42.42'
-    this.headers = {}
-    this.params = {}
-    this.query = {}
-    this.body = {}
-    this._parsedUrl = {}
-    this.i18n = {
-      translate(str) {
-        return str
-      },
-    }
-    this.route = { path: '' }
-    this.accepts = () => {}
-    this.setHeader = () => {}
-    this.logger = {
-      addFields: sinon.stub(),
-      setLevel: sinon.stub(),
-    }
-  }
-
-  param(param) {
-    return this.params[param]
-  }
-}
-
-module.exports = MockRequest

+ 0 - 0
services/web/test/unit/src/helpers/MockRequestVitest.mjs → services/web/test/unit/src/helpers/MockRequest.mjs


+ 0 - 0
services/web/test/unit/src/helpers/MockResponseVitest.mjs → services/web/test/unit/src/helpers/MockResponse.mjs


+ 2 - 2
services/web/test/unit/src/infrastructure/HTTPPermissionsPolicy.test.mjs

@@ -1,8 +1,8 @@
 import { expect, vi } from 'vitest'
 import { expect, vi } from 'vitest'
 import sinon from 'sinon'
 import sinon from 'sinon'
 import HttpPermissionsPolicyMiddleware from '../../../../app/src/infrastructure/HttpPermissionsPolicy.mjs'
 import HttpPermissionsPolicyMiddleware from '../../../../app/src/infrastructure/HttpPermissionsPolicy.mjs'
-import MockRequest from '../helpers/MockRequestVitest.mjs'
-import MockResponse from '../helpers/MockResponseVitest.mjs'
+import MockRequest from '../helpers/MockRequest.mjs'
+import MockResponse from '../helpers/MockResponse.mjs'
 const modulePath =
 const modulePath =
   '../../../../app/src/infrastructure/HttpPermissionsPolicy.mjs'
   '../../../../app/src/infrastructure/HttpPermissionsPolicy.mjs'
 
 

+ 1 - 1
services/web/test/unit/src/infrastructure/RequestContentTypeDetection.test.mjs

@@ -1,6 +1,6 @@
 import accepts from 'accepts'
 import accepts from 'accepts'
 import { expect, vi } from 'vitest'
 import { expect, vi } from 'vitest'
-import MockRequest from '../helpers/MockRequestVitest.mjs'
+import MockRequest from '../helpers/MockRequest.mjs'
 const MODULE_PATH =
 const MODULE_PATH =
   '../../../../app/src/infrastructure/RequestContentTypeDetection.mjs'
   '../../../../app/src/infrastructure/RequestContentTypeDetection.mjs'
 
 

+ 4 - 4
services/web/test/unit/src/infrastructure/ServeStaticWrapper.test.mjs

@@ -1,8 +1,8 @@
 import { expect, vi } from 'vitest'
 import { expect, vi } from 'vitest'
 import Path from 'node:path'
 import Path from 'node:path'
 import sinon from 'sinon'
 import sinon from 'sinon'
-import MockResponse from '../helpers/MockResponse.js'
-import MockRequest from '../helpers/MockRequest.js'
+import MockResponse from '../helpers/MockResponse.mjs'
+import MockRequest from '../helpers/MockRequest.mjs'
 
 
 const modulePath = Path.join(
 const modulePath = Path.join(
   import.meta.dirname,
   import.meta.dirname,
@@ -13,8 +13,8 @@ describe('ServeStaticWrapperTests', function () {
   let error = null
   let error = null
 
 
   beforeEach(async function (ctx) {
   beforeEach(async function (ctx) {
-    ctx.req = new MockRequest()
-    ctx.res = new MockResponse()
+    ctx.req = new MockRequest(vi)
+    ctx.res = new MockResponse(vi)
     ctx.express = {
     ctx.express = {
       static: () => (req, res, next) => {
       static: () => (req, res, next) => {
         if (error) {
         if (error) {

+ 2 - 1
services/web/vitest.config.js

@@ -17,9 +17,10 @@ if (process.env.CI && process.env.MOCHA_ROOT_SUITE_NAME) {
 }
 }
 module.exports = defineConfig({
 module.exports = defineConfig({
   test: {
   test: {
-    setupFiles: ['./test/unit/vitest_bootstrap.mjs'],
+    setupFiles: ['./test/unit/bootstrap.mjs'],
     globals: true,
     globals: true,
     isolate: false,
     isolate: false,
+    passWithNoTests: true, // in case there are no tests from one project or other in a module
     projects: [
     projects: [
       {
       {
         extends: true,
         extends: true,